From 86dafa17489d56dc38d1c016b947fc5d9735d099 Mon Sep 17 00:00:00 2001 From: Azgaar Date: Mon, 26 Feb 2018 21:45:08 +0300 Subject: [PATCH] Demo version added --- Demo version/README.md | 15 + Demo version/font/fontello.css | 148 ++ Demo version/font/fontello.eot | Bin 0 -> 34836 bytes Demo version/font/fontello.svg | 216 ++ Demo version/font/fontello.ttf | Bin 0 -> 34668 bytes Demo version/font/fontello.woff | Bin 0 -> 19324 bytes Demo version/font/fontello.woff2 | Bin 0 -> 16268 bytes Demo version/fonts.css | 143 ++ Demo version/index.css | Bin 0 -> 21964 bytes Demo version/index.html | 477 ++++ Demo version/names.js | 10 + Demo version/quantize.js | 436 ++++ Demo version/script.js | 3821 ++++++++++++++++++++++++++++++ {src => WIP version}/index.css | 1 + {src => WIP version}/index.html | 0 {src => WIP version}/script.js | 9 +- {src => WIP version}/vec2.js | 0 17 files changed, 5271 insertions(+), 5 deletions(-) create mode 100644 Demo version/README.md create mode 100644 Demo version/font/fontello.css create mode 100644 Demo version/font/fontello.eot create mode 100644 Demo version/font/fontello.svg create mode 100644 Demo version/font/fontello.ttf create mode 100644 Demo version/font/fontello.woff create mode 100644 Demo version/font/fontello.woff2 create mode 100644 Demo version/fonts.css create mode 100644 Demo version/index.css create mode 100644 Demo version/index.html create mode 100644 Demo version/names.js create mode 100644 Demo version/quantize.js create mode 100644 Demo version/script.js rename {src => WIP version}/index.css (99%) rename {src => WIP version}/index.html (100%) rename {src => WIP version}/script.js (99%) rename {src => WIP version}/vec2.js (100%) diff --git a/Demo version/README.md b/Demo version/README.md new file mode 100644 index 00000000..0676ebe6 --- /dev/null +++ b/Demo version/README.md @@ -0,0 +1,15 @@ +Azgaar's _Fantasy Map Generator_ demo, v. 0.52b. Based on [D3](https://d3js.org/) Voronoi diagram rendered to svg. + +Project goal is a procedurally generated map for my *Medieval Dynasty* simulator. Map should be interactive, scalable, fast and plausible. There should be enought space to place at least 500 manors within 7 regions. The imagined area is about 200.000 km2. + +Click on the arrow to open the Options. Click on *New map* to genarate a random map based on options setup. Check out [the project wiki](https://github.com/Azgaar/Fantasy-Map-Generator/wiki) for guidance. + +This is a demo version, some new cool features are developed, but not yet deployed. Details are covered in my blog [Fantasy Maps for fun and glory](https://azgaar.wordpress.com). Comments and ideas are *highly* welcomed, kindly contact me [via email](mailto:maxganiev@yandex.ru). I would also like to see your completed or work in progress maps. For bug reports and change requests please use the main project [issues page](https://github.com/Azgaar/Fantasy-Map-Generator/issues). + +_Inspiration:_ + +* Martin O'Leary's [_Generating fantasy maps_](https://mewo2.com/notes/terrain/) + +* Amit Patel's [_Polygonal Map Generation for Games_](http://www-cs-students.stanford.edu/~amitp/game-programming/polygon-map-generation/) + +* Scott Turner's [_Here Dragons Abound_](https://heredragonsabound.blogspot.com) \ No newline at end of file diff --git a/Demo version/font/fontello.css b/Demo version/font/fontello.css new file mode 100644 index 00000000..cb295101 --- /dev/null +++ b/Demo version/font/fontello.css @@ -0,0 +1,148 @@ +@font-face { + font-family: 'fontello'; + src: url('../font/fontello.eot?69634679'); + src: url('../font/fontello.eot?69634679#iefix') format('embedded-opentype'), + url('../font/fontello.woff2?69634679') format('woff2'), + url('../font/fontello.woff?69634679') format('woff'), + url('../font/fontello.ttf?69634679') format('truetype'), + url('../font/fontello.svg?69634679#fontello') format('svg'); + font-weight: normal; + font-style: normal; +} +/* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */ +/* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */ +/* +@media screen and (-webkit-min-device-pixel-ratio:0) { + @font-face { + font-family: 'fontello'; + src: url('../font/fontello.svg?69634679#fontello') format('svg'); + } +} +*/ + + [class^="icon-"]:before, [class*=" icon-"]:before { + font-family: "fontello"; + font-style: normal; + font-weight: normal; + speak: none; + + display: inline-block; + text-decoration: inherit; + width: 1em; + text-align: center; + font-size: 1em; + margin: -1px; + padding: 0; + + /* For safety - reset parent styles, that can break glyph codes*/ + font-variant: normal; + text-transform: none; + line-height: 1em; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */ +} + +.icon-pencil:before { content: '\e800'; } /* '' */ +.icon-font:before { content: '\e801'; } /* '' */ +.icon-arrows-cw:before { content: '\e802'; } /* '' */ +.icon-doc:before { content: '\e803'; } /* '' */ +.icon-trash-empty:before { content: '\e804'; } /* '' */ +.icon-ok:before { content: '\e805'; } /* '' */ +.icon-ok-circled:before { content: '\e806'; } /* '' */ +.icon-ok-circled2:before { content: '\e807'; } /* '' */ +.icon-link:before { content: '\e808'; } /* '' */ +.icon-globe:before { content: '\e809'; } /* '' */ +.icon-plus:before { content: '\e80a'; } /* '' */ +.icon-plus-circled:before { content: '\e80b'; } /* '' */ +.icon-minus-circled:before { content: '\e80c'; } /* '' */ +.icon-minus:before { content: '\e80d'; } /* '' */ +.icon-text-height:before { content: '\e80e'; } /* '' */ +.icon-adjust:before { content: '\e80f'; } /* '' */ +.icon-tag:before { content: '\e810'; } /* '' */ +.icon-tags:before { content: '\e811'; } /* '' */ +.icon-logout:before { content: '\e812'; } /* '' */ +.icon-download:before { content: '\e813'; } /* '' */ +.icon-down-circled2:before { content: '\e814'; } /* '' */ +.icon-upload:before { content: '\e815'; } /* '' */ +.icon-up-circled2:before { content: '\e816'; } /* '' */ +.icon-cancel-circled2:before { content: '\e817'; } /* '' */ +.icon-cancel-circled:before { content: '\e818'; } /* '' */ +.icon-cancel:before { content: '\e819'; } /* '' */ +.icon-check:before { content: '\e81a'; } /* '' */ +.icon-align-left:before { content: '\e81b'; } /* '' */ +.icon-align-center:before { content: '\e81c'; } /* '' */ +.icon-align-right:before { content: '\e81d'; } /* '' */ +.icon-align-justify:before { content: '\e81e'; } /* '' */ +.icon-star:before { content: '\e81f'; } /* '' */ +.icon-star-empty:before { content: '\e820'; } /* '' */ +.icon-search:before { content: '\e821'; } /* '' */ +.icon-mail:before { content: '\e822'; } /* '' */ +.icon-eye:before { content: '\e823'; } /* '' */ +.icon-eye-off:before { content: '\e824'; } /* '' */ +.icon-pin:before { content: '\e825'; } /* '' */ +.icon-lock-open:before { content: '\e826'; } /* '' */ +.icon-lock:before { content: '\e827'; } /* '' */ +.icon-attach:before { content: '\e828'; } /* '' */ +.icon-home:before { content: '\e829'; } /* '' */ +.icon-info-circled:before { content: '\e82a'; } /* '' */ +.icon-help-circled:before { content: '\e82b'; } /* '' */ +.icon-shuffle:before { content: '\e82c'; } /* '' */ +.icon-ccw:before { content: '\e82d'; } /* '' */ +.icon-cw:before { content: '\e82e'; } /* '' */ +.icon-play:before { content: '\e82f'; } /* '' */ +.icon-play-circled2:before { content: '\e830'; } /* '' */ +.icon-down-big:before { content: '\e831'; } /* '' */ +.icon-left-big:before { content: '\e832'; } /* '' */ +.icon-right-big:before { content: '\e833'; } /* '' */ +.icon-up-big:before { content: '\e834'; } /* '' */ +.icon-up-open:before { content: '\e835'; } /* '' */ +.icon-right-open:before { content: '\e836'; } /* '' */ +.icon-left-open:before { content: '\e837'; } /* '' */ +.icon-down-open:before { content: '\e838'; } /* '' */ +.icon-cloud:before { content: '\e839'; } /* '' */ +.icon-text-width:before { content: '\e83a'; } /* '' */ +.icon-italic:before { content: '\e83b'; } /* '' */ +.icon-bold:before { content: '\e83c'; } /* '' */ +.icon-move:before { content: '\f047'; } /* '' */ +.icon-link-ext:before { content: '\f08e'; } /* '' */ +.icon-check-empty:before { content: '\f096'; } /* '' */ +.icon-docs:before { content: '\f0c5'; } /* '' */ +.icon-list-bullet:before { content: '\f0ca'; } /* '' */ +.icon-mail-alt:before { content: '\f0e0'; } /* '' */ +.icon-sitemap:before { content: '\f0e8'; } /* '' */ +.icon-exchange:before { content: '\f0ec'; } /* '' */ +.icon-download-cloud:before { content: '\f0ed'; } /* '' */ +.icon-upload-cloud:before { content: '\f0ee'; } /* '' */ +.icon-plus-squared:before { content: '\f0fe'; } /* '' */ +.icon-unlink:before { content: '\f127'; } /* '' */ +.icon-help:before { content: '\f128'; } /* '' */ +.icon-info:before { content: '\f129'; } /* '' */ +.icon-eraser:before { content: '\f12d'; } /* '' */ +.icon-rocket:before { content: '\f135'; } /* '' */ +.icon-lock-open-alt:before { content: '\f13e'; } /* '' */ +.icon-play-circled:before { content: '\f144'; } /* '' */ +.icon-minus-squared:before { content: '\f146'; } /* '' */ +.icon-minus-squared-alt:before { content: '\f147'; } /* '' */ +.icon-level-up:before { content: '\f148'; } /* '' */ +.icon-level-down:before { content: '\f149'; } /* '' */ +.icon-ok-squared:before { content: '\f14a'; } /* '' */ +.icon-expand:before { content: '\f150'; } /* '' */ +.icon-collapse:before { content: '\f151'; } /* '' */ +.icon-expand-right:before { content: '\f152'; } /* '' */ +.icon-sort-alt-up:before { content: '\f160'; } /* '' */ +.icon-sort-alt-down:before { content: '\f161'; } /* '' */ +.icon-right-circled2:before { content: '\f18e'; } /* '' */ +.icon-left-circled2:before { content: '\f190'; } /* '' */ +.icon-collapse-left:before { content: '\f191'; } /* '' */ +.icon-plus-squared-alt:before { content: '\f196'; } /* '' */ +.icon-history:before { content: '\f1da'; } /* '' */ +.icon-header:before { content: '\f1dc'; } /* '' */ +.icon-trash:before { content: '\f1f8'; } /* '' */ +.icon-brush:before { content: '\f1fc'; } /* '' */ +.icon-clone:before { content: '\f24d'; } /* '' */ +.icon-hourglass-1:before { content: '\f251'; } /* '' */ +.icon-hand-grab-o:before { content: '\f255'; } /* '' */ +.icon-hand-paper-o:before { content: '\f256'; } /* '' */ +.icon-calendar-check-o:before { content: '\f274'; } /* '' */ +.icon-map-pin:before { content: '\f276'; } /* '' */ \ No newline at end of file diff --git a/Demo version/font/fontello.eot b/Demo version/font/fontello.eot new file mode 100644 index 0000000000000000000000000000000000000000..11f05e9a056844bc633526511370525a29ae6db3 GIT binary patch literal 34836 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 literal 0 HcmV?d00001 diff --git a/Demo version/font/fontello.svg b/Demo version/font/fontello.svg new file mode 100644 index 00000000..d6e5debd --- /dev/null +++ b/Demo version/font/fontello.svg @@ -0,0 +1,216 @@ + + + +Copyright (C) 2018 by original authors @ fontello.com + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Demo version/font/fontello.ttf b/Demo version/font/fontello.ttf new file mode 100644 index 0000000000000000000000000000000000000000..6399f4aa73e4d8ede5f7f292158811143b8cf90a GIT binary patch literal 34668 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�p}j>Zz9R9PzwWY=TxR_7U6GAa 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

>b8@e7CRO@gKd@MXo7C-I>O7jZ2+vzN62rh_6%b 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 FI{;k@bdmr7 literal 0 HcmV?d00001 diff --git a/Demo version/index.html b/Demo version/index.html new file mode 100644 index 00000000..4285f51c --- /dev/null +++ b/Demo version/index.html @@ -0,0 +1,477 @@ + + + Azgaar's Fantasy Map Generator Demo + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Anchor + + + + + + + + + + + + + + + + + + +

+
+ + +
+
+
+
+ + + + +
+
+

Select preset:

+ +

Displayed layers. Drag to move, click to toggle

+
+
  • Ocean
  • +
  • Landmass
  • +
  • Heightmap
  • +
  • Cultures
  • +
  • Routes
  • +
  • Rivers
  • +
  • Countries
  • +
  • Borders
  • +
  • Relief
  • +
  • Grid
  • +
  • Labels
  • +
  • Burgs
  • +
    +
    +
    +

    Select element:

    + +
    +
    + Fill: + #5E4FA2 +
    +
    + Stroke: + #5E4FA2 +
    +
    Colors:
    +
    +
    Stroke width: + 1 +
    +
    +
    Stroke dasharray: +
    +
    +
    Stroke linecap: +
    +
    +
    Font size: +
    +
    +
    Radius: + Stroke: +
    +
    +
    Opacity: + 1 +
    +
    +
    Filter: +
    +
    +
    Color scheme: +
    +
    +

    Toggle filters:

    + + + + +
    +
    +

    Generate new map to apply the options!

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    Heightmap template + +
    Graph size + + + 1 +
    Burgs count + + + 500 +
    Countries count + + + 13 +
    Countries disbalance + + + 5
    +
    Burg influence radius + + + 100 +
    Swampness + + + 10 +
    Coastline curvature + + + 0.2 +
    Coast outline layers + +
    Coastline style + +
    +
    +
    +

    Heightmap customization:

    +
    + + + +
    + +

    Click to add a Label:

    + + +
    +

    Save / Load map:

    + + + + + +
    +
    +
    + +
    + + +
    +
    +
    +
    + + + + + + + + + + + + + +
    + Coord: 0/0; + Cell: 0; + Height: 0; + Type: no; + Flux: 0; + Region: no; + Culture: no; + River: no; + Path: no; + Score: no. +
    + + + \ No newline at end of file diff --git a/Demo version/names.js b/Demo version/names.js new file mode 100644 index 00000000..a2cc4081 --- /dev/null +++ b/Demo version/names.js @@ -0,0 +1,10 @@ +var cultures = ["Shwazen","Angshire","Luari","Latian","Toledi","Slovian","Varangian"]; +var manorNames = [ + ["Achern","Aichhalden","Aitern","Albbruck","Alpirsbach","Altensteig","Althengstett","Appenweier","Auggen","Wildbad","Badenen","Badenweiler","Baiersbronn","Ballrechten","Bellingen","Berghaupten","Bernau","Biberach","Biederbach","Binzen","Birkendorf","Birkenfeld","Bischweier","Blumberg","Bollen","Bollschweil","Bonndorf","Bosingen","Braunlingen","Breisach","Breisgau","Breitnau","Brigachtal","Buchenbach","Buggingen","Buhl","Buhlertal","Calw","Dachsberg","Dobel","Donaueschingen","Dornhan","Dornstetten","Dottingen","Dunningen","Durbach","Durrheim","Ebhausen","Ebringen","Efringen","Egenhausen","Ehrenkirchen","Ehrsberg","Eimeldingen","Eisenbach","Elzach","Elztal","Emmendingen","Endingen","Engelsbrand","Enz","Enzklosterle","Eschbronn","Ettenheim","Ettlingen","Feldberg","Fischerbach","Fischingen","Fluorn","Forbach","Freiamt","Freiburg","Freudenstadt","Friedenweiler","Friesenheim","Frohnd","Furtwangen","Gaggenau","Geisingen","Gengenbach","Gernsbach","Glatt","Glatten","Glottertal","Gorwihl","Gottenheim","Grafenhausen","Grenzach","Griesbach","Gutach","Gutenbach","Hag","Haiterbach","Hardt","Harmersbach","Hasel","Haslach","Hausach","Hausen","Hausern","Heitersheim","Herbolzheim","Herrenalb","Herrischried","Hinterzarten","Hochenschwand","Hofen","Hofstetten","Hohberg","Horb","Horben","Hornberg","Hufingen","Ibach","Ihringen","Inzlingen","Kandern","Kappel","Kappelrodeck","Karlsbad","Karlsruhe","Kehl","Keltern","Kippenheim","Kirchzarten","Konigsfeld","Krozingen","Kuppenheim","Kussaberg","Lahr","Lauchringen","Lauf","Laufenburg","Lautenbach","Lauterbach","Lenzkirch","Liebenzell","Loffenau","Loffingen","Lorrach","Lossburg","Mahlberg","Malsburg","Malsch","March","Marxzell","Marzell","Maulburg","Monchweiler","Muhlenbach","Mullheim","Munstertal","Murg","Nagold","Neubulach","Neuenburg","Neuhausen","Neuried","Neuweiler","Niedereschach","Nordrach","Oberharmersbach","Oberkirch","Oberndorf","Oberbach","Oberried","Oberwolfach","Offenburg","Ohlsbach","Oppenau","Ortenberg","otigheim","Ottenhofen","Ottersweier","Peterstal","Pfaffenweiler","Pfalzgrafenweiler","Pforzheim","Rastatt","Renchen","Rheinau","Rheinfelden","Rheinmunster","Rickenbach","Rippoldsau","Rohrdorf","Rottweil","Rummingen","Rust","Sackingen","Sasbach","Sasbachwalden","Schallbach","Schallstadt","Schapbach","Schenkenzell","Schiltach","Schliengen","Schluchsee","Schomberg","Schonach","Schonau","Schonenberg","Schonwald","Schopfheim","Schopfloch","Schramberg","Schuttertal","Schwenningen","Schworstadt","Seebach","Seelbach","Seewald","Sexau","Simmersfeld","Simonswald","Sinzheim","Solden","Staufen","Stegen","Steinach","Steinen","Steinmauern","Straubenhardt","Stuhlingen","Sulz","Sulzburg","Teinach","Tiefenbronn","Tiengen","Titisee","Todtmoos","Todtnau","Todtnauberg","Triberg","Tunau","Tuningen","uhlingen","Unterkirnach","Reichenbach","Utzenfeld","Villingen","Villingendorf","Vogtsburg","Vohrenbach","Waldachtal","Waldbronn","Waldkirch","Waldshut","Wehr","Weil","Weilheim","Weisenbach","Wembach","Wieden","Wiesental","Wildberg","Winzeln","Wittlingen","Wittnau","Wolfach","Wutach","Wutoschingen","Wyhlen","Zavelstein"], + ["Abingdon","Albrighton","Alcester","Almondbury","Altrincham","Amersham","Andover","Appleby","Ashboume","Atherstone","Aveton","Axbridge","Aylesbury","Baldock","Bamburgh","Barton","Basingstoke","Berden","Bere","Berkeley","Berwick","Betley","Bideford","Bingley","Birmingham","Blandford","Blechingley","Bodmin","Bolton","Bootham","Boroughbridge","Boscastle","Bossinney","Bramber","Brampton","Brasted","Bretford","Bridgetown","Bridlington","Bromyard","Bruton","Buckingham","Bungay","Burton","Calne","Cambridge","Canterbury","Carlisle","Castleton","Caus","Charmouth","Chawleigh","Chichester","Chillington","Chinnor","Chipping","Chisbury","Cleobury","Clifford","Clifton","Clitheroe","Cockermouth","Coleshill","Combe","Congleton","Crafthole","Crediton","Cuddenbeck","Dalton","Darlington","Dodbrooke","Drax","Dudley","Dunstable","Dunster","Dunwich","Durham","Dymock","Exeter","Exning","Faringdon","Felton","Fenny","Finedon","Flookburgh","Fowey","Frampton","Gateshead","Gatton","Godmanchester","Grampound","Grantham","Guildford","Halesowen","Halton","Harbottle","Harlow","Hatfield","Hatherleigh","Haydon","Helston","Henley","Hertford","Heytesbury","Hinckley","Hitchin","Holme","Hornby","Horsham","Kendal","Kenilworth","Kilkhampton","Kineton","Kington","Kinver","Kirby","Knaresborough","Knutsford","Launceston","Leighton","Lewes","Linton","Louth","Luton","Lyme","Lympstone","Macclesfield","Madeley","Malborough","Maldon","Manchester","Manningtree","Marazion","Marlborough","Marshfield","Mere","Merryfield","Middlewich","Midhurst","Milborne","Mitford","Modbury","Montacute","Mousehole","Newbiggin","Newborough","Newbury","Newenden","Newent","Norham","Northleach","Noss","Oakham","Olney","Orford","Ormskirk","Oswestry","Padstow","Paignton","Penkneth","Penrith","Penzance","Pershore","Petersfield","Pevensey","Pickering","Pilton","Pontefract","Portsmouth","Preston","Quatford","Reading","Redcliff","Retford","Rockingham","Romney","Rothbury","Rothwell","Salisbury","Saltash","Seaford","Seasalter","Sherston","Shifnal","Shoreham","Sidmouth","Skipsea","Skipton","Solihull","Somerton","Southam","Southwark","Standon","Stansted","Stapleton","Stottesdon","Sudbury","Swavesey","Tamerton","Tarporley","Tetbury","Thatcham","Thaxted","Thetford","Thornbury","Tintagel","Tiverton","Torksey","Totnes","Towcester","Tregoney","Trematon","Tutbury","Uxbridge","Wallingford","Wareham","Warenmouth","Wargrave","Warton","Watchet","Watford","Wendover","Westbury","Westcheap","Weymouth","Whitford","Wickwar","Wigan","Wigmore","Winchelsea","Winkleigh","Wiscombe","Witham","Witheridge","Wiveliscombe","Woodbury","Yeovil"], + ["Adon","Aillant","Amilly","Andonville","Ardon","Artenay","Ascheres","Ascoux","Attray","Aubin","Audeville","Aulnay","Autruy","Auvilliers","Auxy","Aveyron","Baccon","Bardon","Barville","Batilly","Baule","Bazoches","Beauchamps","Beaugency","Beaulieu","Beaune","Bellegarde","Boesses","Boigny","Boiscommun","Boismorand","Boisseaux","Bondaroy","Bonnee","Bonny","Bordes","Bou","Bougy","Bouilly","Boulay","Bouzonville","Bouzy","Boynes","Bray","Breteau","Briare","Briarres","Bricy","Bromeilles","Bucy","Cepoy","Cercottes","Cerdon","Cernoy","Cesarville","Chailly","Chaingy","Chalette","Chambon","Champoulet","Chanteau","Chantecoq","Chapell","Charme","Charmont","Charsonville","Chateau","Chateauneuf","Chatel","Chatenoy","Chatillon","Chaussy","Checy","Chevannes","Chevillon","Chevilly","Chevry","Chilleurs","Choux","Chuelles","Clery","Coinces","Coligny","Combleux","Combreux","Conflans","Corbeilles","Corquilleroy","Cortrat","Coudroy","Coullons","Coulmiers","Courcelles","Courcy","Courtemaux","Courtempierre","Courtenay","Cravant","Crottes","Dadonville","Dammarie","Dampierre","Darvoy","Desmonts","Dimancheville","Donnery","Dordives","Dossainville","Douchy","Dry","Echilleuses","Egry","Engenville","Epieds","Erceville","Ervauville","Escrennes","Escrignelles","Estouy","Faverelles","Fay","Feins","Ferolles","Ferrieres","Fleury","Fontenay","Foret","Foucherolles","Freville","Gatinais","Gaubertin","Gemigny","Germigny","Gidy","Gien","Girolles","Givraines","Gondreville","Grangermont","Greneville","Griselles","Guigneville","Guilly","Gyleslonains","Huetre","Huisseau","Ingrannes","Ingre","Intville","Isdes","Jargeau","Jouy","Juranville","Bussiere","Laas","Ladon","Lailly","Langesse","Leouville","Ligny","Lombreuil","Lorcy","Lorris","Loury","Louzouer","Malesherbois","Marcilly","Mardie","Mareau","Marigny","Marsainvilliers","Melleroy","Menestreau","Merinville","Messas","Meung","Mezieres","Migneres","Mignerette","Mirabeau","Montargis","Montbarrois","Montbouy","Montcresson","Montereau","Montigny","Montliard","Mormant","Morville","Moulinet","Moulon","Nancray","Nargis","Nesploy","Neuville","Neuvy","Nevoy","Nibelle","Nogent","Noyers","Ocre","Oison","Olivet","Ondreville","Onzerain","Orleans","Ormes","Orville","Oussoy","Outarville","Ouzouer","Pannecieres","Pannes","Patay","Paucourt","Pers","Pierrefitte","Pithiverais","Pithiviers","Poilly","Potier","Prefontaines","Presnoy","Pressigny","Puiseaux","Quiers","Ramoulu","Rebrechien","Rouvray","Rozieres","Rozoy","Ruan","Sandillon","Santeau","Saran","Sceaux","Seichebrieres","Semoy","Sennely","Sermaises","Sigloy","Solterre","Sougy","Sully","Sury","Tavers","Thignonville","Thimory","Thorailles","Thou","Tigy","Tivernon","Tournoisis","Trainou","Treilles","Trigueres","Trinay","Vannes","Varennes","Vennecy","Vieilles","Vienne","Viglain","Vignes","Villamblain","Villemandeur","Villemoutiers","Villemurlin","Villeneuve","Villereau","Villevoques","Villorceau","Vimory","Vitry","Vrigny","Ivre"], + ["Accumoli","Acquafondata","Acquapendente","Acuto","Affile","Agosta","Alatri","Albano","Allumiere","Alvito","Amaseno","Amatrice","Anagni","Anguillara","Anticoli","Antrodoco","Anzio","Aprilia","Aquino","Arce","Arcinazzo","Ardea","Ariccia","Arlena","Arnara","Arpino","Arsoli","Artena","Ascrea","Atina","Ausonia","Bagnoregio","Barbarano","Bassano","Bassiano","Bellegra","Belmonte","Blera","Bolsena","Bomarzo","Borbona","Borgo","Borgorose","Boville","Bracciano","Broccostella","Calcata","Camerata","Campagnano","Campodimele","Campoli","Canale","Canepina","Canino","Cantalice","Cantalupo","Canterano","Capena","Capodimonte","Capranica","Caprarola","Carbognano","Casalattico","Casalvieri","Casape","Casaprota","Casperia","Cassino","Castelforte","Castelliri","Castello","Castelnuovo","Castiglione","Castro","Castrocielo","Cave","Ceccano","Celleno","Cellere","Ceprano","Cerreto","Cervara","Cervaro","Cerveteri","Ciampino","Ciciliano","Cineto","Cisterna","Cittaducale","Cittareale","Civita","Civitavecchia","Civitella","Colfelice","Collalto","Colle","Colleferro","Collegiove","Collepardo","Collevecchio","Colli","Colonna","Concerviano","Configni","Contigliano","Corchiano","Coreno","Cori","Cottanello","Esperia","Fabrica","Faleria","Falvaterra","Fara","Farnese","Ferentino","Fiamignano","Fiano","Filacciano","Filettino","Fiuggi","Fiumicino","Fondi","Fontana","Fonte","Fontechiari","Forano","Formello","Formia","Frascati","Frasso","Frosinone","Fumone","Gaeta","Gallese","Gallicano","Gallinaro","Gavignano","Genazzano","Genzano","Gerano","Giuliano","Gorga","Gradoli","Graffignano","Greccio","Grottaferrata","Grotte","Guarcino","Guidonia","Ischia","Isola","Itri","Jenne","Labico","Labro","Ladispoli","Lanuvio","Lariano","Latera","Lenola","Leonessa","Licenza","Longone","Lubriano","Maenza","Magliano","Mandela","Manziana","Marano","Marcellina","Marcetelli","Marino","Marta","Mazzano","Mentana","Micigliano","Minturno","Mompeo","Montalto","Montasola","Monte","Montebuono","Montefiascone","Monteflavio","Montelanico","Monteleone","Montelibretti","Montenero","Monterosi","Monterotondo","Montopoli","Montorio","Moricone","Morlupo","Morolo","Morro","Nazzano","Nemi","Nepi","Nerola","Nespolo","Nettuno","Norma","Olevano","Onano","Oriolo","Orte","Orvinio","Paganico","Palestrina","Paliano","Palombara","Pastena","Patrica","Percile","Pescorocchiano","Pescosolido","Petrella","Piansano","Picinisco","Pico","Piedimonte","Piglio","Pignataro","Pisoniano","Pofi","Poggio","Poli","Pomezia","Pontecorvo","Pontinia","Ponza","Ponzano","Posta","Pozzaglia","Priverno","Proceno","Prossedi","Riano","Rieti","Rignano","Riofreddo","Ripi","Rivodutri","Rocca","Roccagiovine","Roccagorga","Roccantica","Roccasecca","Roiate","Ronciglione","Roviano","Sabaudia","Sacrofano","Salisano","Sambuci","Santa","Santi","Santopadre","Saracinesco","Scandriglia","Segni","Selci","Sermoneta","Serrone","Settefrati","Sezze","Sgurgola","Sonnino","Sora","Soriano","Sperlonga","Spigno","Stimigliano","Strangolagalli","Subiaco","Supino","Sutri","Tarano","Tarquinia","Terelle","Terracina","Tessennano","Tivoli","Toffia","Tolfa","Torre","Torri","Torrice","Torricella","Torrita","Trevi","Trevignano","Trivigliano","Turania","Tuscania","Vacone","Valentano","Vallecorsa","Vallemaio","Vallepietra","Vallerano","Vallerotonda","Vallinfreda","Valmontone","Varco","Vasanello","Vejano","Velletri","Ventotene","Veroli","Vetralla","Vicalvi","Vico","Vicovaro","Vignanello","Viterbo","Viticuso","Vitorchiano","Vivaro","Zagarolo"], + ["Abanades","Ablanque","Adobes","Ajofrin","Alameda","Alaminos","Alarilla","Albalate","Albares","Albarreal","Albendiego","Alcabon","Alcanizo","Alcaudete","Alcocer","Alcolea","Alcoroches","Aldea","Aldeanueva","Algar","Algora","Alhondiga","Alique","Almadrones","Almendral","Almoguera","Almonacid","Almorox","Alocen","Alovera","Alustante","Angon","Anguita","Anover","Anquela","Arbancon","Arbeteta","Arcicollar","Argecilla","Arges","Armallones","Armuna","Arroyo","Atanzon","Atienza","Aunon","Azuqueca","Azutan","Baides","Banos","Banuelos","Barcience","Bargas","Barriopedro","Belvis","Berninches","Borox","Brihuega","Budia","Buenaventura","Bujalaro","Burguillos","Burujon","Bustares","Cabanas","Cabanillas","Calera","Caleruela","Calzada","Camarena","Campillo","Camunas","Canizar","Canredondo","Cantalojas","Cardiel","Carmena","Carranque","Carriches","Casa","Casarrubios","Casas","Casasbuenas","Caspuenas","Castejon","Castellar","Castilforte","Castillo","Castilnuevo","Cazalegas","Cebolla","Cedillo","Cendejas","Centenera","Cervera","Checa","Chequilla","Chillaron","Chiloeches","Chozas","Chueca","Cifuentes","Cincovillas","Ciruelas","Ciruelos","Cobeja","Cobeta","Cobisa","Cogollor","Cogolludo","Condemios","Congostrina","Consuegra","Copernal","Corduente","Corral","Cuerva","Domingo","Dosbarrios","Driebes","Duron","El","Embid","Erustes","Escalona","Escalonilla","Escamilla","Escariche","Escopete","Espinosa","Espinoso","Esplegares","Esquivias","Estables","Estriegana","Fontanar","Fuembellida","Fuensalida","Fuentelsaz","Gajanejos","Galve","Galvez","Garciotum","Gascuena","Gerindote","Guadamur","Henche","Heras","Herreria","Herreruela","Hijes","Hinojosa","Hita","Hombrados","Hontanar","Hontoba","Horche","Hormigos","Huecas","Huermeces","Huerta","Hueva","Humanes","Illan","Illana","Illescas","Iniestola","Irueste","Jadraque","Jirueque","Lagartera","Las","Layos","Ledanca","Lillo","Lominchar","Loranca","Los","Lucillos","Lupiana","Luzaga","Luzon","Madridejos","Magan","Majaelrayo","Malaga","Malaguilla","Malpica","Mandayona","Mantiel","Manzaneque","Maqueda","Maranchon","Marchamalo","Marjaliza","Marrupe","Mascaraque","Masegoso","Matarrubia","Matillas","Mazarete","Mazuecos","Medranda","Megina","Mejorada","Mentrida","Mesegar","Miedes","Miguel","Millana","Milmarcos","Mirabueno","Miralrio","Mocejon","Mochales","Mohedas","Molina","Monasterio","Mondejar","Montarron","Mora","Moratilla","Morenilla","Muduex","Nambroca","Navalcan","Negredo","Noblejas","Noez","Nombela","Noves","Numancia","Nuno","Ocana","Ocentejo","Olias","Olmeda","Ontigola","Orea","Orgaz","Oropesa","Otero","Palmaces","Palomeque","Pantoja","Pardos","Paredes","Pareja","Parrillas","Pastrana","Pelahustan","Penalen","Penalver","Pepino","Peralejos","Peralveche","Pinilla","Pioz","Piqueras","Polan","Portillo","Poveda","Pozo","Pradena","Prados","Puebla","Puerto","Pulgar","Quer","Quero","Quintanar","Quismondo","Rebollosa","Recas","Renera","Retamoso","Retiendas","Riba","Rielves","Rillo","Riofrio","Robledillo","Robledo","Romanillos","Romanones","Rueda","Sacecorbo","Sacedon","Saelices","Salmeron","San","Santa","Santiuste","Santo","Sartajada","Sauca","Sayaton","Segurilla","Selas","Semillas","Sesena","Setiles","Sevilleja","Sienes","Siguenza","Solanillos","Somolinos","Sonseca","Sotillo","Sotodosos","Talavera","Tamajon","Taragudo","Taravilla","Tartanedo","Tembleque","Tendilla","Terzaga","Tierzo","Tordellego","Tordelrabano","Tordesilos","Torija","Torralba","Torre","Torrecilla","Torrecuadrada","Torrejon","Torremocha","Torrico","Torrijos","Torrubia","Tortola","Tortuera","Tortuero","Totanes","Traid","Trijueque","Trillo","Turleque","Uceda","Ugena","Ujados","Urda","Utande","Valdarachas","Valdesotos","Valhermoso","Valtablado","Valverde","Velada","Viana","Vinuelas","Yebes","Yebra","Yelamos","Yeles","Yepes","Yuncler","Yunclillos","Yuncos","Yunquera","Zaorejas","Zarzuela","Zorita"], + ["Belgorod","Beloberezhye","Belyi","Belz","Berestei","Berezhets","Berezovech","Berezutsk","Bobruisk","Bolonets","Borisov","Borovsk","Bozhesk","Bratslav","Bryansk","Brynsk","Buryn","Byhov","Chechersk","Chemesov","Cheremosh","Cherlen","Chern","Chernigov","Chernitsa","Chernobyl","Chernogorod","Chertoryesk","Chetvertnia","Demyansk","Derevesk","Devyagoresk","Dichin","Dmitrov","Dorogobuch","Dorogobuzh","Drestvin","Drokov","Drutsk","Dubechin","Dubichi","Dubki","Dubkov","Dveren","Galich","Glebovo","Glinsk","Goloty","Gomiy","Gorodets","Gorodische","Gorodno","Gorohovets","Goroshin","Gorval","Goryshon","Holm","Horobor","Hoten","Hotin","Hotmyzhsk","Ilovech","Ivan","Izborsk","Izheslavl","Kamenets","Kanev","Karachev","Karna","Kavarna","Klechesk","Klyapech","Kolomyya","Kolyvan","Kopyl","Korec","Kornik","Korochunov","Korshev","Korsun","Koshkin","Kotelno","Kovyla","Kozelsk","Kozelsk","Kremenets","Krichev","Krylatsk","Ksniatin","Kulatsk","Kursk","Kursk","Lebedev","Lida","Logosko","Lomihvost","Loshesk","Loshichi","Lubech","Lubno","Lubutsk","Lutsk","Luchin","Luki","Lukoml","Luzha","Lvov","Mtsensk","Mdin","Medniki","Melecha","Merech","Meretsk","Mescherskoe","Meshkovsk","Metlitsk","Mezetsk","Mglin","Mihailov","Mikitin","Mikulino","Miloslavichi","Mogilev","Mologa","Moreva","Mosalsk","Moschiny","Mozyr","Mstislav","Mstislavets","Muravin","Nemech","Nemiza","Nerinsk","Nichan","Novgorod","Novogorodok","Obolichi","Obolensk","Obolensk","Oleshsk","Olgov","Omelnik","Opoka","Opoki","Oreshek","Orlets","Osechen","Oster","Ostrog","Ostrov","Perelai","Peremil","Peremyshl","Pererov","Peresechen","Perevitsk","Pereyaslav","Pinsk","Ples","Polotsk","Pronsk","Proposhesk","Punia","Putivl","Rechitsa","Rodno","Rogachev","Romanov","Romny","Roslavl","Rostislavl","Rostovets","Rsha","Ruza","Rybchesk","Rylsk","Rzhavesk","Rzhev","Rzhischev","Sambor","Serensk","Serensk","Serpeysk","Shilov","Shuya","Sinech","Sizhka","Skala","Slovensk","Slutsk","Smedin","Sneporod","Snitin","Snovsk","Sochevo","Sokolec","Starica","Starodub","Stepan","Sterzh","Streshin","Sutesk","Svinetsk","Svisloch","Terebovl","Ternov","Teshilov","Teterin","Tiversk","Torchevsk","Toropets","Torzhok","Tripolye","Trubchevsk","Tur","Turov","Usvyaty","Uteshkov","Vasilkov","Velil","Velye","Venev","Venicha","Verderev","Vereya","Veveresk","Viazma","Vidbesk","Vidychev","Voino","Volodimer","Volok","Volyn","Vorobesk","Voronich","Voronok","Vorotynsk","Vrev","Vruchiy","Vselug","Vyatichsk","Vyatka","Vyshegorod","Vyshgorod","Vysokoe","Yagniatin","Yaropolch","Yasenets","Yuryev","Yuryevets","Zaraysk","Zhitomel","Zholvazh","Zizhech","Zubkov","Zudechev","Zvenigorod"], + ["Akureyri","Aldra","Alftanes","Andenes","Austbo","Auvog","Bakkafjordur","Ballangen","Bardal","Beisfjord","Bifrost","Bildudalur","Bjerka","Bjerkvik","Bjorkosen","Bliksvaer","Blokken","Blonduos","Bolga","Bolungarvik","Borg","Borgarnes","Bosmoen","Bostad","Bostrand","Botsvika","Brautarholt","Breiddalsvik","Bringsli","Brunahlid","Budardalur","Byggdakjarni","Dalvik","Djupivogur","Donnes","Drageid","Drangsnes","Egilsstadir","Eiteroga","Elvenes","Engavogen","Ertenvog","Eskifjordur","Evenes","Eyrarbakki","Fagernes","Fallmoen","Fellabaer","Fenes","Finnoya","Fjaer","Fjelldal","Flakstad","Flateyri","Flostrand","Fludir","Gardabær","Gardur","Gimstad","Givaer","Gjeroy","Gladstad","Godoya","Godoynes","Granmoen","Gravdal","Grenivik","Grimsey","Grindavik","Grytting","Hafnir","Halsa","Hauganes","Haugland","Hauknes","Hella","Helland","Hellissandur","Hestad","Higrav","Hnifsdalur","Hofn","Hofsos","Holand","Holar","Holen","Holkestad","Holmavik","Hopen","Hovden","Hrafnagil","Hrisey","Husavik","Husvik","Hvammstangi","Hvanneyri","Hveragerdi","Hvolsvollur","Igeroy","Indre","Inndyr","Innhavet","Innnes","Isafjordur","Jarklaustur","Jarnsreykir","Junkerdal","Kaldvog","Kanstad","Karlsoy","Kavosen","Keflavik","Kjelde","Kjerstad","Klakk","Kopasker","Kopavogur","Korgen","Kristnes","Krutoga","Krystad","Kvina","Lande","Laugar","Laugaras","Laugarbakki","Laugarvatn","Laupstad","Leines","Leira","Leiren","Leland","Lenvika","Loding","Lodingen","Lonsbakki","Lopsmarka","Lovund","Luroy","Maela","Melahverfi","Meloy","Mevik","Misvaer","Mornes","Mosfellsbær","Moskenes","Myken","Naurstad","Nesberg","Nesjahverfi","Nesset","Nevernes","Obygda","Ofoten","Ogskardet","Okervika","Oknes","Olafsfjordur","Oldervika","Olstad","Onstad","Oppeid","Oresvika","Orsnes","Orsvog","Osmyra","Overdal","Prestoya","Raudalaekur","Raufarhofn","Reipo","Reykholar","Reykholt","Reykjahlid","Rif","Rinoya","Rodoy","Rognan","Rosvika","Rovika","Salhus","Sanden","Sandgerdi","Sandoker","Sandset","Sandvika","Saudarkrokur","Selfoss","Selsoya","Sennesvik","Setso","Siglufjordur","Silvalen","Skagastrond","Skjerstad","Skonland","Skorvogen","Skrova","Sleneset","Snubba","Softing","Solheim","Solheimar","Sorarnoy","Sorfugloy","Sorland","Sormela","Sorvaer","Sovika","Stamsund","Stamsvika","Stave","Stokka","Stokkseyri","Storjord","Storo","Storvika","Strand","Straumen","Strendene","Sudavik","Sudureyri","Sundoya","Sydalen","Thingeyri","Thorlakshofn","Thorshofn","Tjarnabyggd","Tjotta","Tosbotn","Traelnes","Trofors","Trones","Tverro","Ulvsvog","Unnstad","Utskor","Valla","Vandved","Varmahlid","Vassos","Vevelstad","Vidrek","Vik","Vikholmen","Vogar","Vogehamn","Vopnafjordur"] +]; \ No newline at end of file diff --git a/Demo version/quantize.js b/Demo version/quantize.js new file mode 100644 index 00000000..f549dc16 --- /dev/null +++ b/Demo version/quantize.js @@ -0,0 +1,436 @@ +// Forked from color-thief.js Copyright 2011 Lokesh Dhakar under MIT license +// var pixelArray = [[190,197,190], [202,204,200], [207,214,210]]; // ... etc; +// var cmap = MMCQ.quantize(pixelArray, colorCount); +// var palette = cmap ? cmap.palette() : null; + +// Protovis. Copyright 2010 Stanford Visualization Group (http://mbostock.github.com/protovis/) +// Licensed under the BSD License: http://www.opensource.org/licenses/bsd-license.php +if (!pv) { + var pv = { + map: function(array, f) { + var o = {}; + return f ? array.map(function(d, i) { o.index = i; return f.call(o, d); }) : array.slice(); + }, + naturalOrder: function(a, b) { + return (a < b) ? -1 : ((a > b) ? 1 : 0); + }, + sum: function(array, f) { + var o = {}; + return array.reduce(f ? function(p, d, i) { o.index = i; return p + f.call(o, d); } : function(p, d) { return p + d; }, 0); + }, + max: function(array, f) { + return Math.max.apply(null, f ? pv.map(array, f) : array); + } + }; +} + +// MMCQ (Modified median cut quantization). Algorithm from the Leptonica library, modified by Nick Rabinowitz +// quantize.js Copyright 2008 Nick Rabinowitz under MIT license +var MMCQ = (function() { + // private constants + var sigbits = 5, + rshift = 8 - sigbits, + maxIterations = 1000, + fractByPopulations = 0.75; + + // get reduced-space color index for a pixel + function getColorIndex(r, g, b) { + return (r << (2 * sigbits)) + (g << sigbits) + b; + } + + // Simple priority queue + function PQueue(comparator) { + var contents = [], + sorted = false; + + function sort() { + contents.sort(comparator); + sorted = true; + } + + return { + push: function(o) { + contents.push(o); + sorted = false; + }, + peek: function(index) { + if (!sorted) sort(); + if (index===undefined) index = contents.length - 1; + return contents[index]; + }, + pop: function() { + if (!sorted) sort(); + return contents.pop(); + }, + size: function() { + return contents.length; + }, + map: function(f) { + return contents.map(f); + }, + debug: function() { + if (!sorted) sort(); + return contents; + } + }; + } + + // 3d color space box + function VBox(r1, r2, g1, g2, b1, b2, histo) { + var vbox = this; + vbox.r1 = r1; + vbox.r2 = r2; + vbox.g1 = g1; + vbox.g2 = g2; + vbox.b1 = b1; + vbox.b2 = b2; + vbox.histo = histo; + } + VBox.prototype = { + volume: function(force) { + var vbox = this; + if (!vbox._volume || force) { + vbox._volume = ((vbox.r2 - vbox.r1 + 1) * (vbox.g2 - vbox.g1 + 1) * (vbox.b2 - vbox.b1 + 1)); + } + return vbox._volume; + }, + count: function(force) { + var vbox = this, + histo = vbox.histo; + if (!vbox._count_set || force) { + var npix = 0, + index, i, j, k; + for (i = vbox.r1; i <= vbox.r2; i++) { + for (j = vbox.g1; j <= vbox.g2; j++) { + for (k = vbox.b1; k <= vbox.b2; k++) { + index = getColorIndex(i,j,k); + npix += (histo[index] || 0); + } + } + } + vbox._count = npix; + vbox._count_set = true; + } + return vbox._count; + }, + copy: function() { + var vbox = this; + return new VBox(vbox.r1, vbox.r2, vbox.g1, vbox.g2, vbox.b1, vbox.b2, vbox.histo); + }, + avg: function(force) { + var vbox = this, + histo = vbox.histo; + if (!vbox._avg || force) { + var ntot = 0, + mult = 1 << (8 - sigbits), + rsum = 0, + gsum = 0, + bsum = 0, + hval, + i, j, k, histoindex; + for (i = vbox.r1; i <= vbox.r2; i++) { + for (j = vbox.g1; j <= vbox.g2; j++) { + for (k = vbox.b1; k <= vbox.b2; k++) { + histoindex = getColorIndex(i,j,k); + hval = histo[histoindex] || 0; + ntot += hval; + rsum += (hval * (i + 0.5) * mult); + gsum += (hval * (j + 0.5) * mult); + bsum += (hval * (k + 0.5) * mult); + } + } + } + if (ntot) { + vbox._avg = [~~(rsum/ntot), ~~(gsum/ntot), ~~(bsum/ntot)]; + } else { +// console.log('empty box'); + vbox._avg = [ + ~~(mult * (vbox.r1 + vbox.r2 + 1) / 2), + ~~(mult * (vbox.g1 + vbox.g2 + 1) / 2), + ~~(mult * (vbox.b1 + vbox.b2 + 1) / 2) + ]; + } + } + return vbox._avg; + }, + contains: function(pixel) { + var vbox = this, + rval = pixel[0] >> rshift; + gval = pixel[1] >> rshift; + bval = pixel[2] >> rshift; + return (rval >= vbox.r1 && rval <= vbox.r2 && + gval >= vbox.g1 && gval <= vbox.g2 && + bval >= vbox.b1 && bval <= vbox.b2); + } + }; + + // Color map + function CMap() { + this.vboxes = new PQueue(function(a,b) { + return pv.naturalOrder( + a.vbox.count()*a.vbox.volume(), + b.vbox.count()*b.vbox.volume() + ); + }); + } + CMap.prototype = { + push: function(vbox) { + this.vboxes.push({ + vbox: vbox, + color: vbox.avg() + }); + }, + palette: function() { + return this.vboxes.map(function(vb) { return vb.color; }); + }, + size: function() { + return this.vboxes.size(); + }, + map: function(color) { + var vboxes = this.vboxes; + for (var i=0; i 251 + var idx = vboxes.length-1, + highest = vboxes[idx].color; + if (highest[0] > 251 && highest[1] > 251 && highest[2] > 251) + vboxes[idx].color = [255,255,255]; + } + }; + + // histo (1-d array, giving the number of pixels in + // each quantized region of color space), or null on error + function getHisto(pixels) { + var histosize = 1 << (3 * sigbits), + histo = new Array(histosize), + index, rval, gval, bval; + pixels.forEach(function(pixel) { + rval = pixel[0] >> rshift; + gval = pixel[1] >> rshift; + bval = pixel[2] >> rshift; + index = getColorIndex(rval, gval, bval); + histo[index] = (histo[index] || 0) + 1; + }); + return histo; + } + + function vboxFromPixels(pixels, histo) { + var rmin=1000000, rmax=0, + gmin=1000000, gmax=0, + bmin=1000000, bmax=0, + rval, gval, bval; + // find min/max + pixels.forEach(function(pixel) { + rval = pixel[0] >> rshift; + gval = pixel[1] >> rshift; + bval = pixel[2] >> rshift; + if (rval < rmin) rmin = rval; + else if (rval > rmax) rmax = rval; + if (gval < gmin) gmin = gval; + else if (gval > gmax) gmax = gval; + if (bval < bmin) bmin = bval; + else if (bval > bmax) bmax = bval; + }); + return new VBox(rmin, rmax, gmin, gmax, bmin, bmax, histo); + } + + function medianCutApply(histo, vbox) { + if (!vbox.count()) return; + + var rw = vbox.r2 - vbox.r1 + 1, + gw = vbox.g2 - vbox.g1 + 1, + bw = vbox.b2 - vbox.b1 + 1, + maxw = pv.max([rw, gw, bw]); + // only one pixel, no split + if (vbox.count() == 1) { + return [vbox.copy()]; + } + /* Find the partial sum arrays along the selected axis. */ + var total = 0, + partialsum = [], + lookaheadsum = [], + i, j, k, sum, index; + if (maxw == rw) { + for (i = vbox.r1; i <= vbox.r2; i++) { + sum = 0; + for (j = vbox.g1; j <= vbox.g2; j++) { + for (k = vbox.b1; k <= vbox.b2; k++) { + index = getColorIndex(i,j,k); + sum += (histo[index] || 0); + } + } + total += sum; + partialsum[i] = total; + } + } + else if (maxw == gw) { + for (i = vbox.g1; i <= vbox.g2; i++) { + sum = 0; + for (j = vbox.r1; j <= vbox.r2; j++) { + for (k = vbox.b1; k <= vbox.b2; k++) { + index = getColorIndex(j,i,k); + sum += (histo[index] || 0); + } + } + total += sum; + partialsum[i] = total; + } + } + else { /* maxw == bw */ + for (i = vbox.b1; i <= vbox.b2; i++) { + sum = 0; + for (j = vbox.r1; j <= vbox.r2; j++) { + for (k = vbox.g1; k <= vbox.g2; k++) { + index = getColorIndex(j,k,i); + sum += (histo[index] || 0); + } + } + total += sum; + partialsum[i] = total; + } + } + partialsum.forEach(function(d,i) { + lookaheadsum[i] = total-d; + }); + function doCut(color) { + var dim1 = color + '1', + dim2 = color + '2', + left, right, vbox1, vbox2, d2, count2=0; + for (i = vbox[dim1]; i <= vbox[dim2]; i++) { + if (partialsum[i] > total / 2) { + vbox1 = vbox.copy(); + vbox2 = vbox.copy(); + left = i - vbox[dim1]; + right = vbox[dim2] - i; + if (left <= right) + d2 = Math.min(vbox[dim2] - 1, ~~(i + right / 2)); + else d2 = Math.max(vbox[dim1], ~~(i - 1 - left / 2)); + // avoid 0-count boxes + while (!partialsum[d2]) d2++; + count2 = lookaheadsum[d2]; + while (!count2 && partialsum[d2-1]) count2 = lookaheadsum[--d2]; + // set dimensions + vbox1[dim2] = d2; + vbox2[dim1] = vbox1[dim2] + 1; +// console.log('vbox counts:', vbox.count(), vbox1.count(), vbox2.count()); + return [vbox1, vbox2]; + } + } + + } + // determine the cut planes + return maxw == rw ? doCut('r') : + maxw == gw ? doCut('g') : + doCut('b'); + } + + function quantize(pixels, maxcolors) { + maxcolors++; + if (!pixels.length || maxcolors < 2 || maxcolors > 256) {return false;} + + // XXX: check color content and convert to grayscale if insufficient + var histo = getHisto(pixels), + histosize = 1 << (3 * sigbits); + + // check that we aren't below maxcolors already + var nColors = 0; + histo.forEach(function() { nColors++; }); + if (nColors <= maxcolors) { + // XXX: generate the new colors from the histo and return + } + + // get the beginning vbox from the colors + var vbox = vboxFromPixels(pixels, histo), + pq = new PQueue(function(a,b) { return pv.naturalOrder(a.count(), b.count()); }); + pq.push(vbox); + + // inner function to do the iteration + function iter(lh, target) { + var ncolors = 1, + niters = 0, + vbox; + while (niters < maxIterations) { + vbox = lh.pop(); + if (!vbox.count()) { /* just put it back */ + lh.push(vbox); + niters++; + continue; + } + // do the cut + var vboxes = medianCutApply(histo, vbox), + vbox1 = vboxes[0], + vbox2 = vboxes[1]; + + if (!vbox1) { +// console.log("vbox1 not defined; shouldn't happen!"); + return; + } + lh.push(vbox1); + if (vbox2) { /* vbox2 can be null */ + lh.push(vbox2); + ncolors++; + } + if (ncolors >= target) return; + if (niters++ > maxIterations) { +// console.log("infinite loop; perhaps too few pixels!"); + return; + } + } + } + + // first set of colors, sorted by population + iter(pq, fractByPopulations * maxcolors); + + // Re-sort by the product of pixel occupancy times the size in color space. + var pq2 = new PQueue(function(a,b) { + return pv.naturalOrder(a.count()*a.volume(), b.count()*b.volume()); + }); + while (pq.size()) { + pq2.push(pq.pop()); + } + + // next set - generate the median cuts using the (npix * vol) sorting. + iter(pq2, maxcolors - pq2.size()); + + // calculate the actual colors + var cmap = new CMap(); + while (pq2.size()) {cmap.push(pq2.pop());} + + return cmap; + } + + return { + quantize: quantize + }; +})(); \ No newline at end of file diff --git a/Demo version/script.js b/Demo version/script.js new file mode 100644 index 00000000..ab520e17 --- /dev/null +++ b/Demo version/script.js @@ -0,0 +1,3821 @@ +// Fantasy Map Generator main script +"use strict;" +fantasyMap(); +function fantasyMap() { + // Declare variables + var svg = d3.select("svg"), + mapWidth = +svg.attr("width"), + mapHeight = +svg.attr("height"), + defs = svg.select("#deftemp"), + viewbox = svg.append("g").attr("id", "viewbox").on("touchmove mousemove", moved).on("click", clicked), + ocean = viewbox.append("g").attr("id", "ocean"), + oceanLayers = ocean.append("g").attr("id", "oceanLayers"), + oceanPattern = ocean.append("g").attr("id", "oceanPattern"), + landmass = viewbox.append("g").attr("id", "landmass"), + terrs = viewbox.append("g").attr("id", "terrs"), + cults = viewbox.append("g").attr("id", "cults"), + routes = viewbox.append("g").attr("id", "routes"), + roads = routes.append("g").attr("id", "roads"), + trails = routes.append("g").attr("id", "trails"), + rivers = viewbox.append("g").attr("id", "rivers"), + terrain = viewbox.append("g").attr("id", "terrain"), + regions = viewbox.append("g").attr("id", "regions"), + borders = viewbox.append("g").attr("id", "borders"), + stateBorders = borders.append("g").attr("id", "stateBorders"), + neutralBorders = borders.append("g").attr("id", "neutralBorders"), + coastline = viewbox.append("g").attr("id", "coastline"), + lakes = viewbox.append("g").attr("id", "lakes"), + grid = viewbox.append("g").attr("id", "grid"), + searoutes = routes.append("g").attr("id", "searoutes"), + labels = viewbox.append("g").attr("id", "labels"), + icons = viewbox.append("g").attr("id", "icons"), + burgs = icons.append("g").attr("id", "burgs"), + debug = viewbox.append("g").attr("id", "debug"); + + // Declare styles + landmass.attr("fill", "#eef6fb"); + coastline.attr("opacity", .5).attr("stroke", "#1f3846").attr("stroke-width", .7).attr("filter", "url(#blurFilter)"); + regions.attr("opacity", .55); + stateBorders.attr("opacity", .8).attr("stroke", "#56566d").attr("stroke-width", .5).attr("stroke-dasharray", "1.2 1.5").attr("stroke-linecap", "butt"); + neutralBorders.attr("opacity", .8).attr("stroke", "#56566d").attr("stroke-width", .3).attr("stroke-dasharray", "1 1.5").attr("stroke-linecap", "butt"); + cults.attr("opacity", .6); + rivers.attr("fill", "#5d97bb"); + lakes.attr("fill", "#a6c1fd").attr("stroke", "#477794").attr("stroke-width", .3); + burgs.attr("fill", "#ffffff").attr("stroke", "#3e3e4b"); + roads.attr("opacity", .8).attr("stroke", "#d06324").attr("stroke-width", .4).attr("stroke-dasharray", "1 2").attr("stroke-linecap", "round"); + trails.attr("opacity", .8).attr("stroke", "#d06324").attr("stroke-width", .1).attr("stroke-dasharray", ".5 1").attr("stroke-linecap", "round"); + searoutes.attr("opacity", .8).attr("stroke", "#ffffff").attr("stroke-width", .2).attr("stroke-dasharray", "1 2").attr("stroke-linecap", "round"); + grid.attr("stroke", "#808080").attr("stroke-width", .1); + + // canvas + var canvas = document.getElementById("canvas"), + ctx = canvas.getContext("2d"); + + // Color schemes + var color = d3.scaleSequential(d3.interpolateSpectral), + colors8 = d3.scaleOrdinal(d3.schemeSet2), + colors20 = d3.scaleOrdinal(d3.schemeCategory20); + + // Version control + var version = "0.52b"; + document.title = document.title + " v. " + version; + + // Common variables + var customization, elSelected, cells = [], land = [], riversData = [], manors = [], + queue = [], chain = {}, island = 0, cultureTree, manorTree; + var graphSize = +sizeInput.value, + manorsCount = manorsInput.value, + capitalsCount = regionsInput.value, + power = powerInput.value, + neutral = neutralInput.value, + swampiness = swampinessInput.value, + sharpness = sharpnessInput.value; + if (neutral === "100") {neutral = "300";} + + // Groups for labels + var fonts = ["Amatic+SC:700"], + capitals = labels.append("g").attr("id", "capitals").attr("fill", "#3e3e4b").attr("opacity", 1).attr("font-family", "Amatic SC").attr("data-font", "Amatic+SC:700").attr("font-size", Math.round(6 - capitalsCount / 20)), + towns = labels.append("g").attr("id", "towns").attr("fill", "#3e3e4b").attr("opacity", 1).attr("font-family", "Amatic SC").attr("data-font", "Amatic+SC:700").attr("font-size", 2), + countries = labels.append("g").attr("id", "countries").attr("fill", "#3e3e4b").attr("opacity", 1).attr("font-family", "Amatic SC").attr("data-font", "Amatic+SC:700").attr("font-size", Math.round(18 - capitalsCount / 6)); + + // append ocean pattern + oceanPattern.append("rect").attr("x", 0).attr("y", 0) + .attr("width", mapWidth).attr("height", mapHeight).attr("class", "pattern") + .attr("stroke", "none").attr("fill", "url(#oceanPattern)"); + oceanLayers.append("rect").attr("x", 0).attr("y", 0) + .attr("width", mapWidth).attr("height", mapHeight).attr("id", "oceanBase").attr("fill", "#5167a9"); + + // D3 Line generator + var scX = d3.scaleLinear().domain([0, mapWidth]).range([0, mapWidth]), + scY = d3.scaleLinear().domain([0, mapHeight]).range([0, mapHeight]), + lineGen = d3.line().x(function(d) {return scX(d.scX);}).y(function(d) {return scY(d.scY);}); + + // main data variables + var voronoi = d3.voronoi().extent([[0, 0], [mapWidth, mapHeight]]); + var diagram, polygons, points = [], sample; + + // D3 drag and zoom behavior + var scale = 1, viewX = 0, viewY = 0; + var zoom = d3.zoom().scaleExtent([1, 40]) // 40x is default max zoom + .translateExtent([[0, 0], [mapWidth, mapHeight]]) // 0,0 as default extent + .on("zoom", zoomed); + svg.call(zoom); + + $("#optionsContainer").draggable({handle: ".drag-trigger", snap: "svg", snapMode: "both"}); + $("#mapLayers").sortable({items: "li:not(.solid)", cancel: ".solid", update: moveLayer}); + $("#templateBody").sortable({items: "div:not(div[data-type='Mountain'])"}); + $("#mapLayers, #templateBody").disableSelection(); + + 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; + 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); + } + + generate(); // genarate map on load + + function generate() { + console.group("Random map"); + console.time("TOTAL"); + placePoints(); + calculateVoronoi(points); + detectNeighbors(); + defineHeightmap(); + markFeatures(); + drawOcean(); + reGraph(); + resolveDepressions(); + flux(); + drawRelief(); + drawCoastline(); + manorsAndRegions(); + console.timeEnd("TOTAL"); + console.groupEnd("Random map"); + } + + // Locate points to calculate Voronoi diagram + function placePoints() { + console.time("placePoints"); + points = []; + var radius = 5.9 / graphSize; // 5.9 is a radius to get 8k cells + var sampler = poissonDiscSampler(mapWidth, mapHeight, radius); + while (sample = sampler()) {points.push([Math.ceil(sample[0]), Math.ceil(sample[1])]);} + console.timeEnd("placePoints"); + } + + // Calculate Voronoi Diagram + function calculateVoronoi(points) { + console.time("calculateVoronoi"); + diagram = voronoi(points), + polygons = diagram.polygons(); + console.log(" cells: " + points.length); + console.timeEnd("calculateVoronoi"); + } + + // Get cell info on mouse move (useful for debugging) + function moved() { + var point = d3.mouse(this); + var i = diagram.find(point[0], point[1]).index; + if (i) { + var p = cells[i]; // get cell + $("#lx").text(Math.ceil(point[0])); + $("#ly").text(Math.ceil(point[1])); + $("#cell").text(i); + $("#height").text(ifDefined(p.height, 2)); + $("#flux").text(ifDefined(p.flux, 3)); + $("#river").text(ifDefined(p.river)); + $("#region").text(ifDefined(p.region)); + $("#feature").text(ifDefined(p.feature) + "" + ifDefined(p.featureNumber)); + $("#score").text(ifDefined(p.score)); + $("#path").text(ifDefined(p.path)); + $("#culture").text(ifDefined(cultures[p.culture])); + d3.select("body").on("keydown", function() { + if (d3.event.keyCode == 32) {console.table(p);} + if (d3.event.keyCode == 77) {console.table(manors);} + if (d3.event.keyCode == 67) {console.log(cells);} + }); + } + // draw line for Customization range placing + icons.selectAll(".line").remove(); + if (customization === 1 && icons.selectAll(".tag").size() === 1) { + var x = +icons.select(".tag").attr("cx"); + var y = +icons.select(".tag").attr("cy"); + icons.insert("line", ":first-child").attr("class", "line").attr("x1", x).attr("y1", y).attr("x2", point[0]).attr("y2", point[1]); + } + } + + // return value (e) if defined with specified number of decimals (f) + function ifDefined(e, f) { + if (e == undefined) {return "no";} + if (f) {return e.toFixed(f);} + return e; + } + + // Drag actions + function dragstarted() { + var x0 = d3.event.x, + y0 = d3.event.y, + c0 = diagram.find(x0, y0).index, + c1 = c0; + d3.event.on("drag", function() { + if (customization === 1) { + var x1 = d3.event.x, + y1 = d3.event.y, + c2 = diagram.find(x1, y1).index; + if (c2 !== c1) { + c1 = c2; + var brush = $("#options .pressed").attr("id"); + var power = +brushPower.value; + if (brush === "brushElevate") { + if (cells[c2].height < 0.2) {cells[c2].height = 0.2} else {cells[c2].height += power;} + } + if (brush === "brushDepress") {cells[c2].height -= power;} + if (brush === "brushHill") {add(c2, "hill", power);} + if (brush === "brushPit") {addPit(1, power, c2);} + if (brush === "brushAlign") {cells[c2].height = cells[c0].height;} + if (brush === "brushSmooth") { + var heights = [cells[c2].height]; + cells[c2].neighbors.forEach(function(e) {heights.push(cells[e].height);}); + cells[c2].height = d3.mean(heights); + } + } + mockHeightmap(); + } else { + viewbox.on(".drag", null); + } + }); + } + + + // turn D3 polygons array into cell array, define neighbors for each cell + function detectNeighbors(withGrid) { + console.time("detectNeighbors"); + var gridPath = ""; // store grid as huge single path string + polygons.map(function(i, d) { + var neighbors = []; + var type; // define type, -99 for map borders + if (withGrid) {gridPath += "M" + i.join("L") + "Z";} // grid path + 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;} + type = -99; // polygon is on border if it has edge without opposite side polygon + } + }) + cells.push({index: d, data: i.data, height: 0, type, neighbors}); + }); + if (withGrid) {grid.append("path").attr("d", round(gridPath));} + console.timeEnd("detectNeighbors"); + } + + // Generate Heigtmap routine + function defineHeightmap() { + console.time('defineHeightmap'); + var mapTemplate = templateInput.value; + if (mapTemplate === "Random") { + var rnd = Math.random(); + if (rnd > 0.98) {mapTemplate = "Volcano";} + if (rnd > 0.8 && rnd <= 0.98) {mapTemplate = "High Island";} + if (rnd > 0.62 && rnd <= 0.8) {mapTemplate = "Low Island";} + if (rnd > 0.35 && rnd <= 0.62) {mapTemplate = "Continents";} + if (rnd > 0.01 && rnd <= 0.35) {mapTemplate = "Archipelago";} + if (rnd <= 0.01) {mapTemplate = "Atoll";} + } + addMountain(); + if (mapTemplate === "Volcano") {templateVolcano();} + if (mapTemplate === "High Island") {templateHighIsland();} + if (mapTemplate === "Low Island") {templateLowIsland();} + if (mapTemplate === "Continents") {templateContinents();} + if (mapTemplate === "Archipelago") {templateArchipelago();} + if (mapTemplate === "Atoll") {templateAtoll();} + console.log(mapTemplate + " template is applied"); + console.timeEnd('defineHeightmap'); + } + + // Heighmap Template: Volcano + function templateVolcano() { + modifyHeights("all", 0.07, 1.1); + addHill(5, 0.4); + addHill(2, 0.15); + } + +// Heighmap Template: High Island + function templateHighIsland() { + modifyHeights("all", 0.08, 0.9); + addRange(4); + addHill(12, 0.25); + addRange(-3); + modifyHeights("land", 0, 0.75); + addHill(3, 0.15); + } + +// Heighmap Template: Low Island + function templateLowIsland() { + modifyHeights("all", 0.05, 1); + smoothHeights(); + addHill(4, 0.4); + addHill(12, 0.2); + addRange(-3); + modifyHeights("land", 0, 0.3); + } + + // Heighmap Template: Continents + function templateContinents() { + addHill(24, 0.25); + addRange(2); + addHill(3, 0.1); + modifyHeights("land", 0, 0.7); + var count = Math.floor(Math.random() * 7 + 2); + addStrait(count); + smoothHeights(); + addPit(5); + addRange(-3); + modifyHeights("land", 0, 0.8); + modifyHeights("all", 0.02, 1); + } + + // Heighmap Template: Archipelago + function templateArchipelago() { + modifyHeights("land", -0.2, 1); + addHill(15, 0.15); + addRange(-2); + addPit(8); + modifyHeights("land", -0.05, 0.9); + } + + // Heighmap Template: Atoll + function templateAtoll() { + addHill(2, 0.35); + addRange(1); + modifyHeights("all", 0.07, 1); + smoothHeights(); + modifyHeights("0.27-10", 0, 0.1); + } + + function addMountain() { + var x = Math.floor(Math.random() * mapWidth / 3 + mapWidth / 3); + var y = Math.floor(Math.random() * mapHeight * 0.2 + mapHeight * 0.4); + 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 (cells[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 = []; // cells to check + var used = []; // used cells + cells[start].height += height; + cells[start].feature = undefined; + queue.push(start); + used.push(start); + for (i = 0; i < queue.length && height >= 0.01; i++) { + if (type == "mountain") { + height = +cells[queue[i]].height * radius - height / 100; + } else { + height *= radius; + } + cells[queue[i]].neighbors.forEach(function(e) { + if (used.indexOf(e) < 0) { + var mod = Math.random() * sharpness + 1.1 - sharpness; + if (sharpness == 0) {mod = 1;} + cells[e].height += height * mod; + if (cells[e].height > 1) {cells[e].height = 1;} + cells[e].feature = undefined; + queue.push(e); + used.push(e); + } + }); + } + } + + function addRange(mod, height, from, to) { + var count = Math.abs(mod); + for (c = 0; c < count; c++) { + var diff = 0; + var start = from; + var end = to; + if (!start || !end) { + 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; + start = 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; + end = diagram.find(xt, yt).index; + diff = Math.hypot(xt - xf, yt - yf); + } while (diff < 180 || diff > 400) + } + var range = []; + if (start && end) { + for (var l = 0; start != end && l < 1000; l++) { + var min = 10000; + cells[start].neighbors.forEach(function(e) { + diff = Math.hypot(cells[end].data[0] - cells[e].data[0], cells[end].data[1] - cells[e].data[1]); + if (Math.random() > 0.5) {diff = diff / 2} + if (diff < min) { + min = diff; + start = e; + } + }); + range.push(start); + } + } + if (range.length > 0) { + var change = height ? height + 0.2 : Math.random() * 0.2 + 0.2; + var query = []; + var used = []; + for (var i = 1; change >= 0.01; i++) { + var rnd = Math.random() * 0.4 + 0.8; + change -= i / 40 * rnd; + range.map(function(r) { + cells[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) { + cells[e].height += change; + if (cells[e].height > 1) {cells[e].height = 1;} + } else if (cells[e].height >= 0.2) { + cells[e].height -= change; + if (cells[e].height < 0.1) { + cells[e].height = 0.13 + i / 100; + if (cells[e].height >= 0.2) {cells[e].height = 0.19;} + } + } + } + }); + range = query.slice(); + }); + } + } + } + } + + function addStrait(width) { + var top = Math.floor(Math.random() * mapWidth * 0.3 + mapWidth * 0.35); + var bottom = Math.floor((mapWidth - top) - (mapWidth * 0.1) + (Math.random() * mapWidth * 0.2)); + var start = diagram.find(top, mapHeight * 0.1).index; + var end = diagram.find(bottom, mapHeight * 0.9).index; + var range = []; + for (var l = 0; start !== end && l < 1000; l++) { + var min = 10000; + cells[start].neighbors.forEach(function(e) { + diff = Math.hypot(cells[end].data[0] - cells[e].data[0], cells[end].data[1] - cells[e].data[1]); + if (Math.random() > 0.5) {diff = diff / 2} + if (diff < min) {min = diff; start = e;} + }); + range.push(start); + } + var query = [], used = []; + for (var i = 1; width > 0; i++) { + width --; + range.map(function(r) { + cells[r].neighbors.forEach(function(e) { + if (used.indexOf(e) == -1) { + query.push(e), used.push(e); + var height = (Math.floor(Math.random() * 101) + 100) / 1000; + cells[e].height = Math.trunc(height * 100) / 100; + } + }); + range = query.slice(); + }); + } + } + + function addPit(count, height, cell) { + for (c = 0; c < count; c++) { + var change = height ? height + 0.2 : Math.random() * 0.3 + 0.2; + var start = cell, used = []; + if (!start) { + var lowlands = $.grep(cells, function(e) {return (e.height >= 0.2);}); + if (lowlands.length == 0) {return;} + var rnd = Math.floor(Math.random() * lowlands.length); + start = lowlands[rnd].index; + } + var query = [start]; + for (var i = 1; change >= 0.01; i++) { + var rnd = Math.random() * 0.4 + 0.8; + change -= i / 60 * rnd; + query.map(function(p) { + cells[p].neighbors.forEach(function(e) { + if (used.indexOf(e) == -1 && change > 0) { + query.push(e); + used.push(e); + cells[e].height -= change; + if (cells[e].height < 0.1) { + cells[e].height = 0.1 + i / 100; + if (cells[e].height >= 0.2) {cells[e].height = 0.19;} + } + } + }); + }); + } + } + } + + // Modify heights multiplying/adding by value + function modifyHeights(type, add, mult) { + cells.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; + } + } else if (type === "all") { + if (i.height > 0) { + i.height += add; + i.height *= mult; + } + } else { + var interval = type.split("-"); + if (i.height >= +interval[0] && i.height <= +interval[1]) { + i.height += add; + i.height *= mult; + } + } + }); + } + + // Smooth heights using mean of neighbors + function smoothHeights() { + cells.map(function(i) { + var heights = [i.height]; + i.neighbors.forEach(function(e) {heights.push(cells[e].height);}); + i.height = d3.mean(heights); + }); + } + + // Get polygone neighbors and update their height with small optional modifier + function neighbors(i, height) { + cells[i].neighbors.forEach(function(e) { + if (!cells[e].used) { + var mod = Math.random() * sharpness + 1.1 - sharpness; + if (sharpness == 0.1) {mod = 1;} + cells[e].height += height * mod; + cells[e].used = 1; + queue.push(e); + } + }); + } + + // Mark features (ocean, lakes, islands) + function markFeatures() { + console.time("markFeatures"); + var queue = [], lake = 0, number = 0, type, greater = 0, less = 0; + // ensure all near border cells are ocean + cells.map(function(l) { + l.height = Math.trunc(l.height * 100) / 100; + if (l.type === -99) { + l.height = 0; + l.neighbors.forEach(function(e) {cells[e].height = 0;}); + } + }); + // start with top left corner to define Ocean first + var start = diagram.find(0, 0).index; + var unmarked = [cells[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); + cells[start].feature = type; + cells[start].featureNumber = number; + while (queue.length > 0) { + var i = queue[0]; + queue.shift(); + cells[i].neighbors.forEach(function(e) { + if (!cells[e].feature && cells[e].height >= greater && cells[e].height < less) { + cells[e].feature = type; + cells[e].featureNumber = number; + queue.push(e); + } + if (type == "Island" && cells[e].height < 0.2) { + cells[i].type = 2; + cells[e].type = -1; + if (cells[e].feature === "Ocean") { + if (cells[i].harbor) { + cells[i].harbor += 1; + } else { + cells[i].harbor = 1; + } + } + } + }); + } + unmarked = $.grep(cells, function(e) {return (!e.feature);}); + } + console.timeEnd("markFeatures"); + } + + function drawOcean() { + console.time("drawOcean"); + var limits = [], odd = 0.8; // initial odd for ocean layer is 80% + // Define type of ocean cells based on cell distance form land + var frontier = $.grep(cells, function(e) {return (e.type === -1);}); + if (Math.random() < odd) {limits.push(-1); odd = 0.3;} + for (var c = -2; frontier.length > 0 && c > -10; c--) { + if (Math.random() < odd) {limits.unshift(c); odd = 0.3;} else {odd += 0.2;} + frontier.map(function(i) { + i.neighbors.forEach(function(e) { + if (!cells[e].type) {cells[e].type = c;} + }); + }); + frontier = $.grep(cells, function(e) {return (e.type === c);}); + } + if (outlineLayers.value !== "random") {limits = outlineLayers.value.split(",");} + // Define area edges + for (var c = 0; c < limits.length; c++) { + var edges = []; + for (var i = 0; i < cells.length; i++) { + if (cells[i].feature === "Ocean" && cells[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 = cells[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); + oceanLayers.append("path").attr("d", line).attr("fill", "#ecf2f9").style("opacity", 0.4 / limits.length); + } + console.timeEnd("drawOcean"); + } + + // recalculate Voronoi Graph to pack cells + function reGraph() { + console.time("reGraph"); + var tempCells = [], newPoints = []; // to store new data + land = [], polygons= []; // clear old data + cells.map(function(i) { + var height = Math.trunc(i.height * 100) / 100; + var type = i.type || undefined; + if (type !== -1 && type !== -2 && height < 0.2) {return;} + var x = Math.round(i.data[0] * 10) / 10; + var y = Math.round(i.data[1] * 10) / 10; + var feature = i.feature; + var featureNumber = i.featureNumber; + var harbor = type === 2 ? +i.harbor : undefined; + var flux = y >= mapHeight / 2 ? 0.07 : 0.1; + var copy = $.grep(newPoints, function(e) {return (e[0] == x && e[1] == y);}); + if (!copy.length) { + newPoints.push([x, y]); + tempCells.push({index:tempCells.length, data:[x, y], height, type, feature, featureNumber, harbor, flux}); + } + if (type === 2 || type === -1) { // add additional points + i.neighbors.forEach(function(e) { + if (cells[e].type == type) { + var x1 = Math.ceil((x * 2 + cells[e].data[0]) / 3); + var y1 = Math.ceil((y * 2 + cells[e].data[1]) / 3); + copy = $.grep(newPoints, function(e) {return (e[0] == x1 && e[1] == y1);}); + if (!copy.length) { + newPoints.push([x1, y1]); + tempCells.push({index:tempCells.length, data:[x1, y1], height, type, feature, featureNumber, harbor, flux}); + } + }; + }); + } + }); + cells = tempCells; // use tempCells as the only cells array + calculateVoronoi(newPoints); // recalculate Voronoi diagram using new points + var gridPath = ""; // store grid as huge single path string + cells.map(function(i, d) { + gridPath += "M" + polygons[d].join("L") + "Z"; + 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; + }); + grid.append("path").attr("d", round(gridPath)); + land = $.grep(cells, function(e) {return (e.height >= 0.2);}); + land.sort(function(a, b) {return b.height - a.height;}); + console.timeEnd("reGraph"); + } + + // Draw temp Heightmap for the Journey + function mockHeightmap() { + $("#landmass").empty(); + var elevation = []; + cells.map(function(i) { + if (i.height > 1) {i.height = 1;} + if (i.height < 0) {i.height = 0;} + if (i.height >= 0.2) { + elevation.push(i.height); + landmass.append("path") + .attr("d", "M" + polygons[i.index].join("L") + "Z") + .attr("fill", color(1 - i.height)) + .attr("stroke", color(1 - i.height)); + } + }); + var elevationAverage = Math.round(d3.mean(elevation) * 100) / 100; + var landRatio = Math.round(elevation.length / cells.length * 100) + landmassCounter.innerHTML = elevation.length + " (" + landRatio + "%); Average Elevation: " + elevationAverage; + if (elevation.length > 100) { + $("#getMap").attr("disabled", false).removeClass("buttonoff"); + } else { + $("#getMap").attr("disabled", true).addClass("buttonoff"); + } + if (elevation.length > 0) { + $("#featureIsland").attr("disabled", true).addClass("buttonoff"); + } else { + $("#featureIsland").attr("disabled", false).removeClass("buttonoff"); + } + } + + // Detect and draw the coasline + function drawCoastline() { + console.time('drawCoastline'); + $("#landmass").empty(); + var oceanEdges = [], lakeEdges = []; + var edges = diagram.edges; + for (var i = 0; i < edges.length; i++) { + var e = edges[i]; + if (!e) {continue;} + if (!e.left || !e.right) {continue;} + var l = cells[e.left.index], r = cells[e.right.index]; + if (l.height < 0.2 && r.height < 0.2) {continue;} + if (l.height >= 0.2 && r.height >= 0.2) {continue;} + var start = e[0].join(" "); + var end = e[1].join(" "); + if (l.height > r.height) {var land = l, water = r;} else {var land = r, water = l;} + var x = (e[0][0] + e[1][0]) / 2; + var y = (e[0][1] + e[1][1]) / 2; + if (water.feature === "Lake") { + lakeEdges.push({start, end}); + land.data[0] = x + (land.data[0] - x) * 0.4; + land.data[1] = y + (land.data[1] - y) * 0.4; + } else { + oceanEdges.push({start, end}); + if (land.type !== 1) { // locate place at shore + var coastX = x + (land.data[0] - x) * 0.12; + var coastY = y + (land.data[1] - y) * 0.12; + var pointX = x + (land.data[0] - x) * 0.4; + var pointY = y + (land.data[1] - y) * 0.4; + land.coastX = Math.round(coastX * 100) / 100; + land.coastY = Math.round(coastY * 100) / 100; + land.data[0] = Math.round(pointX * 100) / 100; + land.data[1] = Math.round(pointY * 100) / 100; + land.type = 1; + land.haven = water.index; // mark haven + } + } + } + getCurveType(); + var line = getContinuousLine(oceanEdges, 1.5, 0); + d3.select("#shape").append("path").attr("d", line).attr("fill", "white"); // draw the clippath + landmass.append("path").attr("d", line); // draw the landmass + coastline.append("path").attr("d", line); // draw the coastline + line = getContinuousLine(lakeEdges, 1.5, 0); + lakes.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 < 50000; i++) { + var next = $.grep(edges, function(e) {return (e.start == end || e.end == end);}); + if (!next.length) { + end = edges[0].end; + edges.shift(); + continue; + } + if (next[0].start == end) {end = next[0].end;} else {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 round(line); + } + + // Resolve Heightmap Depressions (for a correct water flux modeling) + function resolveDepressions() { + console.time('resolveDepressions'); + var depression = 1, limit = 100, minCell, minHigh; + for (var l = 0; depression > 0 && l < limit; l++) { + depression = 0; + for (var i = 0; i < land.length; i++) { + var heights = []; + land[i].neighbors.forEach(function(e) {heights.push(+cells[e].height);}); + var minHigh = d3.min(heights); + if (land[i].height <= minHigh) { + depression += 1; + land[i].height = minHigh + 0.01; + } + } + if (l === limit - 1) {console.error("Error: resolveDepressions iteration limit");} + } + console.timeEnd('resolveDepressions'); + } + + function flux() { + console.time('flux'); + riversData = []; + var riversOrder = [], riverNext = 0; + land.sort(function(a, b) {return b.height - a.height;}); + for (var i = 0; i < land.length; i++) { + var id = land[i].index; + var heights = []; + land[i].neighbors.forEach(function(e) {heights.push(cells[e].height);}); + var minId = heights.indexOf(d3.min(heights)); + var min = land[i].neighbors[minId]; + // Define river number + if (land[i].flux > 0.85) { + if (land[i].river == undefined) { + // State new River + land[i].river = riverNext; + riversData.push({river: riverNext, cell: id, x: land[i].data[0], y: land[i].data[1]}); + riverNext += 1; + } + // Assing existing River to the downhill cell + if (cells[min].river == undefined) { + cells[min].river = land[i].river; + } else { + var riverTo = cells[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) { + cells[min].river = land[i].river; + iRiverL += 1; + minRiverL -= 1; + } + // mark confluences + if (cells[min].height >= 0.2 && iRiverL > 1 && minRiverL > 1) { + if (!cells[min].confluence) { + cells[min].confluence = minRiverL-1; + } else { + cells[min].confluence += minRiverL-1; + } + } + } + } + cells[min].flux = +(cells[min].flux+land[i].flux).toFixed(2); + if (land[i].river != undefined) { + var px = cells[min].data[0]; + var py = cells[min].data[1]; + if (cells[min].height < 0.2) { + // pour water to the Ocean + var sx = land[i].data[0]; + var sy = land[i].data[1]; + var x = (px + sx) / 2 + (px - sx) / 20; + var y = (py + sy) / 2 + (py - sy) / 20; + riversData.push({river: land[i].river, cell: id, x, y}); + } + else { + // add next River segment + riversData.push({river: land[i].river, cell: min, x: px, y: py}); + } + } + } + console.timeEnd('flux'); + drawRiverLines(riverNext); + } + + function drawRiverLines(riverNext) { + console.time('drawRiverLines'); + lineGen.curve(d3.curveCatmullRom.alpha(0.1)); + for (var i = 0; i < riverNext; i++) { + var dataRiver = $.grep(riversData, function(e) {return e.river === i;}); + if (dataRiver.length > 1) { + var riverAmended = amendRiver(dataRiver, 1); + var d = drawRiver(riverAmended); + rivers.append("path").attr("d", d).attr("data-points", JSON.stringify(riverAmended)); + } + } + rivers.selectAll("path").on("click", editRiver); + console.timeEnd('drawRiverLines'); + } + + // add more river points on 1/3 and 2/3 of length + function amendRiver(dataRiver, rndFactor) { + var riverAmended = [], side = 1; + 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); + var serpentine = 1 / (r+1); + var meandr = serpentine + 0.3 + Math.random() * 0.3 * rndFactor; + if (Math.random() > 0.5) {side *= -1}; + var dist = Math.hypot(eX - dX, eY - dY); + // if dist is big or river is small add 2 extra points + if (dist > 8 || (dist > 4 && dataRiver.length < 6)) { + var stX = (dX * 2 + eX) / 3; + var stY = (dY * 2 + eY) / 3; + var enX = (dX + eX * 2) / 3; + var enY = (dY + eY * 2) / 3; + stX += -Math.sin(angle) * meandr * side; + stY += Math.cos(angle) * meandr * side; + if (Math.random() > 0.8) {side *= -1}; + enX += Math.sin(angle) * meandr * side; + enY += -Math.cos(angle) * meandr * side; + riverAmended.push({scX:stX, scY:stY}, {scX:enX, scY:enY}); + // if dist is medium or river is small add 1 extra point + } else if (dist > 4 || dataRiver.length < 6) { + var scX = (dX + eX) / 2; + var scY = (dY + eY) / 2; + scX += -Math.sin(angle) * meandr * side; + scY += Math.cos(angle) * meandr * side; + riverAmended.push({scX, scY}); + } + } + } + return riverAmended; + } + + function drawRiver(riverPoints, startWidth, widening) { + var extraWidth = startWidth || 0.02; + var widening = widening || 250; + var d = lineGen(riverPoints); + var river = defs.append("path").attr("d", d); + var riverLength = river.node().getTotalLength(); + var riverPointsLeft = [], riverPointsRight = []; + for (var l=0; l < riverLength; l++) { + var point = river.node().getPointAtLength(l); + var cell = diagram.find(point.x, point.y, 1); + if (cell) { + var confluence = cells[cell.index].confluence; + if (confluence) {extraWidth += Math.atan(confluence / 100);} + } + var from = river.node().getPointAtLength(l - 0.1); + var to = river.node().getPointAtLength(l + 0.1); + var angle = Math.atan2(from.y - to.y, from.x - to.x); + var offset = Math.atan(l / widening) + extraWidth; + var xLeft = point.x + -Math.sin(angle) * offset; + var yLeft = point.y + Math.cos(angle) * offset; + riverPointsLeft.push({scX:xLeft, scY:yLeft}); + var xRight = point.x + Math.sin(angle) * offset; + var yRight = point.y + -Math.cos(angle) * offset; + riverPointsRight.unshift({scX:xRight, scY:yRight}); + } + var point = river.node().getPointAtLength(riverLength); + var from = river.node().getPointAtLength(riverLength - 0.1); + var angle = Math.atan2(from.y - point.y, from.x - point.x); + var offset = Math.atan(riverLength / widening) + extraWidth; + var xLeft = point.x + -Math.sin(angle) * offset; + var yLeft = point.y + Math.cos(angle) * offset; + riverPointsLeft.push({scX:xLeft, scY:yLeft}); + var xRight = point.x + Math.sin(angle) * offset; + var yRight = point.y + -Math.cos(angle) * offset; + riverPointsRight.unshift({scX:xRight, scY:yRight}); + river.remove(); + var right = lineGen(riverPointsRight); + var left = lineGen(riverPointsLeft); + left = left.substring(left.indexOf("C")); + var d = right + left + "Z"; + d = d.replace(/[\d\.-][\d\.e-]*/g, function(n) {return Math.round(n*100)/100;}); + return d; + } + + function editRiver() { + if (elSelected) { + if ($("#riverNew").hasClass('pressed')) { + var point = d3.mouse(this); + addRiverPoint({scX:point[0], scY:point[1]}); + redrawRiver(); + $("#riverNew").click(); + return; + } + elSelected.call(d3.drag().on("drag", null)).classed("draggable", false); + rivers.select(".riverPoints").remove(); + } + elSelected = d3.select(this); + elSelected.call(d3.drag().on("drag", riverDrag)).classed("draggable", true); + var points = JSON.parse(elSelected.attr("data-points")); + rivers.append("g").attr("class", "riverPoints").attr("transform", elSelected.attr("transform")); + points.map(function(p) {addRiverPoint(p)}); + var tr = parseTransform(elSelected.attr("transform")); + riverAngle.value = tr[2]; + riverAngleValue.innerHTML = Math.abs(+tr[2]) + "°"; + riverScale.value = tr[5]; + $("#riverEditor").dialog({ + title: "Edit River", + minHeight: 30, width: "auto", maxWidth: 275, resizable: false, + position: {my: "center top", at: "top", of: this} + }).on("dialogclose", function(event) { + if (elSelected) { + elSelected.call(d3.drag().on("drag", null)).classed("draggable", false); + rivers.select(".riverPoints").remove(); + $(".pressed").removeClass('pressed'); + viewbox.style("cursor", "default"); + } + }); + } + + function addRiverPoint(point) { + rivers.select(".riverPoints").append("circle") + .attr("cx", point.scX).attr("cy", point.scY).attr("r", 0.25) + .call(d3.drag().on("drag", riverPointDrag)) + .on("click", function(d) { + if ($("#riverRemovePoint").hasClass('pressed')) { + $(this).remove(); redrawRiver(); + } + if ($("#riverNew").hasClass('pressed')) { + $("#riverNew").click(); + } + }); + } + + $("#riverEditor .editButton, #riverEditor .editButtonS").click(function() { + if (this.id == "riverRemove") { + alertMessage.innerHTML = `Are you sure you want to remove the river?`; + $(function() {$("#alert").dialog({resizable: false, title: "Remove river", + buttons: { + "Remove": function() { + $(this).dialog("close"); + elSelected.remove(); + rivers.select(".riverPoints").remove(); + $("#riverEditor").dialog("close"); + }, + Cancel: function() {$(this).dialog("close");} + }}) + }); + return; + } + if (this.id == "riverCopy") { + var tr = parseTransform(elSelected.attr("transform")); + var d = elSelected.attr("d"); + var points = elSelected.attr("data-points"); + var x = 2, y = 2; + transform = `translate(${tr[0]-x},${tr[1]-y}) rotate(${tr[2]} ${tr[3]} ${tr[4]}) scale(${tr[5]})`; + while (rivers.selectAll("[transform='" + transform + "'][d='" + d + "']").size() > 0) { + x += 2; y += 2; + transform = `translate(${tr[0]-x},${tr[1]-y}) rotate(${tr[2]} ${tr[3]} ${tr[4]}) scale(${tr[5]})`; + } + rivers.append("path").attr("d", d).attr("data-points", points).attr("transform", transform).on("click", editRiver); + return; + } + if (this.id == "riverRenegerate") { + // restore main points + var points = JSON.parse(elSelected.attr("data-points")); + var riverCells = [], dataRiver = []; + for (var p = 0; p < points.length; p++) { + var cell = diagram.find(points[p].scX, points[p].scY, 1); + if (cell !== null && cell !== riverCells[riverCells.length-1]) {riverCells.push(cell);} + } + for (var c = 0; c < riverCells.length; c++) { + dataRiver.push({x:riverCells[c][0], y:riverCells[c][1]}); + } + // if last point not in cell center push it with one extra point + var last = points.pop(); + if (dataRiver[dataRiver.length-1].x !== last.scX) { + dataRiver.push({x:last.scX, y:last.scY}); + } + var rndFactor = 0.2 + Math.random() * 1.6; // random factor in range 0.2-1.8 + var riverAmended = amendRiver(dataRiver, rndFactor); + lineGen.curve(d3.curveCatmullRom.alpha(0.1)); + var startWidth = 0.01 + Math.random() * 0.04; + var widening = 100 + Math.random() * 150; + var d = drawRiver(riverAmended, startWidth, widening); + elSelected.attr("d", d).attr("data-points", JSON.stringify(riverAmended)); + rivers.select(".riverPoints").selectAll("*").remove(); + riverAmended.map(function(p) {addRiverPoint(p);}); + return; + } + if (this.id == "riverRisize") {$("#riverAngle, #riverAngleValue, #riverScaleIcon, #riverScale, #riverReset").toggle();} + if (this.id == "riverAddPoint" || this.id == "riverRemovePoint" || this.id == "riverNew") { + if ($(this).hasClass('pressed')) { + $(".pressed").removeClass('pressed'); + if (elSelected.attr("data-river") == "new") { + rivers.select(".riverPoints").selectAll("*").remove(); + elSelected.attr("data-river", ""); + elSelected.call(d3.drag().on("drag", riverDrag)).classed("draggable", true); + } + viewbox.style("cursor", "default"); + } else { + $(".pressed").removeClass('pressed'); + $(this).addClass('pressed'); + if (this.id == "riverAddPoint" || this.id == "riverNew") {viewbox.style("cursor", "crosshair");} + if (this.id == "riverNew") {rivers.select(".riverPoints").selectAll("*").remove();} + } + return; + } + if (this.id == "riverReset") { + elSelected.attr("transform", ""); + rivers.select(".riverPoints").attr("transform", ""); + riverAngle.value = 0; + riverAngleValue.innerHTML = "0°"; + riverScale.value = 1; + return; + } + $("#riverEditor .editButton").toggle(); + $(this).show().next().toggle(); + }); + + // on riverAngle change + $("#riverAngle").change(function() { + var tr = parseTransform(elSelected.attr("transform")); + riverAngleValue.innerHTML = Math.abs(+this.value) + "°"; + $(this).attr("title", $(this).val()); + var c = elSelected.node().getBBox(); + var angle = this.value; + var scale = +tr[5]; + transform = `translate(${tr[0]},${tr[1]}) rotate(${angle} ${(c.x+c.width/2)*scale} ${(c.y+c.height/2)*scale}) scale(${scale})`; + elSelected.attr("transform", transform); + rivers.select(".riverPoints").attr("transform", transform); + }); + + // on riverScale change + $("#riverScale").change(function() { + var tr = parseTransform(elSelected.attr("transform")); + $(this).attr("title", $(this).val()); + var scaleOld = +tr[5]; + var scale = +this.value; + var c = elSelected.node().getBBox(); + var cx = c.x+c.width/2; + var cy = c.y+c.height/2; + var trX = +tr[0] + cx * (scaleOld - scale); + var trY = +tr[1] + cy * (scaleOld - scale); + var scX = +tr[3] * scale/scaleOld; + var scY = +tr[4] * scale/scaleOld; + transform = `translate(${trX},${trY}) rotate(${tr[2]} ${scX} ${scY}) scale(${scale})`; + elSelected.attr("transform", transform); + rivers.select(".riverPoints").attr("transform", transform); + }); + + function riverDrag() { + var x = d3.event.x, y = d3.event.y; + var el = d3.select(this); + var tr = parseTransform(el.attr("transform")); + d3.event.on("drag", function() { + xc = d3.event.x, yc = d3.event.y; + var transform = `translate(${(+tr[0]+xc-x)},${(+tr[1]+yc-y)}) rotate(${tr[2]} ${tr[3]} ${tr[4]}) scale(${tr[5]})`; + el.attr("transform", transform); + rivers.select(".riverPoints").attr("transform", transform); + }); + } + + function parseTransform(string) { + // [translateX,translateY,rotateDeg,rotateX,rotateY,scale] + if (!string) {return [0,0,0,0,0,1];} + var a = string.replace(/[a-z()]/g,"").replace(/[ ]/g,",").split(","); + return [a[0] || 0, a[1] || 0, a[2] || 0, a[3] || 0, a[4] || 0, a[5] || 1]; + } + + function riverPointDrag() { + var x = d3.event.x, y = d3.event.y; + var el = d3.select(this); + d3.event + .on("drag", function() {el.attr("cx", d3.event.x).attr("cy", d3.event.y);}) + .on("end", function() { + if (Math.abs(d3.event.x - x) + Math.abs(d3.event.y - y) > 0) {redrawRiver();} + }); + } + + function redrawRiver() { + var points = []; + rivers.select(".riverPoints").selectAll("circle").each(function() { + var el = d3.select(this); + points.push({scX: +el.attr("cx"), scY: +el.attr("cy")}); + }); + lineGen.curve(d3.curveCatmullRom.alpha(0.1)); + var d = drawRiver(points); + elSelected.attr("d", d).attr("data-points", JSON.stringify(points)); + } + + function manorsAndRegions() { + console.group('manorsAndRegions'); + calculateChains(); + rankPlacesGeography(); + getCurveType(); + locateCultures(); + locateCapitals(); + generateMainRoads(); + rankPlacesEconomy(); + locateTowns(); + // temporary off as now there are too many islands and searoutes produce mess + //checkAccessibility(); + drawManors(); + defineRegions(); + drawRegions(); + generatePortRoads(); + generateSmallRoads(); + generateOceanRoutes(); + console.groupEnd('manorsAndRegions'); + } + + // Assess cells geographycal suitability for settlement + function rankPlacesGeography() { + console.time('rankPlacesGeography'); + land.map(function(c) { + var score = (1 - c.height) * 5; // base score from height (will be biom) + if (c.type && Math.random() < 0.8 && !c.river) { + c.score = 0; // ignore 80% of extended cells + } else { + if (c.type === 1 && c.harbor) { + score += 3 - c.harbor; // good sea harbor is valued + if (c.river && c.harbor === 1) {score += 3;} // estuaries are valued + } + if (c.flux > 1) {score += c.flux / 2;} // riverbank is valued + if (c.confluence) {score += c.confluence / 2;} // confluence is valued; + } + c.score = Math.floor(score); + }); + land.sort(compareScore); + console.timeEnd('rankPlacesGeography'); + } + + // Assess the cells economical suitability for settlement + function rankPlacesEconomy() { + console.time('rankPlacesEconomy'); + land.map(function(c) { + var score = c.score; + if (c.path) { + var path = Math.ceil(c.path / 15); + if (path < 1) {path = 1;} + if (path > 5) {path = 5;} + if (c.crossroad) {path *= 2;} // crossroads are valued + score += path; // roads are valued + } + c.score = Math.floor(Math.random() * score + score); // 0.5 random factor + }); + land.sort(compareScore); + console.timeEnd('rankPlacesEconomy'); + } + + function compareScore(a, b) { + if (a.score < b.score) return 1; + if (a.score > b.score) return -1; + return 0; + } + + // Locate cultures + function locateCultures() { + var cultureCenters = d3.range(7).map(function(d) {return [Math.random() * mapWidth, Math.random() * mapHeight];}); + cultureTree = d3.quadtree().extent([[0, 0], [mapHeight, mapWidth]]).addAll(cultureCenters);; + } + + function locateCapitals() { + console.time('locateCapitals'); + var spacing = mapWidth / capitalsCount; + manorTree = d3.quadtree().extent([[0, 0], [mapHeight, mapWidth]]); + if (power > 0) {spacing / power;} + console.log(" capitals: " + capitalsCount); + for (var l = 0; l < land.length && manors.length < capitalsCount; l++) { + var m = manors.length; + var dist = 10000; + if (l > 0) { + var closest = manorTree.find(land[l].data[0], land[l].data[1]); + dist = Math.hypot(land[l].data[0] - closest[0], land[l].data[1] - closest[1]); + } + if (dist >= spacing) { + if (land[l].harbor > 0 && land[l].type === 1) { + land[l].port = true; + land[l].data[0] = land[l].coastX; + land[l].data[1] = land[l].coastY; + } + if (land[l].river) { + var shift = Math.floor(0.2 * land[l].flux); + if (shift < 0.2) {shift = 0.2;} + if (shift > 1) {shift = 1;} + land[l].data[0] += shift - Math.random(); + land[l].data[1] += shift - Math.random(); + } + land[l].data[0] = +(land[l].data[0]).toFixed(2); + land[l].data[1] = +(land[l].data[1]).toFixed(2); + var cell = land[l].index; + queue.push(cell); + queue.push(...land[l].neighbors); + var closest = cultureTree.find(land[l].data[0], land[l].data[1]); + var culture = cultureTree.data().indexOf(closest); + var name = generateName(culture); + var capitalPower = Math.round((Math.random() * power / 2 + 1) * 10) / 10; + manors.push({i: m, cell, x: land[l].data[0], y: land[l].data[1], region: m, power: capitalPower, score: land[l].score, culture, name}); + manorTree.add([land[l].data[0], land[l].data[1]]); + } + if (l === land.length - 1) { + console.error("Cannot place capitals with current spacing. Trying again with reduced spacing"); + l = -1; + manors = []; + queue = []; + manorTree = d3.quadtree().extent([[0, 0], [mapHeight, mapWidth]]); + spacing /= 1.2; + } + } + manors.map(function(e, i) { + var p = cells[e.cell]; + p.manor = i; + p.region = i; + p.culture = e.culture; + }); + console.timeEnd('locateCapitals'); + } + + function locateTowns() { + console.time('locateTowns'); + for (var l = 0; l < land.length && manors.length < manorsCount; l++) { + if (queue.indexOf(land[l].index) == -1) { + if (land[l].harbor === 1 && land[l].type === 1) { + land[l].port = true; + land[l].data[0] = land[l].coastX; + land[l].data[1] = land[l].coastY; + } + queue.push(land[l].index); + if (land[l].type || Math.random() > 0.6) {queue.push(...land[l].neighbors);} + if (land[l].river) { + var shift = Math.floor(0.2 * land[l].flux); + if (shift < 0.2) {shift = 0.2;} + if (shift > 1) {shift = 1;} + land[l].data[0] += shift - Math.random(); + land[l].data[1] += shift - Math.random(); + } + land[l].data[0] = +(land[l].data[0]).toFixed(2); + land[l].data[1] = +(land[l].data[1]).toFixed(2); + var x = land[l].data[0]; + var y = land[l].data[1]; + var cell = land[l].index; + var region = "neutral", culture = -1, closest = neutral; + for (c = 0; c < capitalsCount; c++) { + var dist = Math.hypot(manors[c].x - x, manors[c].y - y) / manors[c].power; + var cap = manors[c].cell; + if (cells[cell].featureNumber !== cells[cap].featureNumber) {dist *= 3;} + if (dist < closest) {region = c; closest = dist;} + } + if (closest > neutral / 5 || region === "neutral") { + var closestCulture = cultureTree.find(x, y); + culture = cultureTree.data().indexOf(closestCulture); + } else { + culture = manors[region].culture; + } + var name = generateName(culture); + land[l].manor = manors.length; + land[l].culture = culture; + land[l].region = region; + manors.push({i: manors.length, cell, x, y, region, score: land[l].score, culture, name}); + } + if (l === land.length - 1) { + console.error("Cannot place all towns. Towns requested: " + manorsCount + ". Towns placed: " + manors.length); + } + } + console.timeEnd('locateTowns'); + } + + // Validate each island with manors has at least one port (so Island is accessible) + function checkAccessibility() { + console.time("checkAccessibility"); + for (var i = 0; i < island; i++) { + var manorsOnIsland = $.grep(land, function(e) {return (typeof e.manor !== "undefined" && e.featureNumber === i);}); + if (manorsOnIsland.length > 0) { + var ports = $.grep(manorsOnIsland, function(p) {return (p.port);}); + if (ports.length === 0) { + var portCandidates = $.grep(manorsOnIsland, function(c) {return (c.harbor && c.type === 1);}); + if (portCandidates.length > 0) { + console.error("No ports on Island " + manorsOnIsland[0].featureNumber + ". Upgrading first manor to port"); + portCandidates[0].harbor = 1; + portCandidates[0].port = true; + portCandidates[0].data[0] = portCandidates[0].coastX; + portCandidates[0].data[1] = portCandidates[0].coastY; + manors[portCandidates[0].manor].x = portCandidates[0].coastX; + manors[portCandidates[0].manor].y = portCandidates[0].coastY; + } else { + console.error("Cannot generate ports on Island " + manorsOnIsland[0].featureNumber + ". Removing " + manorsOnIsland.length + " manors"); + manorsOnIsland.map(function(e) { + manors.splice(e.manor, 1); + e.manor = undefined; + }); + } + } + } + } + console.timeEnd("checkAccessibility"); + } + + function generateMainRoads() { + console.time("generateMainRoads"); + for (var i = 0; i < island; i++) { + var manorsOnIsland = $.grep(land, function(e) {return (typeof e.manor !== "undefined" && e.featureNumber === i);}); + if (manorsOnIsland.length > 1) { + for (var d = 1; d < manorsOnIsland.length; d++) { + for (var m = 0; m < d; m++) { + var path = findLandPath(manorsOnIsland[d].index, manorsOnIsland[m].index, "main"); + restorePath(manorsOnIsland[m].index, manorsOnIsland[d].index, "main", path); + } + } + } + } + console.timeEnd("generateMainRoads"); + } + + function generatePortRoads() { + console.time("generatePortRoads"); + var landCapitals = $.grep(land, function(e) {return (e.manor < capitalsCount && !e.port);}); + landCapitals.map(function(e) { + var ports = $.grep(land, function(l) {return (l.port && l.region === e.manor);}); + var minDist = 1000, end = -1; + ports.map(function(p) { + var dist = Math.hypot(e.data[0] - p.data[0], e.data[1] - p.data[1]); + if (dist < minDist) {minDist = dist; end = p.index;} + }); + if (end !== -1) { + var start = e.index; + var path = findLandPath(start, end, "direct"); + restorePath(end, start, "main", path); + } + }); + console.timeEnd("generatePortRoads"); + } + + function generateSmallRoads() { + console.time("generateSmallRoads"); + lineGen.curve(d3.curveBasis); + console.log(" islands: " + island); + for (var i = 0; i < island; i++) { + var manorsOnIsland = $.grep(land, function(e) {return (typeof e.manor !== "undefined" && e.featureNumber === i);}); + var l = manorsOnIsland.length; + if (l > 1) { + var secondary = Math.floor((l + 8) / 10); + for (s = 0; s < secondary; s++) { + var start = manorsOnIsland[Math.floor(Math.random() * l)].index; + var end = manorsOnIsland[Math.floor(Math.random() * l)].index; + var dist = Math.hypot(cells[start].data[0] - cells[end].data[0], cells[start].data[1] - cells[end].data[1]); + if (dist > 10) { + var path = findLandPath(start, end, "direct"); + restorePath(end, start, "small", path); + } + } + manorsOnIsland.map(function(e, d) { + if (!e.path && d > 0) { + var start = e.index, end = -1; + var road = $.grep(land, function(e) {return (e.path && e.featureNumber === i);}); + if (road.length > 0) { + var minDist = 10000; + road.map(function(i) { + var dist = Math.hypot(e.data[0] - i.data[0], e.data[1] - i.data[1]); + if (dist < minDist) {minDist = dist; end = i.index;} + }); + } else { + end = manorsOnIsland[0].index; + } + var path = findLandPath(start, end, "main"); + restorePath(end, start, "small", path); + } + }); + } + } + console.timeEnd("generateSmallRoads"); + } + + function generateOceanRoutes() { + console.time("generateOceanRoutes"); + lineGen.curve(d3.curveBasis); + var ports = []; + for (var i = 0; i < island; i++) { + ports[i] = $.grep(land, function(e) {return (e.featureNumber === i && e.port);}); + if (!ports[i]) {ports[i] = [];} + } + ports.sort(function(a, b) {return a.length < b.length;}) + for (var i = 0; i < island; i++) { + if (ports[i].length === 0) {break;} + var length = ports[i].length; + ports[i].sort(function(a, b) {return a.score < b.score;}) + var start = ports[i][0].index; + var paths = findOceanPaths(start, -1); + /* draw anchor icons + for (var p = 0; p < ports[i].length; p++) { + var x0 = ports[i][p].data[0]; + var y0 = ports[i][p].data[1]; + var x1 = cells[h.haven].data[0]; + var y1 = cells[h.haven].data[1]; + var x = x0 + (x1 - x0) * 0.8; + var y = y0 + (y1 - y0) * 0.8; + icons.append("use").attr("xlink:href", "#icon-anchor").attr("x", x).attr("y", y).attr("width", 1).attr("height", 1); + } */ + for (var h = 1; h < length; h++) { + var end = ports[i][h].index; + restorePath(end, start, "ocean", paths); + } + for (var c = i + 1; c < island; c++) { + if (ports[c].length > 3 && length > 3) { + var end = ports[c][0].index; + restorePath(end, start, "ocean", paths); + } + } + if (length > 5) { + ports[i].sort(function(a, b) {return b.cost - a.cost;}); + for (var a = 2; a < length && a < 10; a++) { + var dist = Math.hypot(ports[i][1].data[0] - ports[i][a].data[0], ports[i][1].data[1] - ports[i][a].data[1]); + var distPath = getPathDist(ports[i][1].index, ports[i][a].index); + if (distPath > dist * 4 + 10) { + var totalCost = ports[i][1].cost + ports[i][a].cost; + var paths = findOceanPaths(ports[i][1].index, ports[i][a].index); + if (ports[i][a].cost < totalCost) { + restorePath(ports[i][a].index, ports[i][1].index, "ocean", paths); + break; + } + } + } + } + } + console.timeEnd("generateOceanRoutes"); + } + + function findLandPath(start, end, type) { + // A* algorithm + var queue = new PriorityQueue({comparator: function(a, b) {return a.p - b.p}}); + var cameFrom = []; + var costTotal = []; + costTotal[start] = 0; + queue.queue({e: start, p: 0}); + while (queue.length > 0) { + var next = queue.dequeue().e; + if (next === end) {break;} + var pol = cells[next]; + pol.neighbors.forEach(function(e) { + if (cells[e].height >= 0.2) { + var cost = cells[e].height * 2; + if (cells[e].path && type === "main") { + cost = 0.15; + } else { + if (typeof e.manor === "undefined") {cost += 0.1;} + if (typeof e.river !== "undefined") {cost -= 0.1;} + if (cells[e].type === 1) {cost *= 0.3;} + if (cells[e].path) {cost *= 0.5;} + cost += Math.hypot(cells[e].data[0] - pol.data[0], cells[e].data[1] - pol.data[1]) / 30; + } + var costNew = costTotal[next] + cost; + if (!cameFrom[e] || costNew < costTotal[e]) { // + costTotal[e] = costNew; + cameFrom[e] = next; + var dist = Math.hypot(cells[e].data[0] - cells[end].data[0], cells[e].data[1] - cells[end].data[1]) / 15; + var priority = costNew + dist; + queue.queue({e, p: priority}); + } + } + }); + } + return cameFrom; + } + + function findLandPaths(start, type) { + // Dijkstra algorithm + var queue = new PriorityQueue({comparator: function(a, b) {return a.p - b.p}}); + var cameFrom = []; + var costTotal = []; + cameFrom[start] = "no"; + costTotal[start] = 0; + queue.queue({e: start, p: 0}); + while (queue.length > 0) { + var next = queue.dequeue().e; + var pol = cells[next]; + pol.neighbors.forEach(function(e) { + var cost = cells[e].height; + if (cost >= 0.2) { + cost *= 2; + if (typeof e.river !== "undefined") {cost -= 0.2;} + if (pol.region !== cells[e].region) {cost += 1;} + if (cells[e].region === "neutral") {cost += 1;} + if (typeof e.manor !== "undefined") {cost = 0.1;} + var costNew = costTotal[next] + cost; + if (!cameFrom[e]) { + costTotal[e] = costNew; + cameFrom[e] = next; + queue.queue({e, p: costNew}); + } + } + }); + } + return cameFrom; + } + + function findOceanPaths(start, end) { + var queue = new PriorityQueue({comparator: function(a, b) {return a.p - b.p}}); + var next; + var cameFrom = []; + var costTotal = []; + cameFrom[start] = "no"; + costTotal[start] = 0; + queue.queue({e: start, p: 0}); + while (queue.length > 0 && next !== end) { + next = queue.dequeue().e; + var pol = cells[next]; + pol.neighbors.forEach(function(e) { + if (cells[e].type < 0 || cells[e].haven === next) { + var cost = 1; + if (cells[e].type > 0) {cost += 100;} + if (cells[e].type < -1) { + var dist = Math.hypot(cells[e].data[0] - pol.data[0], cells[e].data[1] - pol.data[1]); + cost += 50 + dist * 2; + } + if (cells[e].path && cells[e].type < 0) {cost *= 0.8;} + var costNew = costTotal[next] + cost; + if (!cameFrom[e]) { + costTotal[e] = costNew; + cells[e].cost = costNew; + cameFrom[e] = next; + queue.queue({e, p: costNew}); + } + } + }); + } + return cameFrom; + } + + function getPathDist(start, end) { + var queue = new PriorityQueue({comparator: function(a, b) {return a.p - b.p}}); + var next, costNew; + var cameFrom = []; + var costTotal = []; + cameFrom[start] = "no"; + costTotal[start] = 0; + queue.queue({e: start, p: 0}); + while (queue.length > 0 && next !== end) { + next = queue.dequeue().e; + var pol = cells[next]; + pol.neighbors.forEach(function(e) { + if (cells[e].path && (cells[e].type === -1 || cells[e].haven === next)) { + var dist = Math.hypot(cells[e].data[0] - pol.data[0], cells[e].data[1] - pol.data[1]); + costNew = costTotal[next] + dist; + if (!cameFrom[e]) { + costTotal[e] = costNew; + cameFrom[e] = next; + queue.queue({e, p: costNew}); + } + } + }); + } + return costNew; + } + + function restorePath(end, start, type, from) { + var path = [], current = end, limit = 300; + var prev = cells[end]; + if (type === "ocean" || !prev.path) {path.push({scX: prev.data[0], scY: prev.data[1]});} + if (!prev.path) {prev.path = 1;} + for (var i = 0; i < limit; i++) { + current = from[current]; + var cur = cells[current]; + if (!cur) {break;} + if (cur.path) { + cur.path += 1; + path.push({scX: cur.data[0], scY: cur.data[1]}); + prev = cur; + drawPath(); + } else { + cur.path = 1; + if (prev) {path.push({scX: prev.data[0], scY: prev.data[1]});} + prev = undefined; + path.push({scX: cur.data[0], scY: cur.data[1]}); + } + if (current === start || !from[current]) {break;} + } + drawPath(); + function drawPath() { + if (path.length > 1) { + var line = lineGen(path); + line = round(line); + if (type === "main") { + roads.append("path").attr("d", line).attr("data-start", start).attr("data-end", end); + } else if (type === "small") { + trails.append("path").attr("d", line); + } else if (type === "ocean") { + searoutes.append("path").attr("d", line); + } + } + path = []; + } + } + + // Append manors with random / generated names + // For each non-capital manor detect the closes capital (used for areas) + function drawManors() { + console.time('drawManors'); + for (var i = 0; i < manors.length; i++) { + var x = manors[i].x; + var y = manors[i].y; + var cell = manors[i].cell; + var name = manors[i].name; + if (i < capitalsCount) { + burgs.append("circle").attr("r", 1).attr("stroke-width", .24).attr("class", "manor").attr("cx", x).attr("cy", y); + capitals.append("text").attr("x", x).attr("y", y).attr("dy", -1.3).text(name); + } else { + burgs.append("circle").attr("r", .5).attr("stroke-width", .12).attr("class", "manor").attr("cx", x).attr("cy", y); + towns.append("text").attr("x", x).attr("y", y).attr("dy", -.7).text(name); + } + } + labels.selectAll("text").on("click", editLabel); + burgs.selectAll("circle").call(d3.drag().on("drag", dragged).on("end", dragended)).on("click", changeBurg); + console.timeEnd('drawManors'); + } + + // calculate Markov's chain from real data + function calculateChains() { + var vowels = "aeiouy"; + //var digraphs = ["ai","ay","ea","ee","ei","ey","ie","oa","oo","ow","ue","ch","ng","ph","sh","th","wh"]; + for (var l = 0; l < cultures.length; l++) { + var probs = []; // Coleshill -> co les hil l-com + var inline = manorNames[l].join(" ").toLowerCase(); + var syl = ""; + for (var i = -1; i < inline.length - 2;) { + if (i < 0) {var f = " ";} else {var f = inline[i];} + var str = "", vowel = 0; + for (var c = i+1; str.length < 5; c++) { + if (inline[c] === undefined) {break;} + str += inline[c]; + if (str === " ") {break;} + if (inline[c] !== "o" && inline[c] !== "e" && vowels.includes(inline[c]) && inline[c+1] === inline[c]) {break;} + if (inline[c+2] === " ") {str += inline[c+1]; break;} + if (vowels.includes(inline[c])) {vowel++;} + if (vowel && vowels.includes(inline[c+2])) {break;} + } + i += str.length; + probs[f] = probs[f] || []; + probs[f].push(str); + } + chain[l] = probs; + } + } + + // generate random name using Markov's chain + function generateName(culture) { + var data = chain[culture], res = "", next = data[" "]; + var cur = next[Math.floor(Math.random() * next.length)]; + while (res.length < 7) { + var l = cur.charAt(cur.length - 1); + if (cur !== " ") { + res += cur; + next = data[l]; + cur = next[Math.floor(Math.random() * next.length)]; + } else if (res.length > 2 + Math.floor(Math.random() * 5)) { + break; + } else { + next = data[" "]; + cur = next[Math.floor(Math.random() * next.length)]; + } + } + var name = res.charAt(0).toUpperCase() + res.slice(1); + return name; + } + + // Define areas based on the closest manor to a polygon + function defineRegions() { + console.time('defineRegions'); + manorTree = d3.quadtree().extent([[0, 0], [mapHeight, mapWidth]]); + manors.map(function(m) {manorTree.add([m.x, m.y]);}); + land.map(function(i) { + if (i.region === undefined) { + var closest = manorTree.find(i.data[0], i.data[1]); + var dist = Math.hypot(closest[0] - i.data[0], closest[1] - i.data[1]); + if (dist > neutral) { + i.region = "neutral"; + var closestCulture = cultureTree.find(i.data[0], i.data[1]); + i.culture = cultureTree.data().indexOf(closestCulture); + } else { + var manor = $.grep(manors, function(e) {return (e.x === closest[0] && e.y === closest[1]);}); + var cell = manor[0].cell; + if (cells[cell].featureNumber !== i.featureNumber) { + var minDist = dist * 3; + land.map(function(l) { + if (l.featureNumber === i.featureNumber && l.manor !== undefined) { + var distN = Math.hypot(l.data[0] - i.data[0], l.data[1] - i.data[1]); + if (distN < minDist) {minDist = distN; cell = l.index;} + } + }); + } + i.region = cells[cell].region; + i.culture = cells[cell].culture; + } + } + }); + console.timeEnd('defineRegions'); + } + + // Define areas cells + function drawRegions() { + console.time('drawRegions'); + var edges = [], borderEdges = [], coastalEdges = [], neutralEdges = []; // arrays to store edges + for (var i = 0; i < capitalsCount; i++) { + edges[i] = []; + land.map(function(p) { + if (p.region === i) { + var cell = diagram.cells[p.index]; + cell.halfedges.forEach(function(e) { + var edge = diagram.edges[e]; + if (edge.left && edge.right) { + var ea = edge.left.index; + if (ea === p.index) {ea = edge.right.index;} + var opp = cells[ea]; + if (opp.region !== i) { + var start = edge[0].join(" "); + var end = edge[1].join(" "); + edges[i].push({start, end}); + if (opp.height >= 0.2 && opp.region > i) {borderEdges.push({start, end});} + if (opp.height >= 0.2 && opp.region === "neutral") {neutralEdges.push({start, end});} + if (opp.height < 0.2) {coastalEdges.push({start, end});} + } + } + }) + } + }); + drawRegion(edges[i], i); + drawRegionCoast(coastalEdges, i); + } + drawBorders(borderEdges, "state"); + drawBorders(neutralEdges, "neutral"); + console.timeEnd('drawRegions'); + } + + function drawRegion(edges, region) { + var path = "", array = []; + lineGen.curve(d3.curveLinear); + 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]}); + 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(" "); + edgesOrdered.push({scX: spl[0], scY: spl[1]}); + } + var rem = edges.indexOf(next[0]); + edges.splice(rem, 1); + } + path += lineGen(edgesOrdered) + "Z "; + var edgesFormatted = []; + edgesOrdered.map(function(e) {edgesFormatted.push([+e.scX, +e.scY])}); + array[array.length] = edgesFormatted; + } + if (capitalsCount <= 8) { + var scheme = colors8; + } else { + var scheme = colors20; + } + var color = scheme(region / capitalsCount); + regions.append("path").attr("d", round(path)).attr("fill", color).attr("stroke", "none").attr("class", "region"+region); + array.sort(function(a, b){return b.length - a.length;}); + generateRegionName(array, region); + } + + function drawRegionCoast(edges, region) { + var path = ""; + while (edges.length > 0) { + 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 next = $.grep(edges, function(e) {return (e.start == end || e.end == end);}); + while (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(" "); + edgesOrdered.push({scX: spl[0], scY: spl[1]}); + var rem = edges.indexOf(next[0]); + edges.splice(rem, 1); + next = $.grep(edges, function(e) {return (e.start == end || e.end == end);}); + } + path += lineGen(edgesOrdered); + } + if (capitalsCount <= 8) { + var scheme = colors8; + } else { + var scheme = colors20; + } + var color = scheme(region / capitalsCount); + regions.append("path").attr("d", round(path)).attr("fill", "none").attr("stroke", color).attr("stroke-width", 1.5).attr("class", "region"+region); + } + + function drawBorders(edges, type) { + var path = ""; + while (edges.length > 0) { + 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 next = $.grep(edges, function(e) {return (e.start == end || e.end == end);}); + while (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(" "); + edgesOrdered.push({scX: spl[0], scY: spl[1]}); + var rem = edges.indexOf(next[0]); + edges.splice(rem, 1); + next = $.grep(edges, function(e) {return (e.start == end || e.end == end);}); + } + path += lineGen(edgesOrdered); + } + if (type === "state") {stateBorders.append("path").attr("d", round(path));} + if (type === "neutral") {neutralBorders.append("path").attr("d", round(path));} + } + + // generate region name and place label at pole of inaccessibility of the largest continuous element of the region + function generateRegionName(array, region) { + var name; + var culture = manors[region].culture; + var c = polylabel(array, 1.0); // pole of inaccessibility + // get source name (capital name = 20%; random name = 80%) + if (Math.random() < 0.8) { + name = generateName(culture); + } else { + name = manors[region].name; + } + name = addRegionSuffix(name, culture); + countries.append("text").attr("x", c[0].toFixed(2)).attr("y", c[1].toFixed(2)).text(name).on("click", editLabel); + } + + function addRegionSuffix(name, culture) { + var suffix = "ia"; // common latin suffix + var vowels = "aeiouy"; + if (Math.random() < 0.05 && (culture == 3 || culture == 4)) {suffix = "terra";} // 5% "terra" for Italian and Spanish + if (Math.random() < 0.05 && culture == 2) {suffix = "terre";} // 5% "terre" for French + if (Math.random() < 0.5 && culture == 0) {suffix = "land";} // 50% "land" for German + if (Math.random() < 0.33 && (culture == 1 || culture == 6)) {suffix = "land";} // 33% "land" for English and Scandinavian + if (culture == 5 && name.slice(-2) === "sk") {name.slice(0,-2);} // exclude -sk suffix for Slavic + if (name.indexOf(suffix) !== -1) {suffix = "";} // null suffix if name already contains it + var ending = name.slice(-1); + if (vowels.includes(ending) && name.length > 3) { + if (Math.random() > 0.2) { + ending = name.slice(-2,-1); + if (vowels.includes(ending)) { + name = name.slice(0,-2) + suffix; // 80% for vv + } else if (Math.random() > 0.2) { + name = name.slice(0,-1) + suffix; // 64% for cv + } + } + } else if (Math.random() > 0.5) { + name += suffix // 50% for cc + } + //if (name.slice(-2) !== "ia" && culture == 5 && Math.random() > 0.5) {name += "skaya Zemya";} // special case for Slavic + if (name.slice(-4) === "berg") {name += suffix;} // special case for -berg + return name; + } + + // draw the Heightmap + function toggleHeight() { + var scheme = styleSchemeInput.value; + var hColor = color; + if (scheme === "light") {hColor = d3.scaleSequential(d3.interpolateRdYlGn);} + if (scheme === "green") {hColor = d3.scaleSequential(d3.interpolateGreens);} + if (scheme === "monochrome") {hColor = d3.scaleSequential(d3.interpolateGreys);} + if (terrs.selectAll("path").size() == 0) { + land.map(function(i) { + terrs.append("path") + .attr("d", "M" + polygons[i.index].join("L") + "Z") + .attr("fill", hColor(1 - i.height)) + .attr("stroke", hColor(1 - i.height)); + }); + } else { + terrs.selectAll("path").remove(); + } + } + + // draw Cultures + function toggleCultures() { + if (cults.selectAll("path").size() == 0) { + land.map(function(i) { + cults.append("path") + .attr("d", "M" + polygons[i.index].join("L") + "Z") + .attr("fill", colors8(i.culture / cultures.length)) + .attr("stroke", colors8(i.culture / cultures.length)); + }); + } else { + cults.selectAll("path").remove(); + } + } + + // Draw the water flux system (for dubugging) + function toggleFlux() { + var colorFlux = d3.scaleSequential(d3.interpolateBlues); + if (terrs.selectAll("path").size() == 0) { + land.map(function(i) { + terrs.append("path") + .attr("d", "M" + polygons[i.index].join("L") + "Z") + .attr("fill", colorFlux(0.1 + i.flux)) + .attr("stroke", colorFlux(0.1 + i.flux)); + }); + } else { + terrs.selectAll("path").remove(); + } + } + + // Draw the Relief (need to create more beautiness) + function drawRelief() { + console.time('drawRelief'); + var ea, edge, id, cell, x, y, height, path, dash = "", rnd; + var hill = [], hShade = [], swamp = "", swampCount = 0, forest = "", fShade = "", fLight = "", swamp = ""; + hill[0] = "", hill[1] = "", hShade[0] = "", hShade[1] = ""; + var strokes = terrain.append("g").attr("id", "strokes"), + hills = terrain.append("g").attr("id", "hills"), + mounts = terrain.append("g").attr("id", "mounts"), + swamps = terrain.append("g").attr("id", "swamps"), + forests = terrain.append("g").attr("id", "forests"); + // sort the land to Draw the top element first (reduce the elements overlapping) + land.sort(compareY); + for (i = 0; i < land.length; i++) { + x = land[i].data[0]; + y = land[i].data[1]; + height = land[i].height; + if (height >= 0.7 && !land[i].river) { + h = (height - 0.55) * 12; + if (height < 0.8) { + count = 2; + } else { + count = 1; + } + rnd = Math.random() * 0.8 + 0.2; + for (c = 0; c < count; c++) { + cx = x - h * 0.9 - c; + cy = y + h / 4 + c / 2; + path = "M " + cx + "," + cy + " L " + (cx + h / 3 + rnd) + "," + (cy - h / 4 - rnd * 1.2) + " L " + (cx + h / 1.1) + "," + (cy - h) + " L " + (cx + h + rnd) + "," + (cy - h / 1.2 + rnd) + " L " + (cx + h * 2) + "," + cy; + mounts.append("path").attr("d", path).attr("stroke", "#5c5c70"); + path = "M " + cx + "," + cy + " L " + (cx + h / 3 + rnd) + "," + (cy - h / 4 - rnd * 1.2) + " L " + (cx + h / 1.1) + "," + (cy - h) + " L " + (cx + h / 1.5) + "," + cy; + mounts.append("path").attr("d", path).attr("fill", "#999999"); + dash += "M" + (cx - 0.1) + "," + (cy + 0.3) + " L" + (cx + 2 * h + 0.1) + "," + (cy + 0.3); + } + dash += "M" + (cx + 0.4) + "," + (cy + 0.6) + " L" + (cx + 2 * h - 0.3) + "," + (cy + 0.6); + } else if (height > 0.5 && !land[i].river) { + h = (height - 0.4) * 10; + count = Math.floor(4 - h); + if (h > 1.8) { + h = 1.8 + } + for (c = 0; c < count; c++) { + cx = x - h - c; + cy = y + h / 4 + c / 2; + hill[c] += "M" + cx + "," + cy + " Q" + (cx + h) + "," + (cy - h) + " " + (cx + 2 * h) + "," + cy; + hShade[c] += "M" + (cx + 0.6 * h) + "," + (cy + 0.1) + " Q" + (cx + h * 0.95) + "," + (cy - h * 0.91) + " " + (cx + 2 * h * 0.97) + "," + cy; + dash += "M" + (cx - 0.1) + "," + (cy + 0.2) + " L" + (cx + 2 * h + 0.1) + "," + (cy + 0.2); + } + dash += "M" + (cx + 0.4) + "," + (cy + 0.4) + " L" + (cx + 2 * h - 0.3) + "," + (cy + 0.4); + } + if (height >= 0.21 && height < 0.22 && !land[i].river && swampCount < swampiness && land[i].used != 1) { + swampCount++; + land[i].used = 1; + swamp += drawSwamp(x, y); + id = land[i].index; + cell = diagram.cells[id]; + cell.halfedges.forEach(function(e) { + edge = diagram.edges[e]; + ea = edge.left.index; + if (ea === id || !ea) { + ea = edge.right.index; + } + if (cells[ea].height >= 0.2 && cells[ea].height < 0.3 && !cells[ea].river && cells[ea].used != 1) { + cells[ea].used = 1; + swamp += drawSwamp(cells[ea].data[0], cells[ea].data[1]); + } + }) + } + if (Math.random() < height && height >= 0.22 && height < 0.48 && !land[i].river) { + for (c = 0; c < Math.floor(height * 8); c++) { + h = 0.6; + if (c == 0) { + cx = x - h - Math.random(); + cy = y - h - Math.random(); + } + if (c == 1) { + cx = x + h + Math.random(); + cy = y + h + Math.random(); + } + if (c == 2) { + cx = x - h - Math.random(); + cy = y + 2 * h + Math.random(); + } + forest += "M " + cx + " " + cy + " q -1 0.8 -0.05 1.25 v 0.75 h 0.1 v -0.75 q 0.95 -0.47 -0.05 -1.25 z"; + fLight += "M " + cx + " " + cy + " q -1 0.8 -0.05 1.25 h 0.1 q 0.95 -0.47 -0.05 -1.25 z"; + fShade += "M " + cx + " " + cy + " q -1 0.8 -0.05 1.25 q -0.2 -0.55 0 -1.1 z"; + } + } + } + // draw all these stuff + strokes.append("path").attr("d", round(dash)); + hills.append("path").attr("d", round(hill[0])).attr("stroke", "#5c5c70"); + hills.append("path").attr("d", round(hShade[0])).attr("fill", "white"); + hills.append("path").attr("d", round(hill[1])).attr("stroke", "#5c5c70"); + hills.append("path").attr("d", round(hShade[1])).attr("fill", "white").attr("stroke", "white"); + swamps.append("path").attr("d", round(swamp)); + forests.append("path").attr("d", forest); + forests.append("path").attr("d", fLight).attr("fill", "white").attr("stroke", "none"); + forests.append("path").attr("d", fShade).attr("fill", "#999999").attr("stroke", "none"); + console.timeEnd('drawRelief'); + } + + function compareY(a, b) { + if (a.data[1] > b.data[1]) return 1; + if (a.data[1] < b.data[1]) return -1; + return 0; + } + + function drawSwamp(x, y) { + var h = 0.6, line = ""; + for (c = 0; c < 3; c++) { + if (c == 0) { + cx = x; + cy = y - 0.5 - Math.random(); + } + if (c == 1) { + cx = x + h + Math.random(); + cy = y + h + Math.random(); + } + if (c == 2) { + cx = x - h - Math.random(); + cy = y + 2 * h + Math.random(); + } + line += "M" + cx + "," + cy + " H" + (cx - h / 6) + " M" + cx + "," + cy + " H" + (cx + h / 6) + " M" + cx + "," + cy + " L" + (cx - h / 3) + "," + (cy - h / 2) + " M" + cx + "," + cy + " V" + (cy - h / 1.5) + " M" + cx + "," + cy + " L" + (cx + h / 3) + "," + (cy - h / 2); + line += "M" + (cx - h) + "," + cy + " H" + (cx - h / 2) + " M" + (cx + h / 2) + "," + cy + " H" + (cx + h); + } + return line; + } + + function dragged(e) { + var el = d3.select(this); + var x = d3.event.x; + var y = d3.event.y; + el.raise().classed("drag", true); + if (el.attr("x")) { + el.attr("x", x).attr("y", y + 0.8); + var matrix = el.attr("transform"); + if (matrix) { + var angle = matrix.split('(')[1].split(')')[0].split(' ')[0]; + var bbox = el.node().getBBox(); + var rotate = "rotate("+ angle + " " + (bbox.x + bbox.width/2) + " " + (bbox.y + bbox.height/2) + ")"; + el.attr("transform", rotate); + } + } else { + el.attr("cx", x).attr("cy", y); + } + } + + function dragended(d) { + d3.select(this).classed("drag", false); + } + + // Complete the map for the "customize" mode + function getMap() { + exitCustomization(); + console.time("TOTAL"); + markFeatures(); + drawOcean(); + reGraph(); + resolveDepressions(); + flux(); + drawRelief(); + drawCoastline(); + manorsAndRegions(); + console.timeEnd("TOTAL"); + } + + // Add label or burg on mouseclick + function clicked() { + var brush = $("#options .pressed").attr("id"); + var point = d3.mouse(this); + if (brush === "addLabel" || brush === "addBurg") { + var rnd = Math.floor(Math.random() * cultures.length); + var name = generateName(rnd); + if (brush === "addLabel") { + countries.append("text").attr("x", point[0]).attr("y", point[1]).text(name).on("click", editLabel); + } else { + burgs.append("circle").attr("r", 1).attr("stroke-width", .24) + .attr("cx", point[0]).attr("cy", point[1]) + .call(d3.drag().on("drag", dragged).on("end", dragended)).on("click", changeBurg); + capitals.append("text").attr("x", point[0]).attr("y", point[1]).attr("dy", -1.3).text(name).on("click", editLabel); + } + return; + } + if (customization === 1 && brush) { + var cell = diagram.find(point[0], point[1]).index; + var power = +brushPower.value; + if (brush === "brushElevate") {cells[cell].height = +cells[cell].height + power;} + if (brush === "brushDepress") {cells[cell].height = +cells[cell].height - power;} + if (brush === "brushHill") {add(cell, "hill", power);} + if (brush === "brushPit") {addPit(1, power, cell);} + if (brush === "brushRange" || brush === "brushTrough") { + if (icons.selectAll(".tag").size() === 0) { + icons.append("circle").attr("r", 3).attr("class", "tag").attr("cx", point[0]).attr("cy", point[1]); + } else { + var x = +icons.select(".tag").attr("cx"); + var y = +icons.select(".tag").attr("cy"); + var from = diagram.find(x, y).index; + icons.selectAll(".tag, .line").remove(); + addRange(brush === "brushRange" ? 1 : -1, power, from, cell); + } + } + mockHeightmap(); + } + // add new river point if elSelected is river (has data-points) and add button pressed + if (!elSelected) {return;} + if (elSelected.attr("data-points")) { + if ($("#riverAddPoint").hasClass('pressed')) { + var dists = [], points = []; + var tr = parseTransform(elSelected.attr("transform")); + if (tr[5] == "1") { + point[0] -= +tr[0]; + point[1] -= +tr[1]; + } + rivers.select(".riverPoints").selectAll("circle").each(function() { + var x = +d3.select(this).attr("cx"); + var y = +d3.select(this).attr("cy"); + dists.push(Math.hypot(point[0] - x, point[1] - y)); + points.push({scX:x, scY:y}); + }).remove(); + var index = dists.length; + if (points.length > 1) { + var sorded = dists.slice(0).sort(function(a, b) {return a-b;}); + var closest = dists.indexOf(sorded[0]); + var next = dists.indexOf(sorded[1]); + if (closest <= next) {index = closest+1;} else {index = next+1;} + } + points.splice(index, 0, {scX:point[0], scY:point[1]}); + lineGen.curve(d3.curveCatmullRom.alpha(0.1)); + var d = drawRiver(points); + elSelected.attr("d", d).attr("data-points", JSON.stringify(points)); + points.map(function(p) {addRiverPoint(p)}); + return; + } + if ($("#riverNew").hasClass('pressed')) { + if (elSelected.attr("data-river") !== "new") { + elSelected.call(d3.drag().on("drag", null)).classed("draggable", false); + elSelected = rivers.append("path").attr("data-river", "new").on("click", editRiver); + } + addRiverPoint({scX:point[0], scY:point[1]}); + redrawRiver(); + return; + } + } + } + + // Change burg marker size on click + function changeBurg() { + var size = this.getAttribute("r"); + size = +size + .25; + if (size > 1.5) {size = .5;} + var width = this.getAttribute("stroke-width"); + width = +width + .06; + if (width > .36) {width = .12;} + var type = this.getAttribute("class"); + if (type) { + d3.selectAll("."+type).attr("r", size).attr("stroke-width", width); + } else { + this.setAttribute("r", size); + this.setAttribute("stroke-width", width); + } + } + + function editLabel() { + if (elSelected) { + elSelected.call(d3.drag().on("drag", null)).classed("draggable", false); + } + elSelected = d3.select(this); + elSelected.call(d3.drag().on("drag", dragged).on("end", dragended)).classed("draggable", true); + var group = d3.select(this.parentNode); + updateGroupOptions(); + editGroupSelect.value = group.attr("id"); + editFontSelect.value = fonts.indexOf(group.attr("data-font")); + editSize.value = group.attr("font-size"); + editColor.value = toHEX(group.attr("fill")); + editOpacity.value = group.attr("opacity"); + editText.value = elSelected.text(); + var matrix = elSelected.attr("transform"); + if (matrix) { + var rotation = matrix.split('(')[1].split(')')[0].split(' ')[0]; + } else { + var rotation = 0; + } + editAngle.value = rotation; + editAngleValue.innerHTML = rotation + "°"; + $("#labelEditor").dialog({ + title: "Edit Label: " + editText.value, + minHeight: 30, width: "auto", maxWidth: 275, resizable: false, + position: {my: "center top", at: "bottom", of: this} + }); + // fetch default fonts if not done before + if (fonts.indexOf("Bitter") === -1) { + $("head").append(''); + fonts = ["Amatic+SC:700", "IM+Fell+English", "Great+Vibes", "Bitter", "Yellowtail", "Montez", "Lobster", "Josefin+Sans", "Shadows+Into+Light", "Orbitron", "Dancing+Script:700", "Bangers", "Chewy", "Architects+Daughter", "Kaushan+Script", "Gloria+Hallelujah", "Satisfy", "Comfortaa:700", "Cinzel"]; + updateFontOptions(); + } + } + + $("#labelEditor .editButton, #labelEditor .editButtonS").click(function() { + var group = d3.select(elSelected.node().parentNode); + if (this.id == "editRemoveSingle") { + alertMessage.innerHTML = "Are you sure you want to remove the label?"; + $(function() {$("#alert").dialog({resizable: false, title: "Remove label", + buttons: { + "Remove": function() { + $(this).dialog("close"); + elSelected.remove(); + $("#labelEditor").dialog("close"); + }, + Cancel: function() {$(this).dialog("close");} + }}) + }); + return; + } + if (this.id == "editGroupRemove") { + var count = group.selectAll("text").size() + if (count < 2) { + group.remove(); + $("#labelEditor").dialog("close"); + return; + } + var message = "Are you sure you want to remove all labels (" + count + ") of that group?"; + alertMessage.innerHTML = message; + $(function() {$("#alert").dialog({resizable: false, title: "Remove labels", + buttons: { + "Remove": function() { + $(this).dialog("close"); + group.remove(); + $("#labelEditor").dialog("close"); + }, + Cancel: function() {$(this).dialog("close");} + }}) + }); + return; + } + if (this.id == "editCopy") { + var shift = +group.attr("font-size") + 1; + var xn = +elSelected.attr("x") - shift; + var yn = +elSelected.attr("y") - shift; + while (group.selectAll("text[x='" + xn + "']").size() > 0) {xn -= shift; yn -= shift;} + group.append("text").attr("x", xn).attr("y", yn).text(elSelected.text()) + .attr("transform", elSelected.attr("transform")).on("click", editLabel); + return; + } + if (this.id == "editGroupNew") { + if ($("#editGroupInput").css("display") === "none") { + $("#editGroupInput").css("display", "inline-block"); + $("#editGroupSelect").css("display", "none"); + editGroupInput.focus(); + } else { + $("#editGroupSelect").css("display", "inline-block"); + $("#editGroupInput").css("display", "none"); + } + return; + } + if (this.id == "editExternalFont") { + if ($("#editFontInput").css("display") === "none") { + $("#editFontInput").css("display", "inline-block"); + $("#editFontSelect").css("display", "none"); + editFontInput.focus(); + } else { + $("#editFontSelect").css("display", "inline-block"); + $("#editFontInput").css("display", "none"); + } + return; + } + if (this.id == "editTextRandom") { + var culture, index; + // check if label is manor name to get culture + var manor = $.grep(manors, function(e) {return (e.name === editText.value);}); + if (manor.length === 1) { + culture = manor[0].culture; + index = manor[0].i; + } else { + // if not get cell's culture at BBox centre + var c = elSelected.node().getBBox(); + var x = c.x + c.width / 2; + var y = c.y + c.height / 2; + culture = diagram.find(x, y).culture; + if (!culture) {culture = 0;} + } + var name = generateName(culture); + if (group.attr("id") === "countries") {name = addRegionSuffix(name, culture);} + editText.value = name; + elSelected.text(name); + $("div[aria-describedby='labelEditor'] .ui-dialog-title").text("Edit Label: " + name); + if (manor.length === 1) {manors[index].name = name;} + return; + } + $("#labelEditor .editButton").toggle(); + if (this.id == "editGroupButton") { + if ($("#editGroupInput").css("display") !== "none") {$("#editGroupSelect").css("display", "inline-block");} + if ($("#editGroupRemove").css("display") === "none") { + $("#editGroupRemove, #editGroupNew").css("display", "inline-block"); + } else { + $("#editGroupInput, #editGroupRemove, #editGroupNew").css("display", "none"); + } + } + if (this.id == "editFontButton") {$("#editSizeIcon, #editFontSelect, #editSize").toggle();} + if (this.id == "editStyleButton") {$("#editOpacityIcon, #editOpacity, #editShadowIcon, #editShadow").toggle();} + if (this.id == "editAngleButton") {$("#editAngleValue").toggle();} + if (this.id == "editTextButton") {$("#editTextRandom").toggle();} + $(this).show().next().toggle(); + }); + + function updateGroupOptions() { + editGroupSelect.innerHTML = ""; + labels.selectAll("g").each(function(d) { + var opt = document.createElement("option"); + opt.value = opt.innerHTML = d3.select(this).attr("id"); + editGroupSelect.add(opt); + }); + } + + // on editAngle change + $("#editAngle").change(function() { + var c = elSelected.node().getBBox(); + var rotate = `rotate(${this.value} ${(c.x+c.width/2)} ${(c.y+c.height/2)})`; + elSelected.attr("transform", rotate); + }); + + // on editFontInput change. Use a direct link to any @font-face declaration or just font name to fetch from Google Fonts + $("#editFontInput").change(function() { + if (editFontInput.value !== "") { + var url = (editFontInput.value).replace(/ /g, "+"); + if (url.indexOf("http") === -1) {url = "https://fonts.googleapis.com/css?family=" + url;} + addFonts(url); + editFontInput.value = ""; + editExternalFont.click(); + } + }); + + function addFonts(url) { + $('head').append(''); + return fetch(url) + .then(resp => resp.text()) + .then(text => { + let s = document.createElement('style'); + s.innerHTML = text; + document.head.appendChild(s); + let styleSheet = Array.prototype.filter.call( + document.styleSheets, + sS => sS.ownerNode === s)[0]; + let FontRule = rule => { + let family = rule.style.getPropertyValue('font-family'); + let weight = rule.style.getPropertyValue('font-weight'); + let font = family.replace(/['"]+/g, '').replace(/ /g, "+") + ":" + weight; + if (fonts.indexOf(font) == -1) {fonts.push(font);} + }; + for (var r of styleSheet.cssRules) {FontRule(r);} + document.head.removeChild(s); + updateFontOptions(); + }) + } + + // on any Editor input change + $("#labelEditor .editTrigger").change(function() { + $(this).attr("title", $(this).val()); + elSelected.text(editText.value); + // check if Group was changed + var group = d3.select(elSelected.node().parentNode); + var groupOld = group.attr("id"); + var groupNew = editGroupSelect.value; + if (editGroupInput.value !== "") { + groupNew = editGroupInput.value.toLowerCase().replace(/ /g, "_").replace(/[^\w\s]/gi, ""); + if (Number.isFinite(+groupNew.charAt(0))) {groupNew = "g" + groupNew;} + } + if (groupOld !== groupNew) { + var removed = elSelected.remove(); + if (labels.select("#"+groupNew).size() > 0) { + group = labels.select("#"+groupNew); + editFontSelect.value = fonts.indexOf(group.attr("data-font")); + editSize.value = group.attr("font-size"); + editColor.value = toHEX(group.attr("fill")); + editOpacity.value = group.attr("opacity"); + } else { + if (group.selectAll("text").size() === 0) {group.remove();} + group = labels.append("g").attr("id", groupNew); + updateGroupOptions(); + $("#editGroupSelect, #editGroupInput").toggle(); + editGroupInput.value = ""; + } + group.append(function() {return removed.node();}); + editGroupSelect.value = group.attr("id"); + } + // update Group attributes + var font = fonts[editFontSelect.value].split(':')[0].replace(/\+/g, " "); + group.attr("font-size", editSize.value) + .attr("font-family", font) + .attr("data-font", fonts[editFontSelect.value]) + .attr("fill", editColor.title) + .attr("opacity", editOpacity.value); + }); + + // Update font list for Label Editor + function updateFontOptions() { + editFontSelect.innerHTML = ""; + for (var i=0; i < fonts.length; i++) { + var opt = document.createElement('option'); + opt.value = i; + var font = fonts[i].split(':')[0].replace(/\+/g, " "); + opt.style.fontFamily = opt.innerHTML = font; + editFontSelect.add(opt); + } + } + + // convert RGB color string to HEX without # + function toHEX(rgb){ + if (rgb.charAt(0) === "#") {return rgb;} + rgb = rgb.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i); + return (rgb && rgb.length === 4) ? "#" + + ("0" + parseInt(rgb[1],10).toString(16)).slice(-2) + + ("0" + parseInt(rgb[2],10).toString(16)).slice(-2) + + ("0" + parseInt(rgb[3],10).toString(16)).slice(-2) : ''; + } + + // get Curve Type + function getCurveType() { + type = curveType.value; + if (type === "Catmull–Rom") {lineGen.curve(d3.curveCatmullRom);} + if (type === "Linear") {lineGen.curve(d3.curveLinear);} + if (type === "Basis") {lineGen.curve(d3.curveBasisClosed);} + if (type === "Cardinal") {lineGen.curve(d3.curveCardinal);} + if (type === "Step") {lineGen.curve(d3.curveStep);} + } + + // source from https://gist.github.com/jimhigson/7985923 + function round(path) { + return path.replace(/[\d\.-][\d\.e-]*/g, function(n) {return Math.round(n*10)/10;}) + } + + // downalod map as SVG or PNG file + function saveAsImage(type) { + console.time("saveAsImage"); + // get all used fonts + if (type === "svg") {viewbox.attr("transform", null);} + var fontsInUse = []; // to store fonts currently in use + labels.selectAll("g").each(function(d) { + var font = d3.select(this).attr("data-font"); + if (fontsInUse.indexOf(font) === -1) {fontsInUse.push(font);} + }); + var fontsToLoad = "https://fonts.googleapis.com/css?family=" + fontsInUse.join("|"); + + // clone svg + var cloneEl = document.getElementsByTagName("svg")[0].cloneNode(true); + cloneEl.id = "clone"; + document.getElementsByTagName("body")[0].appendChild(cloneEl); + var clone = d3.select("#clone"); + + // for each g element get inline style so it could be used in saved svg + var emptyG = clone.append("g").node(); + var defaultStyles = window.getComputedStyle(emptyG); + clone.selectAll("g").each(function(d) { + var compStyle = window.getComputedStyle(this); + var style = ""; + for (var i=0; i < compStyle.length; i++) { + var key = compStyle[i]; + var value = compStyle.getPropertyValue(key); + if (key !== "cursor" && value != defaultStyles.getPropertyValue(key)) { + style += key + ':' + value + ';'; + } + } + if (style != "") {this.setAttribute('style', style);} + }); + emptyG.remove(); + + // load fonts as dataURI so they will be available in downloaded svg/png + GFontToDataURI(fontsToLoad).then(cssRules => { + clone.select("defs").append("style").text(cssRules.join('\n')); + var svg_xml = (new XMLSerializer()).serializeToString(clone.node()); + var blob = new Blob([svg_xml], {type:'image/svg+xml;charset=utf-8'}); + var url = window.URL.createObjectURL(blob); + var link = document.createElement("a"); + if (type === "png") { + canvas.width = mapWidth * 2; + canvas.height = mapHeight * 2; + var img = new Image(); + img.src = url; + img.onload = function(){ + ctx.drawImage(img, 0, 0, mapWidth * 2, mapHeight * 2); + link.download = "fantasy_map_" + Date.now() + ".png"; + link.href = canvas.toDataURL('image/png'); + canvas.width = mapWidth; + canvas.height = mapHeight; + canvas.style.opacity = 0; + document.body.appendChild(link); + link.click(); + } + } else { + link.download = "fantasy_map_" + Date.now() + ".svg"; + link.href = url; + document.body.appendChild(link); + link.click(); + } + clone.remove(); + console.timeEnd("saveAsImage"); + window.setTimeout(function() {window.URL.revokeObjectURL(url);}, 2000); + }); + } + + // Code from Kaiido's answer: + // https://stackoverflow.com/questions/42402584/how-to-use-google-fonts-in-canvas-when-drawing-dom-objects-in-svg + function GFontToDataURI(url) { + "use strict;" + return fetch(url) // first fecth the embed stylesheet page + .then(resp => resp.text()) // we only need the text of it + .then(text => { + let s = document.createElement('style'); + s.innerHTML = text; + document.head.appendChild(s); + let styleSheet = Array.prototype.filter.call( + document.styleSheets, + sS => sS.ownerNode === s)[0]; + let FontRule = rule => { + let src = rule.style.getPropertyValue('src'); + let family = rule.style.getPropertyValue('font-family'); + let url = src.split('url(')[1].split(')')[0]; + return { + rule: rule, + src: src, + url: url.substring(url.length - 1, 1) + }; + }; + let fontRules = [], fontProms = []; + + for (var r of styleSheet.cssRules) { + let fR = FontRule(r) + fontRules.push(fR); + fontProms.push( + fetch(fR.url) // fetch the actual font-file (.woff) + .then(resp => resp.blob()) + .then(blob => { + return new Promise(resolve => { + let f = new FileReader(); + f.onload = e => resolve(f.result); + f.readAsDataURL(blob); + }) + }) + .then(dataURL => { + return fR.rule.cssText.replace(fR.url, dataURL); + }) + ) + } + document.head.removeChild(s); // clean up + return Promise.all(fontProms); // wait for all this has been done + }); + } + + // Save in .map format, based on FileSystem API + function saveMap() { + console.time("saveMap"); + // data convention: 0 - version; 1 - all points; 2 - cells; 3 - manors; 4 - svg; + var svg_xml = (new XMLSerializer()).serializeToString(svg.node()); + var line = "\r\n"; + var data = version + line + JSON.stringify(points) + line + JSON.stringify(cells) + line + JSON.stringify(manors) + line + svg_xml; + var dataBlob = new Blob([data], {type:"text/plain"}); + var dataURL = window.URL.createObjectURL(dataBlob); + var link = document.createElement("a"); + link.download = "fantasy_map_" + Date.now() + ".map"; + link.href = dataURL; + document.body.appendChild(link); + link.click(); + console.timeEnd("saveMap"); + window.setTimeout(function() {window.URL.revokeObjectURL(dataURL);}, 2000); + } + + // Map Loader based on FileSystem API + $("#fileToLoad").change(function() { + console.time("loadMap"); + var fileToLoad = this.files[0]; + this.value = ""; + var fileReader = new FileReader(); + fileReader.onload = function(fileLoadedEvent) { + newPoints = [], points = [], cells = [], land = [], riversData = [], island = 0, manors = [], queue = []; + var dataLoaded = fileLoadedEvent.target.result; + svg.remove(); + var data = dataLoaded.split("\r\n"); + // data convention: 0 - version; 1 - all points; 2 - cells; 3 - manors; 4 - svg; + var mapVersion = data[0]; + if (mapVersion !== version) { + var message = `The Map version does not match the Generator version (${version}). In case of issues please send the .map file to me (maxganiev@yandex.ru) for update or use an archived version of the Generator (https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog)`; + alertMessage.innerHTML = message; + $(function() {$("#alert").dialog({title: "Load map"});}); + } + if (mapVersion.length > 10) {console.error("Cannot load map"); return;} + points = JSON.parse(data[1]); + cells = JSON.parse(data[2]); + land = $.grep(cells, function(e) {return (e.height >= 0.2);}); + cells.map(function(e) {newPoints.push(e.data);}); + calculateVoronoi(newPoints); + manors = JSON.parse(data[3]); + document.body.insertAdjacentHTML("afterbegin", data[4]); + + // redefine variables + customization = 0, elSelected = ""; + svg = d3.select("svg").call(zoom); + mapWidth = +svg.attr("width"); + mapHeight = +svg.attr("height"); + defs = svg.select("#deftemp"); + viewbox = svg.select("#viewbox").on("touchmove mousemove", moved).on("click", clicked); + ocean = viewbox.select("#ocean"); + oceanLayers = ocean.select("#oceanLayers"); + oceanPattern = ocean.select("#oceanPattern"); + landmass = viewbox.select("#landmass"); + terrs = viewbox.select("#terrs"); + cults = viewbox.select("#cults"); + routes = viewbox.select("#routes"); + roads = routes.select("#roads"); + trails = routes.select("#trails"); + rivers = viewbox.select("#rivers"); + terrain = viewbox.select("#terrain"); + regions = viewbox.select("#regions"); + borders = viewbox.select("#borders"); + stateBorders = borders.select("#stateBorders"); + neutralBorders = borders.select("#neutralBorders"); + coastline = viewbox.select("#coastline"); + lakes = viewbox.select("#lakes"); + grid = viewbox.select("#grid"); + searoutes = routes.select("#searoutes"); + labels = viewbox.select("#labels"); + icons = viewbox.select("#icons"); + burgs = icons.select("#burgs"); + debug = viewbox.select("#debug"); + capitals = labels.select("#capitals"); + towns = labels.select("#towns"); + countries = labels.select("#countries"); + // restore events + labels.selectAll("text").on("click", editLabel); + burgs.selectAll("circle").call(d3.drag().on("drag", dragged).on("end", dragended)).on("click", changeBurg); + // restore layers state + if (cults.selectAll("path").size() == 0) {$("#toggleCultures").addClass("buttonoff");} else {$("#toggleCultures").removeClass("buttonoff");} + if (terrs.selectAll("path").size() == 0) {$("#toggleHeight").addClass("buttonoff");} else {$("#toggleHeight").removeClass("buttonoff");} + if (regions.attr("display") === "none") {$("#toggleCountries").addClass("buttonoff");} else {$("#toggleCountries").removeClass("buttonoff");} + if (rivers.attr("display") === "none") {$("#toggleRivers").addClass("buttonoff");} else {$("#toggleRivers").removeClass("buttonoff");} + if (oceanPattern.attr("display") === "none") {$("#toggleOcean").addClass("buttonoff");} else {$("#toggleOcean").removeClass("buttonoff");} + if (landmass.attr("display") === "none") {$("#landmass").addClass("buttonoff");} else {$("#landmass").removeClass("buttonoff");} + if (terrain.attr("display") === "none") {$("#toggleRelief").addClass("buttonoff");} else {$("#toggleRelief").removeClass("buttonoff");} + if (borders.attr("display") === "none") {$("#toggleBorders").addClass("buttonoff");} else {$("#toggleBorders").removeClass("buttonoff");} + if (burgs.attr("display") === "none") {$("#toggleIcons").addClass("buttonoff");} else {$("#toggleIcons").removeClass("buttonoff");} + if (labels.attr("display") === "none") {$("#toggleLabels").addClass("buttonoff");} else {$("#toggleLabels").removeClass("buttonoff");} + if (routes.attr("display") === "none") {$("#toggleRoutes").addClass("buttonoff");} else {$("#toggleRoutes").removeClass("buttonoff");} + if (grid.attr("display") === "none") {$("#toggleGrid").addClass("buttonoff");} else {$("#toggleGrid").removeClass("buttonoff");} + console.timeEnd("loadMap"); + }; + fileReader.readAsText(fileToLoad, "UTF-8"); + }); + + // Poisson-disc sampling for a points + // Source: bl.ocks.org/mbostock/99049112373e12709381; Based on https://www.jasondavies.com/poisson-disc + function poissonDiscSampler(width, height, radius) { + var k = 5, // maximum number of points 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() { + if (!$("#labelEditor").is(":visible")) { + switch(d3.event.keyCode) { + case 27: // Escape + 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; + } + } + }); + + // Toggle Options pane + $("#optionsTrigger").on("click", function() { + if ($("#options").css("display") === "none") { + $("#regenerate").hide(); + $("#options").fadeIn(); + $("#layoutTab").click(); + this.innerHTML = "◀"; + } else { + $("#options").fadeOut(); + this.innerHTML = "▶"; + } + }); + $("#collapsible").hover(function() { + if ($("#options").css("display") === "none") {$("#regenerate").show();} + }, function() { + $("#regenerate").hide(); + }); + + // move layers on mapLayers dragging (jquery sortable) + function moveLayer(event, ui) { + var el = getLayer(ui.item.attr("id")); + if (el) { + var prev = getLayer(ui.item.prev().attr("id")); + var next = getLayer(ui.item.next().attr("id")); + if (prev) {el.insertAfter(prev);} else if (next) {el.insertBefore(next);} + } + } + + // define connection between option layer buttons and actual svg groups + function getLayer(id) { + if (id === "toggleHeight") {return $("#terrs");} + if (id === "toggleCultures") {return $("#cults");} + if (id === "toggleRivers") {return $("#rivers");} + if (id === "toggleRelief") {return $("#terrain");} + if (id === "toggleBorders") {return $("#borders");} + if (id === "toggleCountries") {return $("#regions");} + if (id === "toggleIcons") {return $("#icons");} + if (id === "toggleLabels") {return $("#labels");} + if (id === "toggleRoutes") {return $("#routes");} + if (id === "toggleGrid") {return $("#grid");} + } + + // UI Button handlers + $("button, a, li").on("click", function() { + var id = this.id; + var parent = this.parentNode.id; + if (icons.selectAll(".tag").size() > 0) {icons.selectAll(".tag, .line").remove();} + if (id === "toggleHeight") {toggleHeight();} + if (id === "toggleCountries") { + var countries = !$("#toggleCountries").hasClass("buttonoff"); + var cultures = !$("#toggleCultures").hasClass("buttonoff"); + if (!countries && cultures) { + $("#toggleCultures").toggleClass("buttonoff"); + toggleCultures(); + } + $('#regions').fadeToggle(); + } + if (id === "toggleCultures") { + var countries = !$("#toggleCountries").hasClass("buttonoff"); + var cultures = !$("#toggleCultures").hasClass("buttonoff"); + if (!cultures && countries) { + $("#toggleCountries").toggleClass("buttonoff"); + $('#regions').fadeToggle(); + } + toggleCultures(); + } + if (id === "toggleFlux") {toggleFlux();} + if (parent === "mapLayers" || parent === "styleContent") {$(this).toggleClass("buttonoff");} + if (id === "randomMap" || id === "regenerate") { + exitCustomization(); + undraw(); + generate(); + } + if (id === "fromScratch") { + undraw(); + placePoints(); + calculateVoronoi(points); + detectNeighbors("grid"); + customizeHeightmap(); + } + if (id === "fromHeightmap") { + var heights = []; + for (var i = 0; i < points.length; i++) { + var cell = diagram.find(points[i][0], points[i][1]).index; + heights.push(cells[cell].height); + } + undraw(); + calculateVoronoi(points); + detectNeighbors("grid"); + for (var i = 0; i < points.length; i++) { + cells[i].height = heights[i]; + } + mockHeightmap(); + customizeHeightmap(); + } + // heightmap customization buttons + if (customization === 1) { + if (id === "rescale") { + $("#heightmapRescaler").dialog({ + title: "Rescale Heightmap", + minHeight: 30, width: "auto", maxWidth: 260, resizable: false, + position: {my: "right top", at: "right-10 top+10", of: "svg"}}); + + } + if (id === "rescaleMultiply") { + var modifier = rescaleModifier.value; + var subject = rescaleSubject.value; + modifyHeights(subject, 0, modifier); + mockHeightmap(); + } + if (id === "rescaleAdd") { + var modifier = rescaleModifier.value; + var subject = rescaleSubject.value; + modifyHeights(subject, +modifier, 1); + mockHeightmap(); + } + if (id === "smoothHeights") {smoothHeights(); mockHeightmap();} + if (id === "getMap") {getMap();} + if (id === "applyTemplate") { + $("#templateEditor").dialog({ + title: "Template Editor", + minHeight: 50, width: 260, resizable: false, + position: {my: "right top", at: "right-10 top+10", of: "svg"} + }); + } + if (id === "convertImage") {convertImage();} + if (id === "convertImageGrid") {$("#grid").fadeToggle();} + if (id === "convertImageHeights") {$("#landmass").fadeToggle();} + } + if ($(this).hasClass('radio')) { + if ($(this).hasClass('pressed')) { + $(".pressed").removeClass('pressed'); + viewbox.style("cursor", "default").on(".drag", null); + } else { + $(".pressed").removeClass('pressed'); + $(this).addClass('pressed'); + viewbox.style("cursor", "crosshair"); + if (id.slice(0,5) === "brush" && id !== "brushRange" && id !== "brushTrough") { + viewbox.call(drag); + } else { + viewbox.on(".drag", null); + } + } + } + if (id === "saveMap") {saveMap();} + if (id === "loadMap") {fileToLoad.click();} + if (id === "saveSVG") {saveAsImage("svg");} + if (id === "savePNG") {saveAsImage("png");} + if (id === "zoomReset") {svg.transition().duration(1000).call(zoom.transform, d3.zoomIdentity);} + if (id === "zoomPlus") { + scale += 1; + if (scale > 40) {scale = 40;} + zoomUpdate(); + } + if (id === "zoomMinus") { + scale -= 1; + if (scale <= 1) {scale = 1; viewX = 0; viewY = 0;} + zoomUpdate(); + } + if (id === "styleFontPlus" || id === "styleFontMinus") { + var el = viewbox.select("#"+styleElementSelect.value); + var mod = id === "styleFontPlus" ? 1.1 : 0.9; + el.selectAll("g").each(function() { + var el = d3.select(this); + var size = Math.trunc(+el.attr("font-size") * mod * 100) / 100; + if (size < 0.2) {size = 0.2;} + el.attr("font-size", size); + }); + return; + } + if (id === "styleFillPlus" || id === "styleFillMinus") { + var el = viewbox.select("#"+styleElementSelect.value); + var mod = id === "styleFillPlus" ? 1.1 : 0.9; + el.selectAll("*").each(function() { + var el = d3.select(this); + var size = Math.trunc(+el.attr("r") * mod * 100) / 100; + if (size < 0.1) {size = 0.1;} + if (el.node().nodeName === "circle") {el.attr("r", size);} + }); + return; + } + if (id === "styleStrokePlus" || id === "styleStrokeMinus") { + var el = viewbox.select("#"+styleElementSelect.value); + var mod = id === "styleStrokePlus" ? 1.1 : 0.9; + el.selectAll("*").each(function() { + var el = d3.select(this); + var size = Math.trunc(+el.attr("stroke-width") * mod * 100) / 100; + if (size < 0.1) {size = 0.1;} + if (el.node().nodeName === "circle") {el.attr("stroke-width", size);} + }); + return; + } + if (id === "templateClear") { + if (customization === 1) { + $("#customizationMenu").fadeIn("slow"); + viewbox.style("cursor", "crosshair").call(drag); + landmassCounter.innerHTML = "0"; + $("#landmass").empty(); + cells.map(function(i) {i.height = 0;}); + } else { + start.click(); + } + } + if (id === "templateComplete") { + if (customization === 1 && !$("#getMap").attr("disabled")) {getMap();} + } + if (id === "convertColorsMinus") { + var current = +convertColors.value - 1; + if (current < 4) {current = 3;} + convertColors.value = current; + heightsFromImage(current); + } + if (id === "convertColorsPlus") { + var current = +convertColors.value + 1; + if (current > 255) {current = 256;} + convertColors.value = current; + heightsFromImage(current); + } + if (id === "convertOverlayButton") { + $("#convertImageButtons").children().not(this).not("#imageToLoad, #convertColors").toggle(); + } + if (id === "convertAutoLum") {autoAssing("lum");} + if (id === "convertAutoHue") {autoAssing("hue");} + if (id === "convertComplete") {completeConvertion();} + }); + + // templateEditor Button handlers + $("#templateTools > button").on("click", function() { + var id = this.id; + id = id.replace("template", ""); + if (id === "Mountain") { + var steps = $("#templateBody > div").length; + if (steps > 0) {return;} + } + $("#templateBody").attr("data-changed", 1); + $("#templateBody").append('
    ' + id + '
    '); + var el = $("#templateBody div:last-child"); + if (id === "Hill" || id === "Pit" || id === "Range" || id === "Trough") { + var count = ''; + } + if (id === "Hill") { + var dist = ''; + } + if (id === "Add" || id === "Multiply") { + var dist = ''; + } + if (id === "Add") { + var count = ''; + } + if (id === "Multiply") { + var count = ''; + } + if (id === "Strait") { + var count = ''; + } + el.append(''); + $(".icon-trash-empty").on("click", function() {$(this).parent().remove();}); + if (dist) {el.append(dist);} + if (count) {el.append(count);} + el.find("select.templateElDist").on("input", fireTemplateElDist); + $("#templateBody").attr("data-changed", 1); + }); + + // fireTemplateElDist selector handlers + function fireTemplateElDist() { + if (this.value === "interval") { + var interval = prompt("Populate a height interval (e.g. from 0.17 to 0.2), without space, but with hyphen", "0.17-0.2"); + if (interval) { + var option = ''; + $(this).append(option).val(interval); + } + } + } + + // templateSelect on change listener + $("#templateSelect").on("input", function() { + var steps = $("#templateBody > div").length; + var changed = +$("#templateBody").attr("data-changed"); + var message = "Are you sure you want to change the base tamplate? All the changes will be lost." + if (steps && changed === 1) {var proceed = confirm(message);} + if (steps === 0 || proceed === true || changed === 0) { + $("#templateBody").empty(); + var template = this.value; + $("#templateSelect").attr("data-prev", template); + addStep("Mountain"); + if (template === "templateVolcano") { + addStep("Add", 0.07); + addStep("Multiply", 1.1); + addStep("Hill", 5, 0.4); + addStep("Hill", 2, 0.15); + } + if (template === "templateHighIsland") { + addStep("Add", 0.08); + addStep("Multiply", 0.9); + addStep("Range", 4); + addStep("Hill", 12, 0.25); + addStep("Trough", 3); + addStep("Multiply", 0.75, "land"); + addStep("Hill", 3, 0.15); + } + if (template === "templateLowIsland") { + addStep("Add", 0.05); + addStep("Smooth"); + addStep("Hill", 4, 0.4); + addStep("Hill", 12, 0.2); + addStep("Trough", 3); + addStep("Multiply", 0.3, "land"); + } + if (template === "templateContinents") { + addStep("Hill", 24, 0.25); + addStep("Range", 2); + addStep("Hill", 3, 0.1); + addStep("Multiply", 0.7, "land"); + addStep("Strait", "2-8"); + addStep("Smooth"); + addStep("Pit", 5); + addStep("Trough", 3); + addStep("Multiply", 0.8, "land"); + addStep("Add", 0.02, "all"); + } + if (template === "templateArchipelago") { + addStep("Add", -0.2, "land"); + addStep("Hill", 15, 0.15); + addStep("Trough", 2); + addStep("Pit", 8); + addStep("Add", -0.05, "land"); + addStep("Multiply", 0.9, "land"); + } + if (template === "templateAtoll") { + addStep("Hill", 2, 0.35); + addStep("Range", 1); + addStep("Add", 0.07, "all"); + addStep("Smooth"); + addStep("Multiply", 0.1, "0.27-10"); + } + $("#templateBody").attr("data-changed", 0); + } else { + var prev = $("#templateSelect").attr("data-prev"); + $("#templateSelect").val(prev); + } + }); + + // interprete template function + function addStep(feature, count, dist) { + if (!feature) {return;} + if (feature === "Mountain") {templateMountain.click();} + if (feature === "Hill") {templateHill.click();} + if (feature === "Pit") {templatePit.click();} + if (feature === "Range") {templateRange.click();} + if (feature === "Trough") {templateTrough.click();} + if (feature === "Strait") {templateStrait.click();} + if (feature === "Add") {templateAdd.click();} + if (feature === "Multiply") {templateMultiply.click();} + if (feature === "Smooth") {templateSmooth.click();} + if (count) {$("#templateBody div:last-child .templateElCount").val(count);} + if (dist) { + if (dist !== "land") { + var option = ''; + $("#templateBody div:last-child .templateElDist").append(option); + } + $("#templateBody div:last-child .templateElDist").val(dist); + } + } + + // Execute custom template + $("#templateRun").on("click", function() { + if (customization !== 1) {return;} + var steps = $("#templateBody > div").length; + if (steps) {cells.map(function(i) {i.height = 0;});} + for (var step=1; step <= steps; step++) { + var element = $("#templateBody div:nth-child(" + step + ")"); + var type = element.attr("data-type"); + if (type === "Mountain") {addMountain(); continue;} + var count = $("#templateBody div:nth-child(" + step + ") .templateElCount").val(); + var dist = $("#templateBody div:nth-child(" + step + ") .templateElDist").val(); + if (count) { + if (count[0] !== "-" && count.includes("-")) { + var lim = count.split("-"); + count = Math.floor(Math.random() * (+lim[1] - +lim[0] + 1) + +lim[0]); + } else { + count = +count; // parse string + } + } + if (type === "Hill") {addHill(count, +dist);} + if (type === "Pit") {addPit(count);} + if (type === "Range") {addRange(count);} + if (type === "Trough") {addRange(-1 * count);} + if (type === "Strait") {addStrait(count);} + if (type === "Add") {modifyHeights(dist, count, 1);} + if (type === "Multiply") {modifyHeights(dist, 0, count);} + if (type === "Smooth") {smoothHeights();} + } + if (steps) {mockHeightmap();} + }); + + // Save custom template as text file + $("#templateSave").on("click", function() { + var steps = $("#templateBody > div").length; + var stepsData = ""; + for (var step=1; step <= steps; step++) { + var element = $("#templateBody div:nth-child(" + step + ")"); + var type = element.attr("data-type"); + var count = $("#templateBody div:nth-child(" + step + ") .templateElCount").val(); + var dist = $("#templateBody div:nth-child(" + step + ") .templateElDist").val(); + if (!count) {count = "0";} + if (!dist) {dist = "0";} + stepsData += type + " " + count + " " + dist + "\r\n"; + } + var dataBlob = new Blob([stepsData], {type:"text/plain"}); + var url = window.URL.createObjectURL(dataBlob); + var link = document.createElement("a"); + link.download = "template_" + Date.now() + ".txt"; + link.href = url; + link.click(); + $("#templateBody").attr("data-changed", 0); + }); + + // Load custom template as text file + $("#templateLoad").on("click", function() {templateToLoad.click();}); + $("#templateToLoad").change(function() { + var fileToLoad = this.files[0]; + this.value = ""; + var fileReader = new FileReader(); + fileReader.onload = function(fileLoadedEvent) { + var dataLoaded = fileLoadedEvent.target.result; + var data = dataLoaded.split("\r\n"); + $("#templateBody").empty(); + if (data.length > 0) { + $("#templateBody").attr("data-changed", 1); + $("#templateSelect").attr("data-prev", "templateCustom").val("templateCustom"); + } + for (var i=0; i < data.length; i++) { + var line = data[i].split(" "); + addStep(line[0], line[1], line[2]); + } + }; + fileReader.readAsText(fileToLoad, "UTF-8"); + }); + + // Image to Heightmap Converter dialog + function convertImage() { + $(".pressed").removeClass('pressed'); + viewbox.style("cursor", "default").on(".drag", null); + var div = d3.select("#colorScheme"); + if (div.selectAll("*").size() === 0) { + for (var i = 0; i <= 100; i++) { + var width = i < 20 || i > 70 ? "1px" : "3px"; + if (i === 0) {width = "4px";} + var clr = color(1-i/100); + var style = "background-color: " + clr + "; width: " + width; + div.append("div").attr("data-color", i/100).attr("style", style); + } + div.selectAll("*").on("touchmove mousemove", showHeight).on("click", assignHeight); + } + $("#imageConverter").dialog({ + title: "Image to Heightmap Converter", + minHeight: 30, width: 260, resizable: false, + position: {my: "right top", at: "right-10 top+10", of: "svg"}}) + .on('dialogclose', function() {completeConvertion();}); + } + + // Load image to convert + $("#convertImageLoad").on("click", function() {imageToLoad.click();}); + $("#imageToLoad").change(function() { + console.time("loadImage"); + // reset style + viewbox.attr("transform", null); + grid.attr("display", "block").attr("stroke-width", .3); + // load image + var file = this.files[0]; + this.value = ""; // reset input value to get triggered if the same file is uploaded + var reader = new FileReader(); + var img = new Image; + // draw image + img.onload = function() { + ctx.drawImage(img, 0, 0, mapWidth, mapHeight); + heightsFromImage(+convertColors.value); + console.timeEnd("loadImage"); + } + reader.onloadend = function() {img.src = reader.result;} + reader.readAsDataURL(file); + }); + + function heightsFromImage(count) { + var imageData = ctx.getImageData(0, 0, mapWidth, mapHeight); + var data = imageData.data; + $("#landmass > path, .color-div").remove(); + $("#landmass, #colorsUnassigned").fadeIn(); + $("#colorsAssigned").fadeOut(); + var colors = [], palette = []; + points.map(function(i) { + var x = i[0], y = i[1]; + if (y == mapHeight) {y--;} + if (x == mapWidth) {x--;} + var p = (x + y * mapWidth) * 4; + var r = data[p], g = data[p + 1], b = data[p + 2]; + colors.push([r, g, b]); + }); + var cmap = MMCQ.quantize(colors, count); + polygons.map(function(i, d) { + cells[d].height = undefined; + var nearest = cmap.nearest(colors[d]); + var rgb = "rgb(" + nearest[0] + ", " + nearest[1] + ", " + nearest[2] + ")"; + var hex = toHEX(rgb); + if (palette.indexOf(hex) === -1) {palette.push(hex);} + landmass.append("path").attr("d", "M" + i.join("L") + "Z").attr("data-i", d).attr("fill", hex).attr("stroke", hex); + }); + landmass.selectAll("path").on("click", landmassClicked); + palette.sort(function(a, b) {return d3.lab(a).b - d3.lab(b).b;}).map(function(i) { + $("#colorsUnassigned").append('
    '); + }); + $(".color-div").click(selectColor); + } + + function landmassClicked() { + var color = d3.select(this).attr("fill"); + $("#"+color.slice(1)).click(); + } + + function selectColor() { + landmass.selectAll(".selectedCell").classed("selectedCell", 0); + var el = d3.select(this); + if (el.classed("selectedColor")) { + el.classed("selectedColor", 0); + } else { + $(".selectedColor").removeClass("selectedColor"); + el.classed("selectedColor", 1); + $("#colorScheme .hoveredColor").removeClass("hoveredColor"); + $("#colorsSelectValue").text(0); + if (el.attr("data-height")) { + var height = el.attr("data-height"); + $("#colorScheme div[data-color='" + height + "']").addClass("hoveredColor"); + $("#colorsSelectValue").text(Math.round(height * 100)); + } + var color = "#" + d3.select(this).attr("id"); + landmass.selectAll("path").classed("selectedCell", 0); + landmass.selectAll("path[fill='" + color + "']").classed("selectedCell", 1); + } + } + + function showHeight() { + var el = d3.select(this); + var height = Math.round(el.attr("data-color") * 100); + $("#colorsSelectValue").text(height); + $("#colorScheme .hoveredColor").removeClass("hoveredColor"); + el.classed("hoveredColor", 1); + } + + function assignHeight() { + var sel = $(".selectedColor")[0]; + var height = +d3.select(this).attr("data-color"); + var rgb = color(1-height); + var hex = toHEX(rgb); + sel.style.backgroundColor = rgb; + sel.setAttribute("data-height", height); + var cur = "#" + sel.id; + sel.id = hex.substr(1); + landmass.selectAll(".selectedCell").each(function() { + d3.select(this).attr("fill", hex).attr("stroke", hex); + var i = +d3.select(this).attr("data-i"); + cells[i].height = height; + }); + var parent = sel.parentNode; + if (parent.id === "colorsUnassigned") { + colorsAssigned.appendChild(sel); + $("#colorsAssigned").fadeIn(); + if ($("#colorsUnassigned .color-div").length < 1) {$("#colorsUnassigned").fadeOut();} + } + if ($("#colorsAssigned .color-div").length > 1) {sortAssignedColors();} + } + + // sort colors based on assigned height + function sortAssignedColors() { + var data = []; + var colors = d3.select("#colorsAssigned").selectAll(".color-div"); + colors.each(function(d) { + var id = d3.select(this).attr("id"); + var height = +d3.select(this).attr("data-height"); + data.push({id, height}); + }); + data.sort(function(a, b) {return a.height - b.height}).map(function(i) { + $("#colorsAssigned").append($("#"+i.id)); + }); + } + + // auto assign color based on luminosity or hue + function autoAssing(type) { + var imageData = ctx.getImageData(0, 0, mapWidth, mapHeight); + var data = imageData.data; + $("#landmass > path, .color-div").remove(); + $("#colorsAssigned").fadeIn(); + $("#colorsUnassigned").fadeOut(); + var heights = []; + polygons.map(function(i, d) { + var x = i.data[0], y = i.data[1]; + if (y == mapHeight) {y--;} + var p = (x + y * mapWidth) * 4; + var r = data[p], g = data[p + 1], b = data[p + 2]; + var lab = d3.lab("rgb(" + r + ", " + g + ", " + b + ")"); + if (type === "hue") { + var normalized = Math.trunc(normalize(lab.b + lab.a / 2, -50, 200) * 100) / 100; + } else { + var normalized = Math.trunc(normalize(lab.l, 0, 100) * 100) / 100; + } + heights.push(normalized); + var rgb = color(1 - normalized); + var hex = toHEX(rgb); + cells[d].height = normalized; + landmass.append("path").attr("d", "M" + i.join("L") + "Z").attr("data-i", d).attr("fill", hex).attr("stroke", hex); + }); + heights.sort(function(a, b) {return a - b;}); + var unique = [...new Set(heights)]; + unique.map(function(i) { + var rgb = color(1 - i); + var hex = toHEX(rgb); + $("#colorsAssigned").append('
    '); + }); + $(".color-div").click(selectColor); + } + + function normalize(val, min, max) { + var normalized = (val - min) / (max - min); + if (normalized < 0) {normalized = 0;} + if (normalized > 1) {normalized = 1;} + return normalized; + } + + function completeConvertion() { + mockHeightmap(); + canvas.style.opacity = convertOverlay.value = convertOverlayValue.innerHTML = 0; + $("#imageConverter").dialog('close'); + } + + // Clear the map + function undraw() { + svg.selectAll("path, circle, text").remove(); + cells = [], land = [], riversData = [], island = 0, manors = [], queue = []; + } + + // Enter Heightmap Customization mode + function customizeHeightmap() { + customization = 1; + svg.transition().duration(1000).call(zoom.transform, d3.zoomIdentity); + $("#customizationMenu").fadeIn("slow"); + viewbox.style("cursor", "crosshair").call(drag); + landmassCounter.innerHTML = "0"; + $('#grid').fadeIn(); + $('#toggleGrid').removeClass("buttonoff"); + if ($("#labelEditor").is(":visible")) {$("#labelEditor").dialog('close');} + if ($("#riverEditor").is(":visible")) {$("#riverEditor").dialog('close');} + } + + // Remove all customization related styles, reset values + function exitCustomization() { + customization = 0; + canvas.style.opacity = 0; + $("#customizationMenu").fadeOut("slow"); + $("#getMap").attr("disabled", true).addClass("buttonoff"); + $('#grid').empty().fadeOut(); + $('#toggleGrid').addClass("buttonoff"); + viewbox.style("cursor", "default").on(".drag", null); + if (!$("#toggleHeight").hasClass("buttonoff")) {toggleHeight();} + if ($("#imageConverter").is(":visible")) {$("#imageConverter").dialog('close');} + if ($("#templateEditor").is(":visible")) {$("#templateEditor").dialog('close');} + } + + // Options handlers + $("input, select").on("input change", function() { + var id = this.id; + if (id === "styleElementSelect") { + var sel = this.value; + var el = viewbox.select("#"+sel); + $("#styleInputs div").hide(); + if (sel === "rivers" || sel === "oceanBase" || sel === "lakes" || sel === "landmass" || sel === "burgs") { + $("#styleFill").css("display", "inline-block"); + styleFillInput.value = styleFillOutput.value = el.attr("fill"); + } + if (sel === "roads" || sel === "trails" || sel === "searoutes" || sel === "lakes" || sel === "stateBorders" || sel === "neutralBorders" || sel === "grid" || sel === "coastline") { + $("#styleStroke").css("display", "inline-block"); + styleStrokeInput.value = styleStrokeOutput.value = el.attr("stroke"); + $("#styleStrokeWidth").css("display", "block"); + var width = el.attr("stroke-width") || ""; + styleStrokeWidthInput.value = styleStrokeWidthOutput.value = width; + } + if (sel === "roads" || sel === "trails" || sel === "searoutes" || sel === "stateBorders" || sel === "neutralBorders") { + $("#styleStrokeDasharray, #styleStrokeLinecap").css("display", "block"); + styleStrokeDasharrayInput.value = el.attr("stroke-dasharray") || ""; + styleStrokeLinecapInput.value = el.attr("stroke-linecap") || "inherit"; + } + if (sel === "regions") { + $("#styleMultiple").css("display", "inline-block"); + $("#styleMultiple input").remove(); + for (var r = 0; r < capitalsCount; r++) { + var color = regions.select(".region"+r).attr("fill"); + $("#styleMultiple").append(''); + } + $("#styleMultiple input").on("input", function() { + var id = this.id; + var r = +id.replace("regionColor", ""); + regions.selectAll(".region"+r).attr("fill", this.value); + }); + } + if (sel === "terrs") {$("#styleScheme").css("display", "block");} + if (sel === "heightmap") {$("#styleScheme").css("display", "block");} + if (sel === "cults") { + $("#styleMultiple").css("display", "inline-block"); + $("#styleMultiple input").remove(); + var colors = []; + cults.selectAll("path").each(function(d) { + var fill = d3.select(this).attr("fill"); + if (colors.indexOf(fill) === -1) {colors.push(fill);} + }); + for (var c = 0; c < colors.length; c++) { + $("#styleMultiple").append(''); + } + $("#styleMultiple input").on("input", function() { + var oldColor = "#" + d3.select(this).attr("id"); + var newColor = this.value; + cults.selectAll("path").each(function() { + var fill = d3.select(this).attr("fill"); + if (oldColor === fill) {d3.select(this).attr("fill", newColor).attr("stroke", newColor);} + }); + $(this).attr("id", newColor.substr(1)); + }); + } + if (sel === "labels") { + $("#styleFill, #styleFontSize").css("display", "inline-block"); + styleFillInput.value = styleFillOutput.value = el.select("g").attr("fill"); + } + if (sel === "burgs") { + $("#styleSize").css("display", "block"); + $("#styleStroke").css("display", "inline-block"); + styleStrokeInput.value = styleStrokeOutput.value = el.attr("stroke"); + } + // opacity + $("#styleOpacity, #styleFilter").css("display", "block"); + var opacity = el.attr("opacity") || 1; + styleOpacityInput.value = styleOpacityOutput.value = opacity; + // filter + if (sel == "oceanBase") {el = oceanLayers;} + styleFilterInput.value = el.attr("filter") || ""; + return; + } + if (id === "styleFillInput") { + styleFillOutput.value = this.value; + var el = viewbox.select("#"+styleElementSelect.value); + if (styleElementSelect.value !== "labels") { + el.attr('fill', this.value); + } else { + el.selectAll("g").attr('fill', this.value); + } + return; + } + if (id === "styleStrokeInput") { + styleStrokeOutput.value = this.value; + var el = viewbox.select("#"+styleElementSelect.value); + el.attr('stroke', this.value); + return; + } + if (id === "styleStrokeWidthInput") { + styleStrokeWidthOutput.value = this.value; + var sel = styleElementSelect.value; + viewbox.select("#"+sel).attr('stroke-width', +this.value); + return; + } + if (id === "styleStrokeDasharrayInput") { + var sel = styleElementSelect.value; + viewbox.select("#"+sel).attr('stroke-dasharray', this.value); + return; + } + if (id === "styleStrokeLinecapInput") { + var sel = styleElementSelect.value; + viewbox.select("#"+sel).attr('stroke-linecap', this.value); + return; + } + if (id === "styleOpacityInput") { + styleOpacityOutput.value = this.value; + var sel = styleElementSelect.value; + viewbox.select("#"+sel).attr('opacity', this.value); + return; + } + if (id === "styleFilterInput") { + var sel = styleElementSelect.value; + if (sel == "oceanBase") {sel = "oceanLayers";} + var el = viewbox.select("#"+sel); + el.attr('filter', this.value); + return; + } + if (id === "styleSchemeInput") { + terrs.selectAll("path").remove(); + toggleHeight(); + return; + } + if (id === "sizeInput") {graphSize = sizeOutput.value = this.value;} + if (id === "manorsInput") {manorsCount = manorsOutput.value = this.value;} + if (id === "regionsInput") { + capitalsCount = regionsOutput.value = this.value; + var size = Math.round(6 - capitalsCount / 20); + if (size < 3) {size = 3;} + capitals.attr("font-size", size); + size = Math.round(18 - capitalsCount / 6); + if (size < 4) {size = 4;} + countries.attr("font-size", size); + } + if (id === "powerInput") {power = powerOutput.value = this.value;} + if (id === "neutralInput") { + neutral = neutralOutput.value = this.value; + if (this.value === "100") {neutral = "500";} + } + if (id === "swampinessInput") {swampiness = swampinessOutput.value = this.value;} + if (id === "sharpnessInput") {sharpness = sharpnessOutput.value = this.value;} + if (id === "brushPower") {brushPowerOutput.value = this.value;} + if (id === "convertOverlay") {canvas.style.opacity = convertOverlayValue.innerHTML = +this.value;} + }); + + $("#layoutPreset").on("change", function() { + var preset = this.value; + $("#mapLayers li").not("#toggleOcean, #toggleLandmass").addClass("buttonoff"); + $("#toggleOcean, #toggleLandmass").removeClass("buttonoff"); + $("#oceanPattern, #landmass").fadeIn(); + $("#rivers, #terrain, #borders, #regions, #burgs, #labels, #routes, #grid").fadeOut(); + cults.selectAll("path").remove(); + terrs.selectAll("path").remove(); + if (preset === "layoutPolitical") { + toggleRivers.click(); + toggleRelief.click(); + toggleBorders.click(); + toggleCountries.click(); + toggleIcons.click(); + toggleLabels.click(); + toggleRoutes.click(); + } + if (preset === "layoutCultural") { + toggleRivers.click(); + toggleRelief.click(); + toggleBorders.click(); + $("#toggleCultures").click(); + toggleIcons.click(); + toggleLabels.click(); + } + if (preset === "layoutEconomical") { + toggleRivers.click(); + toggleRelief.click(); + toggleBorders.click(); + toggleIcons.click(); + toggleLabels.click(); + toggleRoutes.click(); + } + if (preset === "layoutHeightmap") { + $("#toggleHeight").click(); + toggleRivers.click(); + } + }); + + // UI Button handlers + $(".tab > button").on("click", function() { + $(".tabcontent").hide(); + $(".tab > button").removeClass("active"); + $(this).addClass("active"); + var id = this.id; + if (id === "layoutTab") {$("#layoutContent").show();} + if (id === "styleTab") {$("#styleContent").show();} + if (id === "optionsTab") {$("#optionsContent").show();} + if (id === "customizeTab") {$("#customizeContent").show();} + }); +} \ No newline at end of file diff --git a/src/index.css b/WIP version/index.css similarity index 99% rename from src/index.css rename to WIP version/index.css index 6a2e0884..5e737fd3 100644 --- a/src/index.css +++ b/WIP version/index.css @@ -16,6 +16,7 @@ canvas { .base { stroke: none; + fill: #5167a9; } .ocean { diff --git a/src/index.html b/WIP version/index.html similarity index 100% rename from src/index.html rename to WIP version/index.html diff --git a/src/script.js b/WIP version/script.js similarity index 99% rename from src/script.js rename to WIP version/script.js index 5d6ea7ee..cd6ec22b 100644 --- a/src/script.js +++ b/WIP version/script.js @@ -13,8 +13,6 @@ function fantasyMapGenerator() { 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"), - base = ocean.append("rect").attr("x", -180).attr("y", -125).attr("width", mapWidth+200).attr("height", mapHeight+200).attr("fill", "#5167a9").attr("class", "base"), - mottling = container.append("rect").attr("x", -180).attr("y", -125).attr("width", mapWidth+200).attr("height", mapHeight+200).attr("class", "mottling"), rose = container.append("use").attr("xlink:href","#rose"), islandBack = container.append("g").attr("class", "islandBack"), hCells = container.append("g").attr("class", "hCells"), @@ -32,6 +30,9 @@ function fantasyMapGenerator() { 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'); @@ -859,9 +860,7 @@ function fantasyMapGenerator() { function clicked() { if (journeyStep == 1) { var point = d3.mouse(this), - x = Math.floor(point[0]), - y = Math.floor(point[1]), - cell = diagram.find(x, y).index, + cell = diagram.find(point[0], point[1]).index, status = map_mode.getAttribute("status"); if (status == 1) { var power = +$("#change_power").text(); diff --git a/src/vec2.js b/WIP version/vec2.js similarity index 100% rename from src/vec2.js rename to WIP version/vec2.js