mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-17 01:41:22 +01:00
Merge branch 'master' of https://github.com/Azgaar/Fantasy-Map-Generator into dev-economics
This commit is contained in:
commit
1180a3c67b
41 changed files with 5185 additions and 3469 deletions
837
modules/ui/3d.js
837
modules/ui/3d.js
File diff suppressed because one or more lines are too long
|
|
@ -14,14 +14,14 @@ function restoreDefaultEvents() {
|
|||
function clicked() {
|
||||
const el = d3.event.target;
|
||||
if (!el || !el.parentElement || !el.parentElement.parentElement) return;
|
||||
const parent = el.parentElement,
|
||||
grand = parent.parentElement,
|
||||
great = grand.parentElement;
|
||||
const parent = el.parentElement;
|
||||
const grand = parent.parentElement;
|
||||
const great = grand.parentElement;
|
||||
const p = d3.mouse(this);
|
||||
const i = findCell(p[0], p[1]);
|
||||
|
||||
if (grand.id === 'emblems') editEmblem();
|
||||
else if (parent.id === 'rivers') editRiver();
|
||||
else if (parent.id === 'rivers') editRiver(el.id);
|
||||
else if (grand.id === 'routes') editRoute();
|
||||
else if (el.tagName === 'tspan' && grand.parentNode.parentNode.id === 'labels') editLabel();
|
||||
else if (grand.id === 'burgLabels') editBurg();
|
||||
|
|
@ -118,33 +118,6 @@ function applySorting(headers) {
|
|||
.forEach((line) => list.appendChild(line));
|
||||
}
|
||||
|
||||
function confirmationDialog(options) {
|
||||
const {
|
||||
title = 'Confirm action',
|
||||
message = 'Are you sure you want to continue? <br>The action cannot be reverted',
|
||||
cancel = 'Cancel',
|
||||
confirm = 'Continue',
|
||||
onCancel = () => {},
|
||||
onConfirm = () => {}
|
||||
} = options;
|
||||
|
||||
alertMessage.innerHTML = message;
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
title,
|
||||
buttons: {
|
||||
[confirm]: function () {
|
||||
onConfirm();
|
||||
$(this).dialog('close');
|
||||
},
|
||||
[cancel]: function () {
|
||||
onCancel();
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function addBurg(point) {
|
||||
const cells = pack.cells;
|
||||
const x = rn(point[0], 2),
|
||||
|
|
@ -405,15 +378,14 @@ function clearLegend() {
|
|||
}
|
||||
|
||||
// draw color (fill) picker
|
||||
function createPicker(hatching) {
|
||||
const COLORS_IN_ROW = 14;
|
||||
function createPicker() {
|
||||
const pos = () => tip('Drag to change the picker position');
|
||||
const cl = () => tip('Click to close the picker');
|
||||
const closePicker = () => container.remove();
|
||||
const closePicker = () => contaiter.style('display', 'none');
|
||||
|
||||
const container = d3.select('body').append('svg').attr('id', 'pickerContainer').attr('width', '100%').attr('height', '100%');
|
||||
container.append('rect').attr('width', '100%').attr('height', '100%').attr('opacity', 0).on('mousemove', cl).on('click', closePicker);
|
||||
const picker = container
|
||||
const contaiter = d3.select('body').append('svg').attr('id', 'pickerContainer').attr('width', '100%').attr('height', '100%');
|
||||
contaiter.append('rect').attr('x', 0).attr('y', 0).attr('width', '100%').attr('height', '100%').attr('opacity', 0.2).on('mousemove', cl).on('click', closePicker);
|
||||
const picker = contaiter
|
||||
.append('g')
|
||||
.attr('id', 'picker')
|
||||
.call(
|
||||
|
|
@ -469,7 +441,11 @@ function createPicker(hatching) {
|
|||
spaces.selectAll('input').on('change', changePickerSpace);
|
||||
|
||||
const colors = picker.append('g').attr('id', 'pickerColors').attr('stroke', '#333333');
|
||||
const clr = d3.range(COLORS_IN_ROW).map((i) => d3.hsl((i / COLORS_IN_ROW) * 360, 0.7, 0.7).hex());
|
||||
const hatches = picker.append('g').attr('id', 'pickerHatches').attr('stroke', '#333333');
|
||||
const hatching = d3.selectAll('g#hatching > pattern');
|
||||
const number = hatching.size();
|
||||
|
||||
const clr = d3.range(number).map((i) => d3.hsl((i / number) * 360, 0.7, 0.7).hex());
|
||||
clr.forEach(function (d, i) {
|
||||
colors
|
||||
.append('rect')
|
||||
|
|
@ -481,28 +457,26 @@ function createPicker(hatching) {
|
|||
.attr('width', 16)
|
||||
.attr('height', 16);
|
||||
});
|
||||
|
||||
hatching.each(function (d, i) {
|
||||
hatches
|
||||
.append('rect')
|
||||
.attr('id', 'picker_' + this.id)
|
||||
.attr('fill', 'url(#' + this.id + ')')
|
||||
.attr('x', i * 22 + 4)
|
||||
.attr('y', 61)
|
||||
.attr('width', 16)
|
||||
.attr('height', 16);
|
||||
});
|
||||
|
||||
colors
|
||||
.selectAll('rect')
|
||||
.on('click', pickerFillClicked)
|
||||
.on('mousemove', () => tip('Click to fill with the color'));
|
||||
|
||||
if (hatching) {
|
||||
const hatches = picker.append('g').attr('id', 'pickerHatches').attr('stroke', '#333333');
|
||||
d3.selectAll('g#hatching > pattern').each(function (d, i) {
|
||||
hatches
|
||||
.append('rect')
|
||||
.attr('id', 'picker_' + this.id)
|
||||
.attr('fill', 'url(#' + this.id + ')')
|
||||
.attr('x', i * 22 + 4)
|
||||
.attr('y', 61)
|
||||
.attr('width', 16)
|
||||
.attr('height', 16);
|
||||
});
|
||||
hatches
|
||||
.selectAll('rect')
|
||||
.on('click', pickerFillClicked)
|
||||
.on('mousemove', () => tip('Click to fill with the hatching'));
|
||||
}
|
||||
hatches
|
||||
.selectAll('rect')
|
||||
.on('click', pickerFillClicked)
|
||||
.on('mousemove', () => tip('Click to fill with the hatching'));
|
||||
|
||||
// append box
|
||||
const bbox = picker.node().getBBox();
|
||||
|
|
@ -973,6 +947,7 @@ function selectIcon(initial, callback) {
|
|||
|
||||
// Calls the refresh for all currently open editors
|
||||
function refreshAllEditors() {
|
||||
TIME && console.time('refreshAllEditors');
|
||||
if (document.getElementById('culturesEditorRefresh').offsetParent) culturesEditorRefresh.click();
|
||||
if (document.getElementById('biomesEditorRefresh').offsetParent) biomesEditorRefresh.click();
|
||||
if (document.getElementById('diplomacyEditorRefresh').offsetParent) diplomacyEditorRefresh.click();
|
||||
|
|
@ -980,5 +955,5 @@ function refreshAllEditors() {
|
|||
if (document.getElementById('religionsEditorRefresh').offsetParent) religionsEditorRefresh.click();
|
||||
if (document.getElementById('statesEditorRefresh').offsetParent) statesEditorRefresh.click();
|
||||
if (document.getElementById('zonesEditorRefresh').offsetParent) zonesEditorRefresh.click();
|
||||
if (document.getElementById('resourcesEditorRefresh').offsetParent) resourcesEditorRefresh.click();
|
||||
TIME && console.timeEnd('refreshAllEditors');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ document.getElementById('exitCustomization').addEventListener('mousemove', showD
|
|||
/**
|
||||
* @param {string} tip Tooltip text
|
||||
* @param {boolean} main Show above other tooltips
|
||||
* @param {string} type Message type (color): error, warn, success
|
||||
* @param {string} type Message type (color): error / warn / success
|
||||
* @param {number} time Timeout to auto hide, ms
|
||||
*/
|
||||
function tip(tip = 'Tip is undefined', main, type, time) {
|
||||
|
|
@ -96,10 +96,7 @@ function showMapTooltip(point, e, i, g) {
|
|||
const land = pack.cells.h[i] >= 20;
|
||||
|
||||
// specific elements
|
||||
if (group === 'armies') {
|
||||
tip(e.target.parentNode.dataset.name + '. Click to edit');
|
||||
return;
|
||||
}
|
||||
if (group === 'armies') return tip(e.target.parentNode.dataset.name + '. Click to edit');
|
||||
|
||||
if (group === 'emblems' && e.target.tagName === 'use') {
|
||||
const parent = e.target.parentNode;
|
||||
|
|
@ -130,14 +127,11 @@ function showMapTooltip(point, e, i, g) {
|
|||
if (riversOverview.offsetParent) highlightEditorLine(riversOverview, river, 5000);
|
||||
return;
|
||||
}
|
||||
if (group === 'routes') {
|
||||
tip('Click to edit the Route');
|
||||
return;
|
||||
}
|
||||
if (group === 'terrain') {
|
||||
tip('Click to edit the Relief Icon');
|
||||
return;
|
||||
}
|
||||
|
||||
if (group === 'routes') return tip('Click to edit the Route');
|
||||
|
||||
if (group === 'terrain') return tip('Click to edit the Relief Icon');
|
||||
|
||||
if (subgroup === 'burgLabels' || subgroup === 'burgIcons') {
|
||||
const burg = +path[path.length - 10].dataset.id;
|
||||
const b = pack.burgs[burg];
|
||||
|
|
@ -146,50 +140,25 @@ function showMapTooltip(point, e, i, g) {
|
|||
if (burgsOverview.offsetParent) highlightEditorLine(burgsOverview, burg, 5000);
|
||||
return;
|
||||
}
|
||||
if (group === 'labels') {
|
||||
tip('Click to edit the Label');
|
||||
return;
|
||||
}
|
||||
if (group === 'markers') {
|
||||
tip('Click to edit the Marker');
|
||||
return;
|
||||
}
|
||||
if (group === 'labels') return tip('Click to edit the Label');
|
||||
|
||||
if (group === 'markers') return tip('Click to edit the Marker');
|
||||
|
||||
if (group === 'ruler') {
|
||||
const tag = e.target.tagName;
|
||||
const className = e.target.getAttribute('class');
|
||||
if (tag === 'circle' && className === 'edge') {
|
||||
tip('Drag to adjust. Hold Ctrl and drag to add a point. Click to remove the point');
|
||||
return;
|
||||
}
|
||||
if (tag === 'circle' && className === 'control') {
|
||||
tip('Drag to adjust. Hold Shifta and drag to keep axial direction. Click to remove the point');
|
||||
return;
|
||||
}
|
||||
if (tag === 'circle') {
|
||||
tip('Drag to adjust the measurer');
|
||||
return;
|
||||
}
|
||||
if (tag === 'polyline') {
|
||||
tip('Click on drag to add a control point');
|
||||
return;
|
||||
}
|
||||
if (tag === 'path') {
|
||||
tip('Drag to move the measurer');
|
||||
return;
|
||||
}
|
||||
if (tag === 'text') {
|
||||
tip('Drag to move, click to remove the measurer');
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (subgroup === 'burgIcons') {
|
||||
tip('Click to edit the Burg');
|
||||
return;
|
||||
}
|
||||
if (subgroup === 'burgLabels') {
|
||||
tip('Click to edit the Burg');
|
||||
return;
|
||||
if (tag === 'circle' && className === 'edge') return tip('Drag to adjust. Hold Ctrl and drag to add a point. Click to remove the point');
|
||||
if (tag === 'circle' && className === 'control') return tip('Drag to adjust. Hold Shift and drag to keep axial direction. Click to remove the point');
|
||||
if (tag === 'circle') return tip('Drag to adjust the measurer');
|
||||
if (tag === 'polyline') return tip('Click on drag to add a control point');
|
||||
if (tag === 'path') return tip('Drag to move the measurer');
|
||||
if (tag === 'text') return tip('Drag to move, click to remove the measurer');
|
||||
}
|
||||
|
||||
if (subgroup === 'burgIcons') return tip('Click to edit the Burg');
|
||||
|
||||
if (subgroup === 'burgLabels') return tip('Click to edit the Burg');
|
||||
|
||||
if (group === 'lakes' && !land) {
|
||||
const lakeId = +e.target.dataset.f;
|
||||
const name = pack.features[lakeId]?.name;
|
||||
|
|
@ -197,20 +166,16 @@ function showMapTooltip(point, e, i, g) {
|
|||
tip(`${fullName} lake. Click to edit`);
|
||||
return;
|
||||
}
|
||||
if (group === 'coastline') {
|
||||
tip('Click to edit the coastline');
|
||||
return;
|
||||
}
|
||||
if (group === 'coastline') return tip('Click to edit the coastline');
|
||||
|
||||
if (group === 'zones') {
|
||||
const zone = path[path.length - 8];
|
||||
tip(zone.dataset.description);
|
||||
if (zonesEditor.offsetParent) highlightEditorLine(zonesEditor, zone.id, 5000);
|
||||
return;
|
||||
}
|
||||
if (group === 'ice') {
|
||||
tip('Click to edit the Ice');
|
||||
return;
|
||||
}
|
||||
|
||||
if (group === 'ice') return tip('Click to edit the Ice');
|
||||
|
||||
// covering elements
|
||||
if (layerIsOn('togglePrec') && land) tip('Annual Precipitation: ' + getFriendlyPrecipitation(i));
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
// heightmap-editor module. To be added to window as for now
|
||||
'use strict';
|
||||
|
||||
function editHeightmap() {
|
||||
|
|
@ -134,15 +133,8 @@ function editHeightmap() {
|
|||
|
||||
// Exit customization mode
|
||||
function finalizeHeightmap() {
|
||||
if (viewbox.select('#heights').selectAll('*').size() < 200) {
|
||||
tip('Insufficient land area! There should be at least 200 land cells to finalize the heightmap', null, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (document.getElementById('imageConverter').offsetParent) {
|
||||
tip('Please exit the Image Conversion mode first', null, 'error');
|
||||
return;
|
||||
}
|
||||
if (viewbox.select('#heights').selectAll('*').size() < 200) return tip('Insufficient land area! There should be at least 200 land cells to finalize the heightmap', null, 'error');
|
||||
if (document.getElementById('imageConverter').offsetParent) return tip('Please exit the Image Conversion mode first', null, 'error');
|
||||
|
||||
delete window.edits; // remove global variable
|
||||
redo.disabled = templateRedo.disabled = true;
|
||||
|
|
@ -207,6 +199,7 @@ function editHeightmap() {
|
|||
}
|
||||
}
|
||||
|
||||
drawRivers();
|
||||
Lakes.defineGroup();
|
||||
defineBiomes();
|
||||
|
||||
|
|
@ -586,6 +579,7 @@ function editHeightmap() {
|
|||
document.getElementById('brushesSliders').style.display = 'none';
|
||||
}
|
||||
|
||||
const dragBrushThrottled = throttle(dragBrush, 100);
|
||||
function toggleBrushMode(e) {
|
||||
if (e.target.classList.contains('pressed')) {
|
||||
exitBrushMode();
|
||||
|
|
@ -594,7 +588,7 @@ function editHeightmap() {
|
|||
exitBrushMode();
|
||||
document.getElementById('brushesSliders').style.display = 'block';
|
||||
e.target.classList.add('pressed');
|
||||
viewbox.style('cursor', 'crosshair').call(d3.drag().on('start', dragBrush));
|
||||
viewbox.style('cursor', 'crosshair').call(d3.drag().on('start', dragBrushThrottled));
|
||||
}
|
||||
|
||||
function dragBrush() {
|
||||
|
|
@ -842,118 +836,15 @@ function editHeightmap() {
|
|||
body.setAttribute('data-changed', 0);
|
||||
body.innerHTML = '';
|
||||
|
||||
if (template === 'templateVolcano') {
|
||||
addStep('Hill', '1', '90-100', '44-56', '40-60');
|
||||
addStep('Multiply', 0.8, '50-100');
|
||||
addStep('Range', '1.5', '30-55', '45-55', '40-60');
|
||||
addStep('Smooth', 2);
|
||||
addStep('Hill', '1.5', '25-35', '25-30', '20-75');
|
||||
addStep('Hill', '1', '25-35', '75-80', '25-75');
|
||||
addStep('Hill', '0.5', '20-25', '10-15', '20-25');
|
||||
} else if (template === 'templateHighIsland') {
|
||||
addStep('Hill', '1', '90-100', '65-75', '47-53');
|
||||
addStep('Add', 5, 'all');
|
||||
addStep('Hill', '6', '20-23', '25-55', '45-55');
|
||||
addStep('Range', '1', '40-50', '45-55', '45-55');
|
||||
addStep('Smooth', 2);
|
||||
addStep('Trough', '2-3', '20-30', '20-30', '20-30');
|
||||
addStep('Trough', '2-3', '20-30', '60-80', '70-80');
|
||||
addStep('Hill', '1', '10-15', '60-60', '50-50');
|
||||
addStep('Hill', '1.5', '13-16', '15-20', '20-75');
|
||||
addStep('Multiply', 0.8, '20-100');
|
||||
addStep('Range', '1.5', '30-40', '15-85', '30-40');
|
||||
addStep('Range', '1.5', '30-40', '15-85', '60-70');
|
||||
addStep('Pit', '2-3', '10-15', '15-85', '20-80');
|
||||
} else if (template === 'templateLowIsland') {
|
||||
addStep('Hill', '1', '90-99', '60-80', '45-55');
|
||||
addStep('Hill', '4-5', '25-35', '20-65', '40-60');
|
||||
addStep('Range', '1', '40-50', '45-55', '45-55');
|
||||
addStep('Smooth', 3);
|
||||
addStep('Trough', '1.5', '20-30', '15-85', '20-30');
|
||||
addStep('Trough', '1.5', '20-30', '15-85', '70-80');
|
||||
addStep('Hill', '1.5', '10-15', '5-15', '20-80');
|
||||
addStep('Hill', '1', '10-15', '85-95', '70-80');
|
||||
addStep('Pit', '3-5', '10-15', '15-85', '20-80');
|
||||
addStep('Multiply', 0.4, '20-100');
|
||||
} else if (template === 'templateContinents') {
|
||||
addStep('Hill', '1', '80-85', '75-80', '40-60');
|
||||
addStep('Hill', '1', '80-85', '20-25', '40-60');
|
||||
addStep('Multiply', 0.22, '20-100');
|
||||
addStep('Hill', '5-6', '15-20', '25-75', '20-82');
|
||||
addStep('Range', '.8', '30-60', '5-15', '20-45');
|
||||
addStep('Range', '.8', '30-60', '5-15', '55-80');
|
||||
addStep('Range', '0-3', '30-60', '80-90', '20-80');
|
||||
addStep('Trough', '3-4', '15-20', '15-85', '20-80');
|
||||
addStep('Strait', '2', 'vertical');
|
||||
addStep('Smooth', 2);
|
||||
addStep('Trough', '1-2', '5-10', '45-55', '45-55');
|
||||
addStep('Pit', '3-4', '10-15', '15-85', '20-80');
|
||||
addStep('Hill', '1', '5-10', '40-60', '40-60');
|
||||
} else if (template === 'templateArchipelago') {
|
||||
addStep('Add', 11, 'all');
|
||||
addStep('Range', '2-3', '40-60', '20-80', '20-80');
|
||||
addStep('Hill', '5', '15-20', '10-90', '30-70');
|
||||
addStep('Hill', '2', '10-15', '10-30', '20-80');
|
||||
addStep('Hill', '2', '10-15', '60-90', '20-80');
|
||||
addStep('Smooth', 3);
|
||||
addStep('Trough', '10', '20-30', '5-95', '5-95');
|
||||
addStep('Strait', '2', 'vertical');
|
||||
addStep('Strait', '2', 'horizontal');
|
||||
} else if (template === 'templateAtoll') {
|
||||
addStep('Hill', '1', '75-80', '50-60', '45-55');
|
||||
addStep('Hill', '1.5', '30-50', '25-75', '30-70');
|
||||
addStep('Hill', '.5', '30-50', '25-35', '30-70');
|
||||
addStep('Smooth', 1);
|
||||
addStep('Multiply', 0.2, '25-100');
|
||||
addStep('Hill', '.5', '10-20', '50-55', '48-52');
|
||||
} else if (template === 'templateMediterranean') {
|
||||
addStep('Range', '3-4', '30-50', '0-100', '0-10');
|
||||
addStep('Range', '3-4', '30-50', '0-100', '90-100');
|
||||
addStep('Hill', '5-6', '30-70', '0-100', '0-5');
|
||||
addStep('Hill', '5-6', '30-70', '0-100', '95-100');
|
||||
addStep('Smooth', 1);
|
||||
addStep('Hill', '2-3', '30-70', '0-5', '20-80');
|
||||
addStep('Hill', '2-3', '30-70', '95-100', '20-80');
|
||||
addStep('Multiply', 0.8, 'land');
|
||||
addStep('Trough', '3-5', '40-50', '0-100', '0-10');
|
||||
addStep('Trough', '3-5', '40-50', '0-100', '90-100');
|
||||
} else if (template === 'templatePeninsula') {
|
||||
addStep('Range', '2-3', '20-35', '40-50', '0-15');
|
||||
addStep('Add', 5, 'all');
|
||||
addStep('Hill', '1', '90-100', '10-90', '0-5');
|
||||
addStep('Add', 13, 'all');
|
||||
addStep('Hill', '3-4', '3-5', '5-95', '80-100');
|
||||
addStep('Hill', '1-2', '3-5', '5-95', '40-60');
|
||||
addStep('Trough', '5-6', '10-25', '5-95', '5-95');
|
||||
addStep('Smooth', 3);
|
||||
} else if (template === 'templatePangea') {
|
||||
addStep('Hill', '1-2', '25-40', '15-50', '0-10');
|
||||
addStep('Hill', '1-2', '5-40', '50-85', '0-10');
|
||||
addStep('Hill', '1-2', '25-40', '50-85', '90-100');
|
||||
addStep('Hill', '1-2', '5-40', '15-50', '90-100');
|
||||
addStep('Hill', '8-12', '20-40', '20-80', '48-52');
|
||||
addStep('Smooth', 2);
|
||||
addStep('Multiply', 0.7, 'land');
|
||||
addStep('Trough', '3-4', '25-35', '5-95', '10-20');
|
||||
addStep('Trough', '3-4', '25-35', '5-95', '80-90');
|
||||
addStep('Range', '5-6', '30-40', '10-90', '35-65');
|
||||
} else if (template === 'templateIsthmus') {
|
||||
addStep('Hill', '5-10', '15-30', '0-30', '0-20');
|
||||
addStep('Hill', '5-10', '15-30', '10-50', '20-40');
|
||||
addStep('Hill', '5-10', '15-30', '30-70', '40-60');
|
||||
addStep('Hill', '5-10', '15-30', '50-90', '60-80');
|
||||
addStep('Hill', '5-10', '15-30', '70-100', '80-100');
|
||||
addStep('Smooth', 2);
|
||||
addStep('Trough', '4-8', '15-30', '0-30', '0-20');
|
||||
addStep('Trough', '4-8', '15-30', '10-50', '20-40');
|
||||
addStep('Trough', '4-8', '15-30', '30-70', '40-60');
|
||||
addStep('Trough', '4-8', '15-30', '50-90', '60-80');
|
||||
addStep('Trough', '4-8', '15-30', '70-100', '80-100');
|
||||
} else if (template === 'templateShattered') {
|
||||
addStep('Hill', '8', '35-40', '15-85', '30-70');
|
||||
addStep('Trough', '10-20', '40-50', '5-95', '5-95');
|
||||
addStep('Range', '5-7', '30-40', '10-90', '20-80');
|
||||
addStep('Pit', '12-20', '30-40', '15-85', '20-80');
|
||||
const templateString = HeightmapTemplates[template];
|
||||
if (!templateString) return;
|
||||
|
||||
const steps = templateString.split('\n');
|
||||
if (!steps.length) return tip(`Heightmap template: no steps defined`, false, 'error');
|
||||
|
||||
for (const step of steps) {
|
||||
const elements = step.trim().split(' ');
|
||||
addStep(...elements);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1119,6 +1010,10 @@ function editHeightmap() {
|
|||
const reader = new FileReader();
|
||||
|
||||
const img = new Image();
|
||||
img.id = 'imageToConvert';
|
||||
img.style.display = 'none';
|
||||
document.body.appendChild(img);
|
||||
|
||||
img.onload = function () {
|
||||
const ctx = document.getElementById('canvas').getContext('2d');
|
||||
ctx.drawImage(img, 0, 0, graphWidth, graphHeight);
|
||||
|
|
@ -1311,10 +1206,7 @@ function editHeightmap() {
|
|||
}
|
||||
|
||||
function applyConversion() {
|
||||
if (colorsAssigned.childElementCount < 3) {
|
||||
tip('Please do the assignment first', false, 'error');
|
||||
return;
|
||||
}
|
||||
if (colorsAssigned.childElementCount < 3) return tip('Please do the assignment first', false, 'error');
|
||||
|
||||
viewbox
|
||||
.select('#heights')
|
||||
|
|
@ -1340,6 +1232,9 @@ function editHeightmap() {
|
|||
const canvas = document.getElementById('canvas');
|
||||
if (canvas) canvas.remove();
|
||||
|
||||
const image = document.getElementById('imageToConvert');
|
||||
if (image) image.remove();
|
||||
|
||||
d3.select('#imageConverter').selectAll('div.color-div').remove();
|
||||
colorsAssigned.style.display = 'none';
|
||||
colorsUnassigned.style.display = 'none';
|
||||
|
|
|
|||
|
|
@ -123,9 +123,10 @@ function restoreLayers() {
|
|||
if (layerIsOn('toggleIce')) drawIce();
|
||||
if (layerIsOn('toggleEmblems')) drawEmblems();
|
||||
|
||||
// states are getting rendered each time, if it's not required than layers should be hidden
|
||||
if (!layerIsOn('toggleBorders')) $('#borders').fadeOut();
|
||||
if (!layerIsOn('toggleStates')) regions.style('display', 'none').selectAll('path').remove();
|
||||
// some layers are rendered each time, remove them if they are not on
|
||||
if (!layerIsOn('toggleBorders')) borders.selectAll('path').remove();
|
||||
if (!layerIsOn('toggleStates')) regions.selectAll('path').remove();
|
||||
if (!layerIsOn('toggleRivers')) rivers.selectAll('*').remove();
|
||||
}
|
||||
|
||||
function toggleHeight(event) {
|
||||
|
|
@ -876,35 +877,80 @@ function toggleStates(event) {
|
|||
}
|
||||
}
|
||||
|
||||
// draw states
|
||||
function drawStates() {
|
||||
TIME && console.time('drawStates');
|
||||
regions.selectAll('path').remove();
|
||||
|
||||
const cells = pack.cells,
|
||||
vertices = pack.vertices,
|
||||
states = pack.states,
|
||||
n = cells.i.length;
|
||||
const {cells, vertices, features} = pack;
|
||||
const states = pack.states;
|
||||
const n = cells.i.length;
|
||||
|
||||
const used = new Uint8Array(cells.i.length);
|
||||
const vArray = new Array(states.length); // store vertices array
|
||||
const body = new Array(states.length).fill(''); // store path around each state
|
||||
const gap = new Array(states.length).fill(''); // store path along water for each state to fill the gaps
|
||||
const body = new Array(states.length).fill(''); // path around each state
|
||||
const gap = new Array(states.length).fill(''); // path along water for each state to fill the gaps
|
||||
const halo = new Array(states.length).fill(''); // path around states, but not lakes
|
||||
|
||||
const getStringPoint = (v) => vertices.p[v[0]].join(',');
|
||||
|
||||
// define inner-state lakes to omit on border render
|
||||
const innerLakes = features.map((feature) => {
|
||||
if (feature.type !== 'lake') return false;
|
||||
if (!feature.shoreline) Lakes.getShoreline(feature);
|
||||
|
||||
const states = feature.shoreline.map((i) => cells.state[i]);
|
||||
return new Set(states).size > 1 ? false : true;
|
||||
});
|
||||
|
||||
for (const i of cells.i) {
|
||||
if (!cells.state[i] || used[i]) continue;
|
||||
const s = cells.state[i];
|
||||
const onborder = cells.c[i].some((n) => cells.state[n] !== s);
|
||||
const state = cells.state[i];
|
||||
|
||||
const onborder = cells.c[i].some((n) => cells.state[n] !== state);
|
||||
if (!onborder) continue;
|
||||
|
||||
const borderWith = cells.c[i].map((c) => cells.state[c]).find((n) => n !== s);
|
||||
const borderWith = cells.c[i].map((c) => cells.state[c]).find((n) => n !== state);
|
||||
const vertex = cells.v[i].find((v) => vertices.c[v].some((i) => cells.state[i] === borderWith));
|
||||
const chain = connectVertices(vertex, s, borderWith);
|
||||
if (chain.length < 3) continue;
|
||||
const points = chain.map((v) => vertices.p[v[0]]);
|
||||
if (!vArray[s]) vArray[s] = [];
|
||||
vArray[s].push(points);
|
||||
body[s] += 'M' + points.join('L');
|
||||
gap[s] += 'M' + vertices.p[chain[0][0]] + chain.reduce((r, v, i, d) => (!i ? r : !v[2] ? r + 'L' + vertices.p[v[0]] : d[i + 1] && !d[i + 1][2] ? r + 'M' + vertices.p[v[0]] : r), '');
|
||||
const chain = connectVertices(vertex, state);
|
||||
|
||||
const noInnerLakes = chain.filter((v) => v[1] !== 'innerLake');
|
||||
if (noInnerLakes.length < 3) continue;
|
||||
|
||||
// get path around the state
|
||||
if (!vArray[state]) vArray[state] = [];
|
||||
const points = noInnerLakes.map((v) => vertices.p[v[0]]);
|
||||
vArray[state].push(points);
|
||||
body[state] += 'M' + points.join('L');
|
||||
|
||||
// connect path for halo
|
||||
let discontinued = true;
|
||||
halo[state] += noInnerLakes
|
||||
.map((v) => {
|
||||
if (v[1] === 'border') {
|
||||
discontinued = true;
|
||||
return '';
|
||||
}
|
||||
|
||||
const operation = discontinued ? 'M' : 'L';
|
||||
discontinued = false;
|
||||
return `${operation}${getStringPoint(v)}`;
|
||||
})
|
||||
.join('');
|
||||
|
||||
// connect gaps between state and water into a single path
|
||||
discontinued = true;
|
||||
gap[state] += chain
|
||||
.map((v) => {
|
||||
if (v[1] === 'land') {
|
||||
discontinued = true;
|
||||
return '';
|
||||
}
|
||||
|
||||
const operation = discontinued ? 'M' : 'L';
|
||||
discontinued = false;
|
||||
return `${operation}${getStringPoint(v)}`;
|
||||
})
|
||||
.join('');
|
||||
}
|
||||
|
||||
// find state visual center
|
||||
|
|
@ -913,13 +959,14 @@ function drawStates() {
|
|||
states[i].pole = polylabel(sorted, 1.0); // pole of inaccessibility
|
||||
});
|
||||
|
||||
const bodyData = body.map((p, i) => [p.length > 10 ? p : null, i, states[i].color]).filter((d) => d[0]);
|
||||
const gapData = gap.map((p, i) => [p.length > 10 ? p : null, i, states[i].color]).filter((d) => d[0]);
|
||||
const bodyData = body.map((p, s) => [p.length > 10 ? p : null, s, states[s].color]).filter((d) => d[0]);
|
||||
const gapData = gap.map((p, s) => [p.length > 10 ? p : null, s, states[s].color]).filter((d) => d[0]);
|
||||
const haloData = halo.map((p, s) => [p.length > 10 ? p : null, s, states[s].color]).filter((d) => d[0]);
|
||||
|
||||
const bodyString = bodyData.map((d) => `<path id="state${d[1]}" d="${d[0]}" fill="${d[2]}" stroke="none"/>`).join('');
|
||||
const gapString = gapData.map((d) => `<path id="state-gap${d[1]}" d="${d[0]}" fill="none" stroke="${d[2]}"/>`).join('');
|
||||
const clipString = bodyData.map((d) => `<clipPath id="state-clip${d[1]}"><use href="#state${d[1]}"/></clipPath>`).join('');
|
||||
const haloString = bodyData
|
||||
const haloString = haloData
|
||||
.map((d) => `<path id="state-border${d[1]}" d="${d[0]}" clip-path="url(#state-clip${d[1]})" stroke="${d3.color(d[2]) ? d3.color(d[2]).darker().hex() : '#666666'}"/>`)
|
||||
.join('');
|
||||
|
||||
|
|
@ -928,57 +975,77 @@ function drawStates() {
|
|||
statesHalo.html(haloString);
|
||||
|
||||
// connect vertices to chain
|
||||
function connectVertices(start, t, state) {
|
||||
function connectVertices(start, state) {
|
||||
const chain = []; // vertices chain to form a path
|
||||
let land = vertices.c[start].some((c) => cells.h[c] >= 20 && cells.state[c] !== t);
|
||||
function check(i) {
|
||||
state = cells.state[i];
|
||||
land = cells.h[i] >= 20;
|
||||
}
|
||||
const getType = (c) => {
|
||||
const borderCell = c.find((i) => cells.b[i]);
|
||||
if (borderCell) return 'border';
|
||||
|
||||
const waterCell = c.find((i) => cells.h[i] < 20);
|
||||
if (!waterCell) return 'land';
|
||||
if (innerLakes[cells.f[waterCell]]) return 'innerLake';
|
||||
return features[cells.f[waterCell]].type;
|
||||
};
|
||||
|
||||
for (let i = 0, current = start; i === 0 || (current !== start && i < 20000); i++) {
|
||||
const prev = chain[chain.length - 1] ? chain[chain.length - 1][0] : -1; // previous vertex in chain
|
||||
chain.push([current, state, land]); // add current vertex to sequence
|
||||
const prev = chain.length ? chain[chain.length - 1][0] : -1; // previous vertex in chain
|
||||
|
||||
const c = vertices.c[current]; // cells adjacent to vertex
|
||||
c.filter((c) => cells.state[c] === t).forEach((c) => (used[c] = 1));
|
||||
const c0 = c[0] >= n || cells.state[c[0]] !== t;
|
||||
const c1 = c[1] >= n || cells.state[c[1]] !== t;
|
||||
const c2 = c[2] >= n || cells.state[c[2]] !== t;
|
||||
chain.push([current, getType(c)]); // add current vertex to sequence
|
||||
|
||||
c.filter((c) => cells.state[c] === state).forEach((c) => (used[c] = 1));
|
||||
const c0 = c[0] >= n || cells.state[c[0]] !== state;
|
||||
const c1 = c[1] >= n || cells.state[c[1]] !== state;
|
||||
const c2 = c[2] >= n || cells.state[c[2]] !== state;
|
||||
|
||||
const v = vertices.v[current]; // neighboring vertices
|
||||
if (v[0] !== prev && c0 !== c1) {
|
||||
current = v[0];
|
||||
check(c0 ? c[0] : c[1]);
|
||||
} else if (v[1] !== prev && c1 !== c2) {
|
||||
current = v[1];
|
||||
check(c1 ? c[1] : c[2]);
|
||||
} else if (v[2] !== prev && c0 !== c2) {
|
||||
current = v[2];
|
||||
check(c2 ? c[2] : c[0]);
|
||||
}
|
||||
if (current === chain[chain.length - 1][0]) {
|
||||
|
||||
if (v[0] !== prev && c0 !== c1) current = v[0];
|
||||
else if (v[1] !== prev && c1 !== c2) current = v[1];
|
||||
else if (v[2] !== prev && c0 !== c2) current = v[2];
|
||||
|
||||
if (current === prev) {
|
||||
ERROR && console.error('Next vertex is not found');
|
||||
break;
|
||||
}
|
||||
}
|
||||
chain.push([start, state, land]); // add starting vertex to sequence to close the path
|
||||
|
||||
if (chain.length) chain.push(chain[0]);
|
||||
return chain;
|
||||
}
|
||||
|
||||
invokeActiveZooming();
|
||||
TIME && console.timeEnd('drawStates');
|
||||
}
|
||||
|
||||
function toggleBorders(event) {
|
||||
if (!layerIsOn('toggleBorders')) {
|
||||
turnButtonOn('toggleBorders');
|
||||
drawBorders();
|
||||
if (event && isCtrlClick(event)) editStyle('borders');
|
||||
} else {
|
||||
if (event && isCtrlClick(event)) {
|
||||
editStyle('borders');
|
||||
return;
|
||||
}
|
||||
turnButtonOff('toggleBorders');
|
||||
borders.selectAll('path').remove();
|
||||
}
|
||||
}
|
||||
|
||||
// draw state and province borders
|
||||
function drawBorders() {
|
||||
TIME && console.time('drawBorders');
|
||||
borders.selectAll('path').remove();
|
||||
|
||||
const cells = pack.cells,
|
||||
vertices = pack.vertices,
|
||||
n = cells.i.length;
|
||||
const sPath = [],
|
||||
pPath = [];
|
||||
const sUsed = new Array(pack.states.length).fill('').map((a) => []);
|
||||
const pUsed = new Array(pack.provinces.length).fill('').map((a) => []);
|
||||
const {cells, vertices} = pack;
|
||||
const n = cells.i.length;
|
||||
|
||||
const sPath = [];
|
||||
const pPath = [];
|
||||
|
||||
const sUsed = new Array(pack.states.length).fill('').map((_) => []);
|
||||
const pUsed = new Array(pack.provinces.length).fill('').map((_) => []);
|
||||
|
||||
for (let i = 0; i < cells.i.length; i++) {
|
||||
if (!cells.state[i]) continue;
|
||||
|
|
@ -1070,21 +1137,6 @@ function drawBorders() {
|
|||
TIME && console.timeEnd('drawBorders');
|
||||
}
|
||||
|
||||
function toggleBorders(event) {
|
||||
if (!layerIsOn('toggleBorders')) {
|
||||
turnButtonOn('toggleBorders');
|
||||
$('#borders').fadeIn();
|
||||
if (event && isCtrlClick(event)) editStyle('borders');
|
||||
} else {
|
||||
if (event && isCtrlClick(event)) {
|
||||
editStyle('borders');
|
||||
return;
|
||||
}
|
||||
turnButtonOff('toggleBorders');
|
||||
$('#borders').fadeOut();
|
||||
}
|
||||
}
|
||||
|
||||
function toggleProvinces(event) {
|
||||
if (!layerIsOn('toggleProvinces')) {
|
||||
turnButtonOn('toggleProvinces');
|
||||
|
|
@ -1396,18 +1448,30 @@ function toggleTexture(event) {
|
|||
function toggleRivers(event) {
|
||||
if (!layerIsOn('toggleRivers')) {
|
||||
turnButtonOn('toggleRivers');
|
||||
$('#rivers').fadeIn();
|
||||
drawRivers();
|
||||
if (event && isCtrlClick(event)) editStyle('rivers');
|
||||
} else {
|
||||
if (event && isCtrlClick(event)) {
|
||||
editStyle('rivers');
|
||||
return;
|
||||
}
|
||||
$('#rivers').fadeOut();
|
||||
if (event && isCtrlClick(event)) return editStyle('rivers');
|
||||
rivers.selectAll('*').remove();
|
||||
turnButtonOff('toggleRivers');
|
||||
}
|
||||
}
|
||||
|
||||
function drawRivers() {
|
||||
TIME && console.time('drawRivers');
|
||||
const {addMeandering, getRiverPath} = Rivers;
|
||||
lineGen.curve(d3.curveCatmullRom.alpha(0.1));
|
||||
const riverPaths = pack.rivers.map((river) => {
|
||||
const meanderedPoints = addMeandering(river.cells, river.points);
|
||||
const widthFactor = river.widthFactor || 1;
|
||||
const startingWidth = river.sourceWidth || 0;
|
||||
const path = getRiverPath(meanderedPoints, widthFactor, startingWidth);
|
||||
return `<path id="river${river.i}" d="${path}"/>`;
|
||||
});
|
||||
rivers.html(riverPaths.join(''));
|
||||
TIME && console.timeEnd('drawRivers');
|
||||
}
|
||||
|
||||
function toggleRoutes(event) {
|
||||
if (!layerIsOn('toggleRoutes')) {
|
||||
turnButtonOn('toggleRoutes');
|
||||
|
|
@ -1558,21 +1622,21 @@ function drawEmblems() {
|
|||
const getStateEmblemsSize = () => {
|
||||
const startSize = Math.min(Math.max((graphHeight + graphWidth) / 40, 10), 100);
|
||||
const statesMod = 1 + validStates.length / 100 - (15 - validStates.length) / 200; // states number modifier
|
||||
const sizeMod = +document.getElementById('styleEmblemsStateSizeInput').value || 1;
|
||||
const sizeMod = +document.getElementById('emblemsStateSizeInput').value || 1;
|
||||
return rn((startSize / statesMod) * sizeMod); // target size ~50px on 1536x754 map with 15 states
|
||||
};
|
||||
|
||||
const getProvinceEmblemsSize = () => {
|
||||
const startSize = Math.min(Math.max((graphHeight + graphWidth) / 100, 5), 70);
|
||||
const provincesMod = 1 + validProvinces.length / 1000 - (115 - validProvinces.length) / 1000; // states number modifier
|
||||
const sizeMod = +document.getElementById('styleEmblemsProvinceSizeInput').value || 1;
|
||||
const sizeMod = +document.getElementById('emblemsProvinceSizeInput').value || 1;
|
||||
return rn((startSize / provincesMod) * sizeMod); // target size ~20px on 1536x754 map with 115 provinces
|
||||
};
|
||||
|
||||
const getBurgEmblemSize = () => {
|
||||
const startSize = Math.min(Math.max((graphHeight + graphWidth) / 185, 2), 50);
|
||||
const burgsMod = 1 + validBurgs.length / 1000 - (450 - validBurgs.length) / 1000; // states number modifier
|
||||
const sizeMod = +document.getElementById('styleEmblemsBurgSizeInput').value || 1;
|
||||
const sizeMod = +document.getElementById('emblemsBurgSizeInput').value || 1;
|
||||
return rn((startSize / burgsMod) * sizeMod); // target size ~8.5px on 1536x754 map with 450 burgs
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -98,7 +98,8 @@ function showSupporters() {
|
|||
PlayByMail.Net,Brad Wardell,Lance Saba,Egoensis,Brea Richards,Tiber,Chris Bloom,Maxim Lowe,Aquelion,Page One Project,Spencer Morris,Paul Ingram,
|
||||
Dust Bunny,Adrian Wright,Eric Alexander Cartaya,GameNight,Thomas Mortensen Hansen,Zklaus,Drinarius,Ed Wright,Lon Varnadore,Crys Cain,Heaven N Lee,
|
||||
Jeffrey Henning,Lazer Elf,Jordan Bellah,Alex Beard,Kass Frisson,Petro Lombaard,Emanuel Pietri,Rox,PinkEvil,Gavin Madrigal,Martin Lorber,Prince of Morgoth,
|
||||
Jaryd Armstrong,Andrew Pirkola,ThyHolyDevil,Gary Smith,Tyshaun Wise,Ethan Cook,Jon Stroman,Nobody679,良义 金,Chris Gray`;
|
||||
Jaryd Armstrong,Andrew Pirkola,ThyHolyDevil,Gary Smith,Tyshaun Wise,Ethan Cook,Jon Stroman,Nobody679,良义 金,Chris Gray,Phoenix Boatwright,Mackenzie,
|
||||
"Milo Cohen,Jason Matthew Wuerfel,Rasmus Legêne,Andrew Hines,Wexxler,Espen Sæverud,Binks,Dominick Ormsby,Linn Browning,Václav Švec,Alan Buehne,George J.Lekkas"`;
|
||||
|
||||
const array = supporters
|
||||
.replace(/(?:\r\n|\r|\n)/g, '')
|
||||
|
|
@ -157,6 +158,7 @@ optionsContent.addEventListener('change', function (event) {
|
|||
if (id === 'zoomExtentMin' || id === 'zoomExtentMax') changeZoomExtent(value);
|
||||
else if (id === 'optionsSeed') generateMapWithSeed();
|
||||
else if (id === 'uiSizeInput' || id === 'uiSizeOutput') changeUIsize(value);
|
||||
if (id === 'shapeRendering') viewbox.attr('shape-rendering', value);
|
||||
else if (id === 'yearInput') changeYear();
|
||||
else if (id === 'eraInput') changeEra();
|
||||
});
|
||||
|
|
@ -494,6 +496,9 @@ function applyStoredOptions() {
|
|||
const height = +params.get('height');
|
||||
if (width) mapWidthInput.value = width;
|
||||
if (height) mapHeightInput.value = height;
|
||||
|
||||
// set shape rendering
|
||||
viewbox.attr('shape-rendering', shapeRendering.value);
|
||||
}
|
||||
|
||||
// randomize options if randomization is allowed (not locked or options='default')
|
||||
|
|
@ -537,17 +542,18 @@ function randomizeOptions() {
|
|||
// select heightmap template pseudo-randomly
|
||||
function randomizeHeightmapTemplate() {
|
||||
const templates = {
|
||||
Volcano: 3,
|
||||
'High Island': 22,
|
||||
'Low Island': 9,
|
||||
Continents: 20,
|
||||
Archipelago: 25,
|
||||
Mediterranean: 3,
|
||||
Peninsula: 3,
|
||||
Pangea: 5,
|
||||
Isthmus: 2,
|
||||
Atoll: 1,
|
||||
Shattered: 7
|
||||
volcano: 3,
|
||||
highIsland: 22,
|
||||
lowIsland: 9,
|
||||
continents: 19,
|
||||
archipelago: 23,
|
||||
mediterranean: 5,
|
||||
peninsula: 3,
|
||||
pangea: 5,
|
||||
isthmus: 2,
|
||||
atoll: 1,
|
||||
shattered: 7,
|
||||
taklamakan: 1
|
||||
};
|
||||
document.getElementById('templateInput').value = rw(templates);
|
||||
}
|
||||
|
|
@ -773,6 +779,12 @@ document
|
|||
.forEach((el) => el.addEventListener('input', updateTilesOptions));
|
||||
|
||||
function updateTilesOptions() {
|
||||
if (this?.tagName === 'INPUT') {
|
||||
const {nextElementSibling: next, previousElementSibling: prev} = this;
|
||||
if (next?.tagName === 'INPUT') next.value = this.value;
|
||||
if (prev?.tagName === 'INPUT') prev.value = this.value;
|
||||
}
|
||||
|
||||
const tileSize = document.getElementById('tileSize');
|
||||
const tilesX = +document.getElementById('tileColsOutput').value;
|
||||
const tilesY = +document.getElementById('tileRowsOutput').value;
|
||||
|
|
@ -908,6 +920,7 @@ function toggle3dOptions() {
|
|||
document.getElementById('options3dMeshRotationNumber').addEventListener('change', changeRotation);
|
||||
document.getElementById('options3dGlobeRotationRange').addEventListener('input', changeRotation);
|
||||
document.getElementById('options3dGlobeRotationNumber').addEventListener('change', changeRotation);
|
||||
document.getElementById('options3dMeshLabels3d').addEventListener('change', toggleLabels3d);
|
||||
document.getElementById('options3dMeshSkyMode').addEventListener('change', toggleSkyMode);
|
||||
document.getElementById('options3dMeshSky').addEventListener('input', changeColors);
|
||||
document.getElementById('options3dMeshWater').addEventListener('input', changeColors);
|
||||
|
|
@ -924,6 +937,7 @@ function toggle3dOptions() {
|
|||
options3dSunZ.value = ThreeD.options.sun.z;
|
||||
options3dMeshRotationRange.value = options3dMeshRotationNumber.value = ThreeD.options.rotateMesh;
|
||||
options3dGlobeRotationRange.value = options3dGlobeRotationNumber.value = ThreeD.options.rotateGlobe;
|
||||
options3dMeshLabels3d.value = ThreeD.options.labels3d;
|
||||
options3dMeshSkyMode.value = ThreeD.options.extendedWater;
|
||||
options3dColorSection.style.display = ThreeD.options.extendedWater ? 'block' : 'none';
|
||||
options3dMeshSky.value = ThreeD.options.skyColor;
|
||||
|
|
@ -954,6 +968,10 @@ function toggle3dOptions() {
|
|||
ThreeD.setRotation(speed);
|
||||
}
|
||||
|
||||
function toggleLabels3d() {
|
||||
ThreeD.toggleLabels();
|
||||
}
|
||||
|
||||
function toggleSkyMode() {
|
||||
const hide = ThreeD.options.extendedWater;
|
||||
options3dColorSection.style.display = hide ? 'none' : 'block';
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ function editReliefIcon() {
|
|||
reliefSpacingDiv.style.display = 'none';
|
||||
reliefIconsSeletionAny.style.display = 'none';
|
||||
|
||||
removeCircle();
|
||||
updateReliefSizeInput();
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
|
|
@ -115,10 +116,7 @@ function editReliefIcon() {
|
|||
|
||||
function dragToAdd() {
|
||||
const pressed = reliefIconsDiv.querySelector('svg.pressed');
|
||||
if (!pressed) {
|
||||
tip('Please select an icon', false, error);
|
||||
return;
|
||||
}
|
||||
if (!pressed) return tip('Please select an icon', false, error);
|
||||
|
||||
const type = pressed.dataset.type;
|
||||
const r = +reliefRadiusNumber.value;
|
||||
|
|
@ -188,10 +186,7 @@ function editReliefIcon() {
|
|||
|
||||
function dragToRemove() {
|
||||
const pressed = reliefIconsDiv.querySelector('svg.pressed');
|
||||
if (!pressed) {
|
||||
tip('Please select an icon', false, error);
|
||||
return;
|
||||
}
|
||||
if (!pressed) return tip('Please select an icon', false, error);
|
||||
|
||||
const r = +reliefRadiusNumber.value;
|
||||
const type = pressed.dataset.type;
|
||||
|
|
@ -256,12 +251,32 @@ function editReliefIcon() {
|
|||
}
|
||||
|
||||
function removeIcon() {
|
||||
const message = 'Are you sure you want to remove the relief icon? <br>This action cannot be reverted';
|
||||
const onConfirm = () => {
|
||||
elSelected.remove();
|
||||
$('#reliefEditor').dialog('close');
|
||||
};
|
||||
confirmationDialog({title: 'Remove relief icon', message, confirm: 'Remove', onConfirm});
|
||||
let selection = null;
|
||||
const pressed = reliefTools.querySelector('button.pressed');
|
||||
if (pressed.id === 'reliefIndividual') {
|
||||
alertMessage.innerHTML = `Are you sure you want to remove the icon?`;
|
||||
selection = elSelected;
|
||||
} else {
|
||||
const type = reliefIconsDiv.querySelector('svg.pressed')?.dataset.type;
|
||||
selection = type ? terrain.selectAll("use[href='" + type + "']") : terrain.selectAll('use');
|
||||
const size = selection.size();
|
||||
alertMessage.innerHTML = type ? `Are you sure you want to remove all ${type} icons (${size})?` : `Are you sure you want to remove all icons (${size})?`;
|
||||
}
|
||||
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
title: 'Remove relief icons',
|
||||
buttons: {
|
||||
Remove: function () {
|
||||
if (selection) selection.remove();
|
||||
$(this).dialog('close');
|
||||
$('#reliefEditor').dialog('close');
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function closeReliefEditor() {
|
||||
|
|
|
|||
125
modules/ui/rivers-creator.js
Normal file
125
modules/ui/rivers-creator.js
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
"use strict";
|
||||
function createRiver() {
|
||||
if (customization) return;
|
||||
closeDialogs();
|
||||
if (!layerIsOn("toggleRivers")) toggleRivers();
|
||||
|
||||
document.getElementById("toggleCells").dataset.forced = +!layerIsOn("toggleCells");
|
||||
if (!layerIsOn("toggleCells")) toggleCells();
|
||||
|
||||
tip("Click to add river point, click again to remove", true);
|
||||
debug.append("g").attr("id", "controlCells");
|
||||
viewbox.style("cursor", "crosshair").on("click", onCellClick);
|
||||
|
||||
createRiver.cells = [];
|
||||
const body = document.getElementById("riverCreatorBody");
|
||||
|
||||
$("#riverCreator").dialog({
|
||||
title: "Create River",
|
||||
resizable: false,
|
||||
position: {my: "left top", at: "left+10 top+10", of: "#map"},
|
||||
close: closeRiverCreator
|
||||
});
|
||||
|
||||
if (modules.createRiver) return;
|
||||
modules.createRiver = true;
|
||||
|
||||
// add listeners
|
||||
document.getElementById("riverCreatorComplete").addEventListener("click", addRiver);
|
||||
document.getElementById("riverCreatorCancel").addEventListener("click", () => $("#riverCreator").dialog("close"));
|
||||
body.addEventListener("click", function (ev) {
|
||||
const el = ev.target;
|
||||
const cl = el.classList;
|
||||
const cell = +el.parentNode.dataset.cell;
|
||||
if (cl.contains("editFlux")) pack.cells.fl[cell] = +el.value;
|
||||
else if (cl.contains("icon-trash-empty")) removeCell(cell);
|
||||
});
|
||||
|
||||
function onCellClick() {
|
||||
const cell = findCell(...d3.mouse(this));
|
||||
|
||||
if (createRiver.cells.includes(cell)) removeCell(cell);
|
||||
else addCell(cell);
|
||||
}
|
||||
|
||||
function addCell(cell) {
|
||||
createRiver.cells.push(cell);
|
||||
drawCells(createRiver.cells);
|
||||
|
||||
const flux = pack.cells.fl[cell];
|
||||
const line = `<div class="editorLine" data-cell="${cell}">
|
||||
<span>Cell ${cell}</span>
|
||||
<span data-tip="Set flux affects river width" style="margin-left: 0.4em">Flux</span>
|
||||
<input type="number" min=0 value="${flux}" class="editFlux" style="width: 5em"/>
|
||||
<span data-tip="Remove the cell" class="icon-trash-empty pointer"></span>
|
||||
</div>`;
|
||||
body.innerHTML += line;
|
||||
}
|
||||
|
||||
function removeCell(cell) {
|
||||
createRiver.cells = createRiver.cells.filter(c => c !== cell);
|
||||
drawCells(createRiver.cells);
|
||||
body.querySelector(`div[data-cell='${cell}']`)?.remove();
|
||||
}
|
||||
|
||||
function drawCells(cells) {
|
||||
debug
|
||||
.select("#controlCells")
|
||||
.selectAll(`polygon`)
|
||||
.data(cells)
|
||||
.join("polygon")
|
||||
.attr("points", d => getPackPolygon(d))
|
||||
.attr("class", "current");
|
||||
}
|
||||
|
||||
function addRiver() {
|
||||
const {rivers, cells} = pack;
|
||||
const {addMeandering, getApproximateLength, getWidth, getOffset, getName, getRiverPath, getBasin} = Rivers;
|
||||
|
||||
const riverCells = createRiver.cells;
|
||||
if (riverCells.length < 2) return tip("Add at least 2 cells", false, "error");
|
||||
|
||||
const riverId = rivers.length ? last(rivers).i + 1 : 1;
|
||||
const parent = cells.r[last(riverCells)] || riverId;
|
||||
|
||||
riverCells.forEach(cell => {
|
||||
if (!cells.r[cell]) cells.r[cell] = riverId;
|
||||
});
|
||||
|
||||
const source = riverCells[0];
|
||||
const mouth = parent === riverId ? last(riverCells) : riverCells[riverCells.length - 2];
|
||||
const sourceWidth = 0.05;
|
||||
const widthFactor = 1.2;
|
||||
|
||||
const meanderedPoints = addMeandering(riverCells);
|
||||
|
||||
const discharge = cells.fl[mouth]; // m3 in second
|
||||
const length = getApproximateLength(meanderedPoints);
|
||||
const width = getWidth(getOffset(discharge, meanderedPoints.length, widthFactor, sourceWidth));
|
||||
const name = getName(mouth);
|
||||
const basin = getBasin(parent);
|
||||
|
||||
rivers.push({i: riverId, source, mouth, discharge, length, width, widthFactor, sourceWidth, parent, cells: riverCells, basin, name, type: "River"});
|
||||
|
||||
// render river
|
||||
lineGen.curve(d3.curveCatmullRom.alpha(0.1));
|
||||
viewbox
|
||||
.select("#rivers")
|
||||
.append("path")
|
||||
.attr("id", "river" + riverId)
|
||||
.attr("d", getRiverPath(meanderedPoints, widthFactor, sourceWidth));
|
||||
|
||||
editRiver(riverId);
|
||||
}
|
||||
|
||||
function closeRiverCreator() {
|
||||
body.innerHTML = "";
|
||||
debug.select("#controlCells").remove();
|
||||
restoreDefaultEvents();
|
||||
clearMainTip();
|
||||
|
||||
const forced = +document.getElementById("toggleCells").dataset.forced;
|
||||
document.getElementById("toggleCells").dataset.forced = 0;
|
||||
if (forced && layerIsOn("toggleCells")) toggleCells();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +1,31 @@
|
|||
'use strict';
|
||||
function editRiver(id) {
|
||||
if (customization) return;
|
||||
if (elSelected && d3.event && d3.event.target.id === elSelected.attr('id')) return;
|
||||
if (elSelected && id === elSelected.attr('id')) return;
|
||||
closeDialogs('.stable');
|
||||
if (!layerIsOn('toggleRivers')) toggleRivers();
|
||||
|
||||
const node = id ? document.getElementById(id) : d3.event.target;
|
||||
elSelected = d3.select(node).on('click', addInterimControlPoint);
|
||||
viewbox.on('touchmove mousemove', showEditorTips);
|
||||
debug.append('g').attr('id', 'controlPoints').attr('transform', elSelected.attr('transform'));
|
||||
document.getElementById('toggleCells').dataset.forced = +!layerIsOn('toggleCells');
|
||||
if (!layerIsOn('toggleCells')) toggleCells();
|
||||
|
||||
elSelected = d3.select('#' + id);
|
||||
|
||||
tip('Drag control points to change the river course. For major changes please create a new river instead', true);
|
||||
debug.append('g').attr('id', 'controlCells');
|
||||
debug.append('g').attr('id', 'controlPoints');
|
||||
|
||||
updateRiverData();
|
||||
drawControlPoints(node);
|
||||
|
||||
const river = getRiver();
|
||||
const {cells, points} = river;
|
||||
const riverPoints = Rivers.getRiverPoints(cells, points);
|
||||
drawControlPoints(riverPoints, cells);
|
||||
drawCells(cells, 'current');
|
||||
|
||||
$('#riverEditor').dialog({
|
||||
title: 'Edit River',
|
||||
resizable: false,
|
||||
position: {my: 'center top+80', at: 'top', of: node, collision: 'fit'},
|
||||
position: {my: 'left top', at: 'left+10 top+10', of: '#map'},
|
||||
close: closeRiverEditor
|
||||
});
|
||||
|
||||
|
|
@ -23,27 +33,19 @@ function editRiver(id) {
|
|||
modules.editRiver = true;
|
||||
|
||||
// add listeners
|
||||
document.getElementById('riverCreateSelectingCells').addEventListener('click', createRiver);
|
||||
document.getElementById('riverEditStyle').addEventListener('click', () => editStyle('rivers'));
|
||||
document.getElementById('riverElevationProfile').addEventListener('click', showElevationProfile);
|
||||
document.getElementById('riverLegend').addEventListener('click', editRiverLegend);
|
||||
document.getElementById('riverRemove').addEventListener('click', removeRiver);
|
||||
document.getElementById('riverName').addEventListener('input', changeName);
|
||||
document.getElementById('riverType').addEventListener('input', changeType);
|
||||
document.getElementById('riverNameCulture').addEventListener('click', generateNameCulture);
|
||||
document.getElementById('riverNameRandom').addEventListener('click', generateNameRandom);
|
||||
document.getElementById('riverMainstem').addEventListener('change', changeParent);
|
||||
|
||||
document.getElementById('riverSourceWidth').addEventListener('input', changeSourceWidth);
|
||||
document.getElementById('riverWidthFactor').addEventListener('input', changeWidthFactor);
|
||||
|
||||
document.getElementById('riverNew').addEventListener('click', toggleRiverCreationMode);
|
||||
document.getElementById('riverEditStyle').addEventListener('click', () => editStyle('rivers'));
|
||||
document.getElementById('riverElevationProfile').addEventListener('click', showElevationProfile);
|
||||
document.getElementById('riverLegend').addEventListener('click', editRiverLegend);
|
||||
document.getElementById('riverRemove').addEventListener('click', removeRiver);
|
||||
|
||||
function showEditorTips() {
|
||||
showMainTip();
|
||||
if (d3.event.target.parentNode.id === elSelected.attr('id')) tip('Drag to move, click to add a control point');
|
||||
else if (d3.event.target.parentNode.id === 'controlPoints') tip('Drag to move, click to delete the control point');
|
||||
}
|
||||
|
||||
function getRiver() {
|
||||
const riverId = +elSelected.attr('id').slice(5);
|
||||
const river = pack.rivers.find((r) => r.i === riverId);
|
||||
|
|
@ -67,87 +69,107 @@ function editRiver(id) {
|
|||
document.getElementById('riverBasin').value = pack.rivers.find((river) => river.i === r.basin).name;
|
||||
|
||||
document.getElementById('riverDischarge').value = r.discharge + ' m³/s';
|
||||
r.length = elSelected.node().getTotalLength() / 2;
|
||||
const length = rn(r.length * distanceScaleInput.value) + ' ' + distanceUnitInput.value;
|
||||
document.getElementById('riverLength').value = length;
|
||||
const width = rn(r.width * distanceScaleInput.value, 3) + ' ' + distanceUnitInput.value;
|
||||
document.getElementById('riverWidth').value = width;
|
||||
|
||||
document.getElementById('riverSourceWidth').value = r.sourceWidth;
|
||||
document.getElementById('riverWidthFactor').value = r.widthFactor;
|
||||
|
||||
updateRiverLength(r);
|
||||
updateRiverWidth(r);
|
||||
}
|
||||
|
||||
function drawControlPoints(node) {
|
||||
const length = getRiver().length;
|
||||
const segments = Math.ceil(length / 4);
|
||||
const increment = rn((length / segments) * 1e5);
|
||||
for (let i = increment * segments, c = i; i >= 0; i -= increment, c += increment) {
|
||||
const p1 = node.getPointAtLength(i / 1e5);
|
||||
const p2 = node.getPointAtLength(c / 1e5);
|
||||
addControlPoint([(p1.x + p2.x) / 2, (p1.y + p2.y) / 2]);
|
||||
}
|
||||
function updateRiverLength(river) {
|
||||
river.length = rn(elSelected.node().getTotalLength() / 2, 2);
|
||||
const lengthUI = `${rn(river.length * distanceScaleInput.value)} ${distanceUnitInput.value}`;
|
||||
document.getElementById('riverLength').value = lengthUI;
|
||||
}
|
||||
|
||||
function addControlPoint(point, before = null) {
|
||||
debug.select('#controlPoints').insert('circle', before).attr('cx', point[0]).attr('cy', point[1]).attr('r', 0.6).call(d3.drag().on('drag', dragControlPoint)).on('click', clickControlPoint);
|
||||
function updateRiverWidth(river) {
|
||||
const {addMeandering, getWidth, getOffset} = Rivers;
|
||||
const {cells, discharge, widthFactor, sourceWidth} = river;
|
||||
const meanderedPoints = addMeandering(cells);
|
||||
river.width = getWidth(getOffset(discharge, meanderedPoints.length, widthFactor, sourceWidth));
|
||||
|
||||
const width = `${rn(river.width * distanceScaleInput.value, 3)} ${distanceUnitInput.value}`;
|
||||
document.getElementById('riverWidth').value = width;
|
||||
}
|
||||
|
||||
function dragControlPoint() {
|
||||
this.setAttribute('cx', d3.event.x);
|
||||
this.setAttribute('cy', d3.event.y);
|
||||
redrawRiver();
|
||||
}
|
||||
|
||||
function redrawRiver() {
|
||||
const points = [];
|
||||
function drawControlPoints(points, cells) {
|
||||
debug
|
||||
.select('#controlPoints')
|
||||
.selectAll('circle')
|
||||
.each(function () {
|
||||
points.push([+this.getAttribute('cx'), +this.getAttribute('cy')]);
|
||||
});
|
||||
.data(points)
|
||||
.enter()
|
||||
.append('circle')
|
||||
.attr('cx', (d) => d[0])
|
||||
.attr('cy', (d) => d[1])
|
||||
.attr('r', 0.6)
|
||||
.attr('data-cell', (d, i) => cells[i])
|
||||
.attr('data-i', (d, i) => i)
|
||||
.call(d3.drag().on('start', dragControlPoint));
|
||||
}
|
||||
|
||||
if (points.length < 2) return;
|
||||
if (points.length === 2) {
|
||||
const p0 = points[0],
|
||||
p1 = points[1];
|
||||
const angle = Math.atan2(p1[1] - p0[1], p1[0] - p0[0]);
|
||||
const sin = Math.sin(angle),
|
||||
cos = Math.cos(angle);
|
||||
elSelected.attr('d', `M${p0[0]},${p0[1]} L${p1[0]},${p1[1]} l${-sin / 2},${cos / 2} Z`);
|
||||
return;
|
||||
}
|
||||
function drawCells(cells, type) {
|
||||
debug
|
||||
.select('#controlCells')
|
||||
.selectAll(`polygon.${type}`)
|
||||
.data(cells.filter((i) => pack.cells.i[i]))
|
||||
.join('polygon')
|
||||
.attr('points', (d) => getPackPolygon(d))
|
||||
.attr('class', type);
|
||||
}
|
||||
|
||||
const widthFactor = +document.getElementById('riverWidthFactor').value;
|
||||
const sourceWidth = +document.getElementById('riverSourceWidth').value;
|
||||
const [path, length, offset] = Rivers.getPath(points, widthFactor, sourceWidth);
|
||||
function dragControlPoint() {
|
||||
const {i, r, fl} = pack.cells;
|
||||
const river = getRiver();
|
||||
|
||||
const initCell = +this.dataset.cell;
|
||||
const index = +this.dataset.i;
|
||||
|
||||
let movedToCell = null;
|
||||
|
||||
d3.event.on('drag', function () {
|
||||
const {x, y} = d3.event;
|
||||
const currentCell = findCell(x, y);
|
||||
|
||||
movedToCell = initCell !== currentCell ? currentCell : null;
|
||||
|
||||
this.setAttribute('cx', x);
|
||||
this.setAttribute('cy', y);
|
||||
this.__data__ = [rn(x, 1), rn(y, 1)];
|
||||
redrawRiver();
|
||||
});
|
||||
|
||||
d3.event.on('end', () => {
|
||||
if (movedToCell) {
|
||||
this.dataset.cell = movedToCell;
|
||||
river.cells[index] = movedToCell;
|
||||
drawCells(river.cells, 'current');
|
||||
|
||||
if (!r[movedToCell]) {
|
||||
// swap river data
|
||||
r[initCell] = 0;
|
||||
r[movedToCell] = river.i;
|
||||
const sourceFlux = fl[initCell];
|
||||
fl[initCell] = fl[movedToCell];
|
||||
fl[movedToCell] = sourceFlux;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function redrawRiver() {
|
||||
const river = getRiver();
|
||||
river.points = debug.selectAll('#controlPoints > *').data();
|
||||
const {cells, widthFactor, sourceWidth} = river;
|
||||
const meanderedPoints = Rivers.addMeandering(cells, river.points);
|
||||
|
||||
lineGen.curve(d3.curveCatmullRom.alpha(0.1));
|
||||
const path = Rivers.getRiverPath(meanderedPoints, widthFactor, sourceWidth);
|
||||
elSelected.attr('d', path);
|
||||
|
||||
const r = getRiver();
|
||||
if (r) {
|
||||
r.width = rn(offset ** 2, 2);
|
||||
r.length = length;
|
||||
updateRiverData();
|
||||
}
|
||||
|
||||
updateRiverLength(river);
|
||||
if (modules.elevation) showEPForRiver(elSelected.node());
|
||||
}
|
||||
|
||||
function clickControlPoint() {
|
||||
this.remove();
|
||||
redrawRiver();
|
||||
}
|
||||
|
||||
function addInterimControlPoint() {
|
||||
const point = d3.mouse(this);
|
||||
const controls = document.getElementById('controlPoints').querySelectorAll('circle');
|
||||
const points = Array.from(controls).map((circle) => [+circle.getAttribute('cx'), +circle.getAttribute('cy')]);
|
||||
const index = getSegmentId(points, point, 2);
|
||||
addControlPoint(point, ':nth-child(' + (index + 1) + ')');
|
||||
|
||||
redrawRiver();
|
||||
}
|
||||
|
||||
function changeName() {
|
||||
getRiver().name = this.value;
|
||||
}
|
||||
|
|
@ -174,12 +196,16 @@ function editRiver(id) {
|
|||
}
|
||||
|
||||
function changeSourceWidth() {
|
||||
getRiver().sourceWidth = +this.value;
|
||||
const river = getRiver();
|
||||
river.sourceWidth = +this.value;
|
||||
updateRiverWidth(river);
|
||||
redrawRiver();
|
||||
}
|
||||
|
||||
function changeWidthFactor() {
|
||||
getRiver().widthFactor = +this.value;
|
||||
const river = getRiver();
|
||||
river.widthFactor = +this.value;
|
||||
updateRiverWidth(river);
|
||||
redrawRiver();
|
||||
}
|
||||
|
||||
|
|
@ -194,81 +220,35 @@ function editRiver(id) {
|
|||
editNotes(id, river.name + ' ' + river.type);
|
||||
}
|
||||
|
||||
function toggleRiverCreationMode() {
|
||||
if (document.getElementById('riverNew').classList.contains('pressed')) exitRiverCreationMode();
|
||||
else {
|
||||
document.getElementById('riverNew').classList.add('pressed');
|
||||
tip('Click on map to add control points', true, 'warn');
|
||||
viewbox.on('click', addPointOnClick).style('cursor', 'crosshair');
|
||||
elSelected.on('click', null);
|
||||
}
|
||||
}
|
||||
|
||||
function addPointOnClick() {
|
||||
if (!elSelected.attr('data-new')) {
|
||||
debug.select('#controlPoints').selectAll('circle').remove();
|
||||
const id = getNextId('river');
|
||||
elSelected = d3.select(elSelected.node().parentNode).append('path').attr('id', id).attr('data-new', 1);
|
||||
}
|
||||
|
||||
// add control point
|
||||
const point = d3.mouse(this);
|
||||
addControlPoint([point[0], point[1]]);
|
||||
redrawRiver();
|
||||
}
|
||||
|
||||
function exitRiverCreationMode() {
|
||||
riverNew.classList.remove('pressed');
|
||||
clearMainTip();
|
||||
viewbox.on('click', clicked).style('cursor', 'default');
|
||||
elSelected.on('click', addInterimControlPoint);
|
||||
|
||||
if (!elSelected.attr('data-new')) return; // no need to create a new river
|
||||
elSelected.attr('data-new', null);
|
||||
|
||||
// add a river
|
||||
const r = +elSelected.attr('id').slice(5);
|
||||
const node = elSelected.node(),
|
||||
length = node.getTotalLength() / 2;
|
||||
|
||||
const cells = [];
|
||||
const segments = Math.ceil(length / 4),
|
||||
increment = rn((length / segments) * 1e5);
|
||||
for (let i = increment * segments, c = i; i >= 0; i -= increment, c += increment) {
|
||||
const p = node.getPointAtLength(i / 1e5);
|
||||
const cell = findCell(p.x, p.y);
|
||||
if (!pack.cells.r[cell]) pack.cells.r[cell] = r;
|
||||
cells.push(cell);
|
||||
}
|
||||
|
||||
const source = cells[0],
|
||||
mouth = last(cells);
|
||||
const name = Rivers.getName(mouth);
|
||||
const smallLength = pack.rivers.map((r) => r.length || 0).sort((a, b) => a - b)[Math.ceil(pack.rivers.length * 0.15)];
|
||||
const type = length < smallLength ? rw({Creek: 9, River: 3, Brook: 3, Stream: 1}) : 'River';
|
||||
|
||||
const discharge = rn(cells.length * 20 * Math.random());
|
||||
const widthFactor = +document.getElementById('riverWidthFactor').value;
|
||||
const sourceWidth = +document.getElementById('riverSourceWidth').value;
|
||||
|
||||
pack.rivers.push({i: r, source, mouth, discharge, length, width: sourceWidth, widthFactor, sourceWidth, parent: 0, name, type, basin: r});
|
||||
}
|
||||
|
||||
function removeRiver() {
|
||||
const message = 'Are you sure you want to remove the river? <br>All tributaries will be auto-removed';
|
||||
const onConfirm = () => {
|
||||
const river = +elSelected.attr('id').slice(5);
|
||||
Rivers.remove(river);
|
||||
elSelected.remove(); // if river if missed in pack.rivers
|
||||
$('#riverEditor').dialog('close');
|
||||
};
|
||||
confirmationDialog({title: 'Remove river', message, confirm: 'Remove', onConfirm});
|
||||
alertMessage.innerHTML = 'Are you sure you want to remove the river and all its tributaries';
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
width: '22em',
|
||||
title: 'Remove river and tributaries',
|
||||
buttons: {
|
||||
Remove: function () {
|
||||
$(this).dialog('close');
|
||||
const river = +elSelected.attr('id').slice(5);
|
||||
Rivers.remove(river);
|
||||
elSelected.remove();
|
||||
$('#riverEditor').dialog('close');
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function closeRiverEditor() {
|
||||
exitRiverCreationMode();
|
||||
elSelected.on('click', null);
|
||||
debug.select('#controlPoints').remove();
|
||||
debug.select('#controlCells').remove();
|
||||
unselect();
|
||||
clearMainTip();
|
||||
|
||||
const forced = +document.getElementById('toggleCells').dataset.forced;
|
||||
document.getElementById('toggleCells').dataset.forced = 0;
|
||||
if (forced && layerIsOn('toggleCells')) toggleCells();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ function overviewRivers() {
|
|||
// add listeners
|
||||
document.getElementById('riversOverviewRefresh').addEventListener('click', riversOverviewAddLines);
|
||||
document.getElementById('addNewRiver').addEventListener('click', toggleAddRiver);
|
||||
document.getElementById('riverCreateNew').addEventListener('click', createRiver);
|
||||
document.getElementById('riversBasinHighlight').addEventListener('click', toggleBasinsHightlight);
|
||||
document.getElementById('riversExport').addEventListener('click', downloadRiversData);
|
||||
document.getElementById('riversRemoveAll').addEventListener('click', triggerAllRiversRemove);
|
||||
|
|
@ -129,27 +130,53 @@ function overviewRivers() {
|
|||
}
|
||||
|
||||
function openRiverEditor() {
|
||||
editRiver('river' + this.parentNode.dataset.id);
|
||||
const id = 'river' + this.parentNode.dataset.id;
|
||||
editRiver(id);
|
||||
}
|
||||
|
||||
function triggerRiverRemove() {
|
||||
const river = +this.parentNode.dataset.id;
|
||||
alertMessage.innerHTML = `Are you sure you want to remove the river?
|
||||
All tributaries will be auto-removed`;
|
||||
|
||||
const message = 'Are you sure you want to remove the river? <br>All tributaries will be auto-removed';
|
||||
const onConfirm = () => {
|
||||
Rivers.remove(river);
|
||||
riversOverviewAddLines();
|
||||
};
|
||||
confirmationDialog({title: 'Remove river', message, confirm: 'Remove', onConfirm});
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
width: '22em',
|
||||
title: 'Remove river',
|
||||
buttons: {
|
||||
Remove: function () {
|
||||
Rivers.remove(river);
|
||||
riversOverviewAddLines();
|
||||
$(this).dialog('close');
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function triggerAllRiversRemove() {
|
||||
const message = 'Are you sure you want to remove all rivers? <br>This action cannot be reverted';
|
||||
const onConfirm = () => {
|
||||
pack.rivers = [];
|
||||
rivers.selectAll('*').remove();
|
||||
riversOverviewAddLines();
|
||||
};
|
||||
confirmationDialog({title: 'Remove all rivers', message, confirm: 'Remove', onConfirm});
|
||||
alertMessage.innerHTML = `Are you sure you want to remove all rivers?`;
|
||||
$('#alert').dialog({
|
||||
resizable: false,
|
||||
title: 'Remove all rivers',
|
||||
buttons: {
|
||||
Remove: function () {
|
||||
$(this).dialog('close');
|
||||
removeAllRivers();
|
||||
},
|
||||
Cancel: function () {
|
||||
$(this).dialog('close');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function removeAllRivers() {
|
||||
pack.rivers = [];
|
||||
pack.cells.r = new Uint16Array(pack.cells.i.length);
|
||||
rivers.selectAll('*').remove();
|
||||
riversOverviewAddLines();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -849,18 +849,21 @@ function editStates() {
|
|||
}
|
||||
|
||||
function adjustProvinces(affectedProvinces) {
|
||||
const cells = pack.cells,
|
||||
provinces = pack.provinces,
|
||||
states = pack.states;
|
||||
const {cells, provinces, states} = pack;
|
||||
const form = {Zone: 1, Area: 1, Territory: 2, Province: 1};
|
||||
|
||||
<<<<<<< HEAD
|
||||
affectedProvinces.forEach((p) => {
|
||||
// do nothing if neutral lands are captured
|
||||
if (!p) return;
|
||||
=======
|
||||
affectedProvinces.forEach(p => {
|
||||
if (!p) return; // do nothing if neutral lands are captured
|
||||
const old = provinces[p].state;
|
||||
>>>>>>> 597f9ae038fbcc149315df9b1618e64744fb929d
|
||||
|
||||
// remove province from state provinces list
|
||||
const old = provinces[p].state;
|
||||
if (states[old].provinces.includes(p)) states[old].provinces.splice(states[old].provinces.indexOf(p), 1);
|
||||
if (states[old]?.provinces?.includes(p)) states[old].provinces.splice(states[old].provinces.indexOf(p), 1);
|
||||
|
||||
// find states owning at least 1 province cell
|
||||
const provCells = cells.i.filter((i) => cells.province[i] === p);
|
||||
|
|
@ -871,8 +874,13 @@ function editStates() {
|
|||
if (owner) {
|
||||
const name = provinces[p].name;
|
||||
|
||||
<<<<<<< HEAD
|
||||
// if province is historical part of abouther state province, unite with old province
|
||||
const part = states[owner].provinces.find((n) => name.includes(provinces[n].name));
|
||||
=======
|
||||
// if province is a historical part of another state's province, unite with old province
|
||||
const part = states[owner].provinces.find(n => name.includes(provinces[n].name));
|
||||
>>>>>>> 597f9ae038fbcc149315df9b1618e64744fb929d
|
||||
if (part) {
|
||||
provinces[p].removed = true;
|
||||
provCells.filter((i) => cells.state[i] === owner).forEach((i) => (cells.province[i] = part));
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -551,94 +551,120 @@ function toggleAddRiver() {
|
|||
}
|
||||
|
||||
function addRiverOnClick() {
|
||||
const cells = pack.cells;
|
||||
const point = d3.mouse(this);
|
||||
let i = findCell(point[0], point[1]);
|
||||
if (cells.r[i] || cells.h[i] < 20 || cells.b[i]) return;
|
||||
const {cells, rivers} = pack;
|
||||
let i = findCell(...d3.mouse(this));
|
||||
|
||||
const dataRiver = []; // to store river points
|
||||
let river = +getNextId('river').slice(5); // river id
|
||||
cells.fl[i] = grid.cells.prec[cells.g[i]]; // initial flux
|
||||
if (cells.r[i]) return tip('There is already a river here', false, 'error');
|
||||
if (cells.h[i] < 20) return tip('Cannot create river in water cell', false, 'error');
|
||||
if (cells.b[i]) return;
|
||||
|
||||
const h = Rivers.alterHeights();
|
||||
Lakes.prepareLakeData(h);
|
||||
Rivers.resolveDepressions(h);
|
||||
const {alterHeights, resolveDepressions, addMeandering, getRiverPath, getBasin, getName, getType, getWidth, getOffset, getApproximateLength} = Rivers;
|
||||
const riverCells = [];
|
||||
let riverId = rivers.length ? last(rivers).i + 1 : 1;
|
||||
let parent = riverId;
|
||||
|
||||
const initialFlux = grid.cells.prec[cells.g[i]];
|
||||
cells.fl[i] = initialFlux;
|
||||
|
||||
const h = alterHeights();
|
||||
resolveDepressions(h);
|
||||
|
||||
while (i) {
|
||||
cells.r[i] = river;
|
||||
const [x, y] = cells.p[i];
|
||||
dataRiver.push({x, y, cell: i});
|
||||
cells.r[i] = riverId;
|
||||
riverCells.push(i);
|
||||
|
||||
const min = cells.c[i].sort((a, b) => h[a] - h[b])[0]; // downhill cell
|
||||
if (h[i] <= h[min]) return tip(`Cell ${i} is depressed, river cannot flow further`, false, 'error');
|
||||
|
||||
const [tx, ty] = cells.p[min];
|
||||
|
||||
// pour to water body
|
||||
if (h[min] < 20) {
|
||||
// pour to water body
|
||||
dataRiver.push({x: tx, y: ty, cell: i});
|
||||
riverCells.push(min);
|
||||
|
||||
const feature = pack.features[cells.f[min]];
|
||||
if (feature.type === 'lake') {
|
||||
if (feature.outlet) parent = feature.outlet;
|
||||
feature.inlets ? feature.inlets.push(riverId) : (feature.inlets = [riverId]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// pour outside of map from border cell
|
||||
if (cells.b[min]) {
|
||||
cells.fl[min] += cells.fl[i];
|
||||
riverCells.push(-1);
|
||||
break;
|
||||
}
|
||||
|
||||
// continue propagation if min cell has no river
|
||||
if (!cells.r[min]) {
|
||||
// continue if next cell has not river
|
||||
cells.fl[min] += cells.fl[i];
|
||||
i = min;
|
||||
continue;
|
||||
}
|
||||
|
||||
// handle case when lowest cell already has a river
|
||||
const r = cells.r[min];
|
||||
const riverCells = cells.i.filter((i) => cells.r[i] === r);
|
||||
const riverCellsUpper = riverCells.filter((i) => h[i] > h[min]);
|
||||
const oldRiverId = cells.r[min];
|
||||
const oldRiver = rivers.find((river) => river.i === oldRiverId);
|
||||
const oldRiverCells = oldRiver?.cells || cells.i.filter((i) => cells.r[i] === oldRiverId);
|
||||
const oldRiverCellsUpper = oldRiverCells.filter((i) => h[i] > h[min]);
|
||||
|
||||
// finish new river if old river is longer
|
||||
if (dataRiver.length <= riverCellsUpper.length) {
|
||||
// create new river as a tributary
|
||||
if (riverCells.length <= oldRiverCellsUpper.length) {
|
||||
cells.conf[min] += cells.fl[i];
|
||||
dataRiver.push({x: tx, y: ty, cell: min});
|
||||
dataRiver[0].parent = r; // new river is tributary
|
||||
riverCells.push(min);
|
||||
parent = oldRiverId;
|
||||
break;
|
||||
}
|
||||
|
||||
// extend old river
|
||||
rivers.select('#river' + r).remove();
|
||||
cells.i.filter((i) => cells.r[i] === river).forEach((i) => (cells.r[i] = r));
|
||||
riverCells.forEach((i) => (cells.r[i] = 0));
|
||||
river = r;
|
||||
cells.fl[min] = cells.fl[i] + grid.cells.prec[cells.g[min]];
|
||||
i = min;
|
||||
// continue old river
|
||||
document.getElementById('river' + oldRiverId)?.remove();
|
||||
riverCells.forEach((i) => (cells.r[i] = oldRiverId));
|
||||
oldRiverCells.forEach((cell) => {
|
||||
if (h[cell] > h[min]) {
|
||||
cells.r[cell] = 0;
|
||||
cells.fl[cell] = grid.cells.prec[cells.g[cell]];
|
||||
} else {
|
||||
riverCells.push(cell);
|
||||
cells.fl[cell] += cells.fl[i];
|
||||
}
|
||||
});
|
||||
riverId = oldRiverId;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
const points = Rivers.addMeandering(dataRiver, 1, 0.5);
|
||||
const widthFactor = rn(0.8 + Math.random() * 0.4, 1); // river width modifier [.8, 1.2]
|
||||
const sourceWidth = 0.1;
|
||||
const [path, length, offset] = Rivers.getPath(points, widthFactor, sourceWidth);
|
||||
rivers
|
||||
.append('path')
|
||||
.attr('d', path)
|
||||
.attr('id', 'river' + river);
|
||||
const river = rivers.find((r) => r.i === riverId);
|
||||
|
||||
// add new river to data or change extended river attributes
|
||||
const r = pack.rivers.find((r) => r.i === river);
|
||||
const mouth = last(dataRiver).cell;
|
||||
const discharge = cells.fl[mouth]; // in m3/s
|
||||
const source = riverCells[0];
|
||||
const mouth = riverCells[riverCells.length - 2];
|
||||
const widthFactor = river?.widthFactor || (!parent || parent === riverId ? 1.2 : 1);
|
||||
const meanderedPoints = addMeandering(riverCells);
|
||||
|
||||
if (r) {
|
||||
r.source = dataRiver[0].cell;
|
||||
r.length = length;
|
||||
r.discharge = discharge;
|
||||
const discharge = cells.fl[mouth]; // m3 in second
|
||||
const length = getApproximateLength(meanderedPoints);
|
||||
const width = getWidth(getOffset(discharge, meanderedPoints.length, widthFactor));
|
||||
|
||||
if (river) {
|
||||
river.source = source;
|
||||
river.length = length;
|
||||
river.discharge = discharge;
|
||||
river.width = width;
|
||||
river.cells = riverCells;
|
||||
} else {
|
||||
const parent = dataRiver[0].parent || 0;
|
||||
const basin = Rivers.getBasin(river);
|
||||
const source = dataRiver[0].cell;
|
||||
const width = rn(offset ** 2, 2); // mounth width in km
|
||||
const name = Rivers.getName(mouth);
|
||||
const smallLength = pack.rivers.map((r) => r.length || 0).sort((a, b) => a - b)[Math.ceil(pack.rivers.length * 0.15)];
|
||||
const type = length < smallLength ? rw({Creek: 9, River: 3, Brook: 3, Stream: 1}) : 'River';
|
||||
const basin = getBasin(parent);
|
||||
const name = getName(mouth);
|
||||
const type = getType({i: riverId, length, parent});
|
||||
|
||||
pack.rivers.push({i: river, source, mouth, discharge, length, width, widthFactor, sourceWidth, parent, basin, name, type});
|
||||
rivers.push({i: riverId, source, mouth, discharge, length, width, widthFactor, sourceWidth: 0, parent, cells: riverCells, basin, name, type});
|
||||
}
|
||||
|
||||
// render river
|
||||
lineGen.curve(d3.curveCatmullRom.alpha(0.1));
|
||||
const path = getRiverPath(meanderedPoints, widthFactor);
|
||||
const id = 'river' + riverId;
|
||||
const riversG = viewbox.select('#rivers');
|
||||
riversG.append('path').attr('id', id).attr('d', path);
|
||||
|
||||
if (d3.event.shiftKey === false) {
|
||||
Lakes.cleanupLakeData();
|
||||
unpressClickToAddButton();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
"use strict";
|
||||
|
||||
function editZones() {
|
||||
closeDialogs();
|
||||
if (!layerIsOn("toggleZones")) toggleZones();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue