Merge pull request #818 from Azgaar/insulate

Template editor update
This commit is contained in:
Azgaar 2022-05-22 00:14:59 +03:00 committed by GitHub
commit b144191621
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 375 additions and 273 deletions

View file

@ -922,7 +922,7 @@ fieldset {
display: inline-block; display: inline-block;
} }
body button.noicon { #templateTools > button {
width: 1.8em; width: 1.8em;
height: 1.6em; height: 1.6em;
margin: 1px; margin: 1px;

View file

@ -107,7 +107,7 @@
} }
} }
</style> </style>
<link rel="stylesheet" href="index.css?v=18052022" /> <link rel="stylesheet" href="index.css?v=21052022" />
<link rel="stylesheet" href="icons.css" /> <link rel="stylesheet" href="icons.css" />
<link rel="stylesheet" href="libs/jquery-ui.css" /> <link rel="stylesheet" href="libs/jquery-ui.css" />
</head> </head>
@ -1292,7 +1292,7 @@
<option value="volcano">Volcano</option> <option value="volcano">Volcano</option>
<option value="highIsland">High Island</option> <option value="highIsland">High Island</option>
<option value="lowIsland">Low Island</option> <option value="lowIsland">Low Island</option>
<option value="continents">Two Continents</option> <option value="continents">Continents</option>
<option value="archipelago">Archipelago</option> <option value="archipelago">Archipelago</option>
<option value="atoll">Atoll</option> <option value="atoll">Atoll</option>
<option value="mediterranean">Mediterranean</option> <option value="mediterranean">Mediterranean</option>
@ -1302,6 +1302,7 @@
<option value="shattered">Shattered</option> <option value="shattered">Shattered</option>
<option value="taklamakan">Taklamakan</option> <option value="taklamakan">Taklamakan</option>
<option value="oldWorld">Old World</option> <option value="oldWorld">Old World</option>
<option value="fractious">Fractious</option>
</optgroup> </optgroup>
<optgroup label="Specific"> <optgroup label="Specific">
<option value="africa-centric">Africa Centric</option> <option value="africa-centric">Africa Centric</option>
@ -1758,13 +1759,13 @@
<label for="allowErosion" class="checkbox-label">Allow water erosion</label> <label for="allowErosion" class="checkbox-label">Allow water erosion</label>
</div> </div>
<div data-tip="Maximum number of iterations taken to resolve depressions. Increase if you have rivers ending nowhere"> <div data-tip="Maximum number of iterations taken to resolve depressions. Increase if you have rivers ending nowhere">
Depressions filling max iterations: <div>Depressions filling max iterations:</div>
<input id="resolveDepressionsStepsInput" data-stored="resolveDepressionsSteps" type="range" min="0" max="500" value="250" /> <input id="resolveDepressionsStepsInput" data-stored="resolveDepressionsSteps" type="range" min="0" max="500" value="250" />
<input id="resolveDepressionsStepsOutput" data-stored="resolveDepressionsSteps" type="number" min="0" max="1000" value="250" /> <input id="resolveDepressionsStepsOutput" data-stored="resolveDepressionsSteps" type="number" min="0" max="1000" value="250" />
</div> </div>
<div data-tip="Depression depth to form a new lake. Increase to reduce number of lakes added by system"> <div data-tip="Depression depth to form a new lake. Increase to reduce number of lakes added by system">
Depression depth threshold: <div>Depression depth threshold:</div>
<input id="lakeElevationLimitInput" data-stored="lakeElevationLimit" type="range" min="0" max="80" value="20" /> <input id="lakeElevationLimitInput" data-stored="lakeElevationLimit" type="range" min="0" max="80" value="20" />
<input id="lakeElevationLimitOutput" data-stored="lakeElevationLimit" type="number" min="0" max="80" value="20" /> <input id="lakeElevationLimitOutput" data-stored="lakeElevationLimit" type="number" min="0" max="80" value="20" />
</div> </div>
@ -2932,13 +2933,13 @@
<div id="templateEditor" class="dialog stable" style="display: none"> <div id="templateEditor" class="dialog stable" style="display: none">
<div id="templateTop"> <div id="templateTop">
<i>Select template: </i <i>Select template: </i>
><select id="templateSelect" style="width: 16em" data-prev="templateCustom" data-tip="Select base template"> <select id="templateSelect" style="width: 16em" data-prev="templateCustom" data-tip="Select base template">
<option value="custom" selected>Custom</option> <option value="custom" selected>Custom</option>
<option value="volcano">Volcano</option> <option value="volcano">Volcano</option>
<option value="highIsland">High Island</option> <option value="highIsland">High Island</option>
<option value="lowIsland">Low Island</option> <option value="lowIsland">Low Island</option>
<option value="continents">Two Continents</option> <option value="continents">Continents</option>
<option value="archipelago">Archipelago</option> <option value="archipelago">Archipelago</option>
<option value="atoll">Atoll</option> <option value="atoll">Atoll</option>
<option value="mediterranean">Mediterranean</option> <option value="mediterranean">Mediterranean</option>
@ -2948,17 +2949,20 @@
<option value="shattered">Shattered</option> <option value="shattered">Shattered</option>
<option value="taklamakan">Taklamakan</option> <option value="taklamakan">Taklamakan</option>
<option value="oldWorld">Old World</option> <option value="oldWorld">Old World</option>
<option value="fractious">Fractious</option>
</select> </select>
</div> </div>
<div id="templateTools"> <div id="templateTools">
<button id="templateHill" data-tip="Hill: small blob" class="noicon">H</button> <button data-type="Hill" data-tip="Hill: small blob">H</button>
<button id="templatePit" data-tip="Pit: round depression" class="noicon">P</button> <button data-type="Pit" data-tip="Pit: round depression">P</button>
<button id="templateRange" data-tip="Range: elongated elevation" class="noicon">R</button> <button data-type="Range" data-tip="Range: elongated elevation">R</button>
<button id="templateTrough" data-tip="Trough: elongated depression" class="noicon">T</button> <button data-type="Trough" data-tip="Trough: elongated depression">T</button>
<button id="templateStrait" data-tip="Strait: centered vertical or horizontal depression" class="noicon">S</button> <button data-type="Strait" data-tip="Strait: centered vertical or horizontal depression">S</button>
<button id="templateAdd" data-tip="Add or subtract value from all heights in range" class="noicon">+</button> <button data-type="Mask" data-tip="Mask: lower cells near edges or in map center">M</button>
<button id="templateMultiply" data-tip="Multiply all heights in range by factor" class="noicon">*</button> <button data-type="Invert" data-tip="Invert heightmap along the axes">I</button>
<button id="templateSmooth" data-tip="Smooth the map replacing cell heights by an average values of its neighbors" class="noicon">~</button> <button data-type="Add" data-tip="Add or subtract value from all heights in range">+</button>
<button data-type="Multiply" data-tip="Multiply all heights in range by factor">*</button>
<button data-type="Smooth" data-tip="Smooth the map replacing cell heights by an average values of its neighbors">~</button>
</div> </div>
<div id="templateBody" data-changed="0" class="table" style="padding: 2px 0"> <div id="templateBody" data-changed="0" class="table" style="padding: 2px 0">
<div data-type="Hill"> <div data-type="Hill">
@ -2973,7 +2977,7 @@
</div> </div>
</div> </div>
<div id="templateBottom"> <div id="templateBottom">
<button id="templateRun" data-tip="Apply current template" class="icon-play-circled2"></button> <button id="templateRun" data-tip="Execute the template" class="icon-play-circled2"></button>
<button id="templateUndo" data-tip="Undo the latest action" class="icon-ccw" disabled></button> <button id="templateUndo" data-tip="Undo the latest action" class="icon-ccw" disabled></button>
<button id="templateRedo" data-tip="Redo the action" class="icon-cw" disabled></button> <button id="templateRedo" data-tip="Redo the action" class="icon-cw" disabled></button>
<button id="templateSave" data-tip="Download the template as a text file" class="icon-download"></button> <button id="templateSave" data-tip="Download the template as a text file" class="icon-download"></button>
@ -2985,6 +2989,9 @@
onclick="openURL('https://cartographyassets.com/asset-category/specific-assets/azgaars-generator/templates')" onclick="openURL('https://cartographyassets.com/asset-category/specific-assets/azgaars-generator/templates')"
></button> ></button>
<button id="templateTutorial" data-tip="Open Template Editor Tutorial" class="icon-info" onclick="wiki('Heightmap-template-editor')"></button> <button id="templateTutorial" data-tip="Open Template Editor Tutorial" class="icon-info" onclick="wiki('Heightmap-template-editor')"></button>
<label data-tip="Set seed if you want template to generate the same heightmap each time. Leave blank to use random seed">
Seed: <input id="templateSeed" value="" type="number" min="1" max="999999999" step="1" style="width: 7em" />
</label>
</div> </div>
</div> </div>
@ -6109,8 +6116,8 @@
<script src="utils/unitUtils.js"></script> <script src="utils/unitUtils.js"></script>
<script src="modules/voronoi.js"></script> <script src="modules/voronoi.js"></script>
<script src="modules/heightmap-templates.js"></script> <script src="modules/heightmap-templates.js?v=21052020"></script>
<script src="modules/heightmap-generator.js"></script> <script src="modules/heightmap-generator.js?v=21052020"></script>
<script src="modules/ocean-layers.js"></script> <script src="modules/ocean-layers.js"></script>
<script src="modules/river-generator.js"></script> <script src="modules/river-generator.js"></script>
<script src="modules/lakes.js"></script> <script src="modules/lakes.js"></script>
@ -6140,7 +6147,7 @@
<script defer src="modules/ui/editors.js?v=18052022"></script> <script defer src="modules/ui/editors.js?v=18052022"></script>
<script defer src="modules/ui/tools.js"></script> <script defer src="modules/ui/tools.js"></script>
<script defer src="modules/ui/world-configurator.js"></script> <script defer src="modules/ui/world-configurator.js"></script>
<script defer src="modules/ui/heightmap-editor.js"></script> <script defer src="modules/ui/heightmap-editor.js?v=21052020"></script>
<script defer src="modules/ui/provinces-editor.js?v=19052022"></script> <script defer src="modules/ui/provinces-editor.js?v=19052022"></script>
<script defer src="modules/ui/biomes-editor.js?v=18052022"></script> <script defer src="modules/ui/biomes-editor.js?v=18052022"></script>
<script defer src="modules/ui/namesbase-editor.js"></script> <script defer src="modules/ui/namesbase-editor.js"></script>

View file

@ -26,7 +26,7 @@ window.HeightmapGenerator = (function () {
// load heightmap into image and render to canvas // load heightmap into image and render to canvas
const img = new Image(); const img = new Image();
img.src = `./heightmaps/${input.value}.png`; img.src = `./heightmaps/${input.value}.png`;
img.onload = function () { img.onload = () => {
ctx.drawImage(img, 0, 0, cellsX, cellsY); ctx.drawImage(img, 0, 0, cellsX, cellsY);
const imageData = ctx.getImageData(0, 0, cellsX, cellsY); const imageData = ctx.getImageData(0, 0, cellsX, cellsY);
assignColorsToHeight(imageData.data); assignColorsToHeight(imageData.data);
@ -61,6 +61,7 @@ window.HeightmapGenerator = (function () {
if (a1 === "Range") return addRange(a2, a3, a4, a5); if (a1 === "Range") return addRange(a2, a3, a4, a5);
if (a1 === "Trough") return addTrough(a2, a3, a4, a5); if (a1 === "Trough") return addTrough(a2, a3, a4, a5);
if (a1 === "Strait") return addStrait(a2, a3); if (a1 === "Strait") return addStrait(a2, a3);
if (a1 === "Mask") return mask(a2);
if (a1 === "Add") return modify(a3, +a2, 1); if (a1 === "Add") return modify(a3, +a2, 1);
if (a1 === "Multiply") return modify(a3, 0, +a2); if (a1 === "Multiply") return modify(a3, 0, +a2);
if (a1 === "Smooth") return smooth(a2); if (a1 === "Smooth") return smooth(a2);
@ -100,7 +101,7 @@ window.HeightmapGenerator = (function () {
if (cells === 100000) return 0.93; if (cells === 100000) return 0.93;
} }
const addHill = function (count, height, rangeX, rangeY) { const addHill = (count, height, rangeX, rangeY) => {
count = getNumberInRange(count); count = getNumberInRange(count);
const power = getBlobPower(); const power = getBlobPower();
while (count > 0) { while (count > 0) {
@ -137,7 +138,7 @@ window.HeightmapGenerator = (function () {
} }
}; };
const addPit = function (count, height, rangeX, rangeY) { const addPit = (count, height, rangeX, rangeY) => {
count = getNumberInRange(count); count = getNumberInRange(count);
while (count > 0) { while (count > 0) {
addOnePit(); addOnePit();
@ -173,7 +174,7 @@ window.HeightmapGenerator = (function () {
} }
}; };
const addRange = function (count, height, rangeX, rangeY) { const addRange = (count, height, rangeX, rangeY) => {
count = getNumberInRange(count); count = getNumberInRange(count);
const power = getLinePower(); const power = getLinePower();
while (count > 0) { while (count > 0) {
@ -259,7 +260,7 @@ window.HeightmapGenerator = (function () {
} }
}; };
const addTrough = function (count, height, rangeX, rangeY) { const addTrough = (count, height, rangeX, rangeY) => {
count = getNumberInRange(count); count = getNumberInRange(count);
const power = getLinePower(); const power = getLinePower();
while (count > 0) { while (count > 0) {
@ -354,7 +355,7 @@ window.HeightmapGenerator = (function () {
} }
}; };
const addStrait = function (width, direction = "vertical") { const addStrait = (width, direction = "vertical") => {
width = Math.min(getNumberInRange(width), grid.cellsX / 3); width = Math.min(getNumberInRange(width), grid.cellsX / 3);
if (width < 1 && P(width)) return; if (width < 1 && P(width)) return;
const used = new Uint8Array(cells.h.length); const used = new Uint8Array(cells.h.length);
@ -364,8 +365,8 @@ window.HeightmapGenerator = (function () {
const endX = vert ? Math.floor(graphWidth - startX - graphWidth * 0.1 + Math.random() * graphWidth * 0.2) : graphWidth - 5; const endX = vert ? Math.floor(graphWidth - startX - graphWidth * 0.1 + Math.random() * graphWidth * 0.2) : graphWidth - 5;
const endY = vert ? graphHeight - 5 : Math.floor(graphHeight - startY - graphHeight * 0.1 + Math.random() * graphHeight * 0.2); const endY = vert ? graphHeight - 5 : Math.floor(graphHeight - startY - graphHeight * 0.1 + Math.random() * graphHeight * 0.2);
const start = findGridCell(startX, startY), const start = findGridCell(startX, startY);
end = findGridCell(endX, endY); const end = findGridCell(endX, endY);
let range = getRange(start, end); let range = getRange(start, end);
const query = []; const query = [];
@ -407,7 +408,7 @@ window.HeightmapGenerator = (function () {
} }
}; };
const modify = function (range, add, mult, power) { const modify = (range, add, mult, power) => {
const min = range === "land" ? 20 : range === "all" ? 0 : +range.split("-")[0]; const min = range === "land" ? 20 : range === "all" ? 0 : +range.split("-")[0];
const max = range === "land" || range === "all" ? 100 : +range.split("-")[1]; const max = range === "land" || range === "all" ? 100 : +range.split("-")[1];
const isLand = min === 20; const isLand = min === 20;
@ -422,14 +423,49 @@ window.HeightmapGenerator = (function () {
}); });
}; };
const smooth = function (fr = 2, add = 0) { const smooth = (fr = 2, add = 0) => {
cells.h = cells.h.map((h, i) => { cells.h = cells.h.map((h, i) => {
const a = [h]; const a = [h];
cells.c[i].forEach(c => a.push(cells.h[c])); cells.c[i].forEach(c => a.push(cells.h[c]));
if (fr === 1) return d3.mean(a) + add;
return lim((h * (fr - 1) + d3.mean(a) + add) / fr); return lim((h * (fr - 1) + d3.mean(a) + add) / fr);
}); });
}; };
const mask = (power = 1) => {
const fr = power ? Math.abs(power) : 1;
cells.h = cells.h.map((h, i) => {
const [x, y] = p[i];
const nx = (2 * x) / graphWidth - 1; // [-1, 1], 0 is center
const ny = (2 * y) / graphHeight - 1; // [-1, 1], 0 is center
let distance = (1 - nx ** 2) * (1 - ny ** 2); // 1 is center, 0 is edge
if (power < 0) distance = 1 - distance; // inverted, 0 is center, 1 is edge
const masked = h * distance;
return lim((h * (fr - 1) + masked) / fr);
});
};
const invert = (count, axes) => {
if (!P(count)) return;
const invertX = axes !== "y";
const invertY = axes !== "x";
const {cellsX, cellsY} = grid;
const inverted = cells.h.map((h, i) => {
const x = i % cellsX;
const y = Math.floor(i / cellsX);
const nx = invertX ? cellsX - x - 1 : x;
const ny = invertY ? cellsY - y - 1 : y;
const invertedI = nx + ny * cellsX;
return cells.h[invertedI];
});
cells.h = inverted;
};
function getPointInRange(range, length) { function getPointInRange(range, length) {
if (typeof range !== "string") { if (typeof range !== "string") {
ERROR && console.error("Range should be a string"); ERROR && console.error("Range should be a string");
@ -449,5 +485,5 @@ window.HeightmapGenerator = (function () {
} }
} }
return {generate, addHill, addRange, addTrough, addStrait, addPit, smooth, modify}; return {generate, addHill, addRange, addTrough, addStrait, addPit, smooth, modify, mask, invert};
})(); })();

View file

@ -4,49 +4,55 @@ window.HeightmapTemplates = (function () {
const volcano = `Hill 1 90-100 44-56 40-60 const volcano = `Hill 1 90-100 44-56 40-60
Multiply 0.8 50-100 0 0 Multiply 0.8 50-100 0 0
Range 1.5 30-55 45-55 40-60 Range 1.5 30-55 45-55 40-60
Smooth 2 0 0 0 Smooth 3 0 0 0
Hill 1.5 25-35 25-30 20-75 Hill 1.5 35-45 25-30 20-75
Hill 1 25-35 75-80 25-75 Hill 1 35-55 75-80 25-75
Hill 0.5 20-25 10-15 20-25`; Hill 0.5 20-25 10-15 20-25
Mask 3 0 0 0`;
const highIsland = `Hill 1 90-100 65-75 47-53 const highIsland = `Hill 1 90-100 65-75 47-53
Add 5 all 0 0 Add 7 all 0 0
Hill 6 20-23 25-55 45-55 Hill 5-6 20-30 25-55 45-55
Range 1 40-50 45-55 45-55 Range 1 40-50 45-55 45-55
Multiply 0.8 land 0 0
Mask 3 0 0 0
Smooth 2 0 0 0 Smooth 2 0 0 0
Trough 2-3 20-30 20-30 20-30 Trough 2-3 20-30 20-30 20-30
Trough 2-3 20-30 60-80 70-80 Trough 2-3 20-30 60-80 70-80
Hill 1 10-15 60-60 50-50 Hill 1 10-15 60-60 50-50
Hill 1.5 13-16 15-20 20-75 Hill 1.5 13-16 15-20 20-75
Multiply 0.8 20-100 0 0
Range 1.5 30-40 15-85 30-40 Range 1.5 30-40 15-85 30-40
Range 1.5 30-40 15-85 60-70 Range 1.5 30-40 15-85 60-70
Pit 2-3 10-15 15-85 20-80`; Pit 3-5 10-30 15-85 20-80`;
const lowIsland = `Hill 1 90-99 60-80 45-55 const lowIsland = `Hill 1 90-99 60-80 45-55
Hill 4-5 25-35 20-65 40-60 Hill 1-2 20-30 10-30 10-90
Smooth 2 0 0 0
Hill 6-7 25-35 20-70 30-70
Range 1 40-50 45-55 45-55 Range 1 40-50 45-55 45-55
Smooth 3 0 0 0 Trough 2-3 20-30 15-85 20-30
Trough 1.5 20-30 15-85 20-30 Trough 2-3 20-30 15-85 70-80
Trough 1.5 20-30 15-85 70-80
Hill 1.5 10-15 5-15 20-80 Hill 1.5 10-15 5-15 20-80
Hill 1 10-15 85-95 70-80 Hill 1 10-15 85-95 70-80
Pit 3-5 10-15 15-85 20-80 Pit 5-7 15-25 15-85 20-80
Multiply 0.4 20-100 0 0`; Multiply 0.4 20-100 0 0
Mask 4 0 0 0`;
const continents = `Hill 1 80-85 75-80 40-60 const continents = `Hill 1 80-85 60-80 40-60
Hill 1 80-85 20-25 40-60 Hill 1 80-85 20-30 40-60
Multiply 0.22 20-100 0 0 Hill 6-7 15-30 25-75 15-85
Hill 5-6 15-20 25-75 20-82 Multiply 0.6 land 0 0
Range .8 30-60 5-15 20-45 Hill 8-10 5-10 15-85 20-80
Range .8 30-60 5-15 55-80 Range 1-2 30-60 5-15 25-75
Range 1-2 30-60 80-95 25-75
Range 0-3 30-60 80-90 20-80 Range 0-3 30-60 80-90 20-80
Trough 3-4 15-20 15-85 20-80
Strait 2 vertical 0 0 Strait 2 vertical 0 0
Smooth 2 0 0 0 Strait 1 vertical 0 0
Trough 1-2 5-10 45-55 45-55 Smooth 3 0 0 0
Pit 3-4 10-15 15-85 20-80 Trough 3-4 15-20 15-85 20-80
Hill 1 5-10 40-60 40-60`; Trough 3-4 5-10 45-55 45-55
Pit 3-4 10-20 15-85 20-80
Mask 4 0 0 0`;
const archipelago = `Add 11 all 0 0 const archipelago = `Add 11 all 0 0
Range 2-3 40-60 20-80 20-80 Range 2-3 40-60 20-80 20-80
@ -63,18 +69,19 @@ window.HeightmapTemplates = (function () {
Hill .5 30-50 25-35 30-70 Hill .5 30-50 25-35 30-70
Smooth 1 0 0 0 Smooth 1 0 0 0
Multiply 0.2 25-100 0 0 Multiply 0.2 25-100 0 0
Hill .5 10-20 50-55 48-52`; Hill 0.5 10-20 50-55 48-52`;
const mediterranean = `Range 3-4 30-50 0-100 0-10 const mediterranean = `Range 4-6 30-80 0-100 0-10
Range 3-4 30-50 0-100 90-100 Range 4-6 30-80 0-100 90-100
Hill 5-6 30-70 0-100 0-5 Hill 6-8 30-50 10-90 0-5
Hill 5-6 30-70 0-100 95-100 Hill 6-8 30-50 10-90 95-100
Multiply 0.9 land 0 0
Mask -2 0 0 0
Smooth 1 0 0 0 Smooth 1 0 0 0
Hill 2-3 30-70 0-5 20-80 Hill 2-3 30-70 0-5 20-80
Hill 2-3 30-70 95-100 20-80 Hill 2-3 30-70 95-100 20-80
Multiply 0.8 land 0 0 Trough 3-6 40-50 0-100 0-10
Trough 3-5 40-50 0-100 0-10 Trough 3-6 40-50 0-100 90-100`;
Trough 3-5 40-50 0-100 90-100`;
const peninsula = `Range 2-3 20-35 40-50 0-15 const peninsula = `Range 2-3 20-35 40-50 0-15
Add 5 all 0 0 Add 5 all 0 0
@ -83,7 +90,8 @@ window.HeightmapTemplates = (function () {
Hill 3-4 3-5 5-95 80-100 Hill 3-4 3-5 5-95 80-100
Hill 1-2 3-5 5-95 40-60 Hill 1-2 3-5 5-95 40-60
Trough 5-6 10-25 5-95 5-95 Trough 5-6 10-25 5-95 5-95
Smooth 3 0 0 0`; Smooth 3 0 0 0
Invert 0.4 both 0 0`;
const pangea = `Hill 1-2 25-40 15-50 0-10 const pangea = `Hill 1-2 25-40 15-50 0-10
Hill 1-2 5-40 50-85 0-10 Hill 1-2 5-40 50-85 0-10
@ -106,7 +114,8 @@ window.HeightmapTemplates = (function () {
Trough 4-8 15-30 10-50 20-40 Trough 4-8 15-30 10-50 20-40
Trough 4-8 15-30 30-70 40-60 Trough 4-8 15-30 30-70 40-60
Trough 4-8 15-30 50-90 60-80 Trough 4-8 15-30 50-90 60-80
Trough 4-8 15-30 70-100 80-100`; Trough 4-8 15-30 70-100 80-100
Invert 0.25 x 0 0`;
const shattered = `Hill 8 35-40 15-85 30-70 const shattered = `Hill 8 35-40 15-85 30-70
Trough 10-20 40-50 5-95 5-95 Trough 10-20 40-50 5-95 5-95
@ -117,58 +126,42 @@ window.HeightmapTemplates = (function () {
Hill 2-4 60-85 0-5 0-100 Hill 2-4 60-85 0-5 0-100
Hill 2-4 60-85 95-100 0-100 Hill 2-4 60-85 95-100 0-100
Hill 3-4 60-85 20-80 0-5 Hill 3-4 60-85 20-80 0-5
Hill 3-4 60-85 20-80 95-100`; Hill 3-4 60-85 20-80 95-100
Smooth 3 0 0 0`;
const oldWorld = `Range 3 70 15-85 20-80
const oldWorld = `Hill 4-6 20-40 15-85 30-45 Hill 2-3 50-70 15-45 20-80
Hill 3-7 20-40 15-85 55-70 Hill 2-3 50-70 65-85 20-80
Strait 2-7 vertical 0 0 Hill 4-6 20-25 15-85 20-80
Pit 1-2 40-50 35-55 20-80 Multiply 0.5 land 0 0
Strait 2-7 vertical 0 0 Smooth 2 0 0 0
Range 2-3 20-25 15-35 20-30 Range 3-4 20-50 15-35 20-45
Range 2-3 20-25 15-35 65-80 Range 2-4 20-50 65-85 45-80
Range 2-3 20-25 45-85 20-45
Range 2-3 20-25 45-85 65-80
Multiply .9 80-100 0 0
Strait 2-7 vertical 0 0
Pit 2-3 40-50 45-65 20-80
Trough 1-2 40-50 15-45 20-45
Trough 1-3 40-50 15-45 45-80
Trough 1-2 40-50 45-85 20-45
Trough 1-2 40-50 45-85 45-80
Multiply 1.2 17-20 0 0
Strait 2-7 horizontal 0 0
Multiply 1.2 17-50 0 0
Range 1-2 20-25 15-45 45-65
Range 1-2 20-25 65-85 45-80
Multiply 1.1 50-80 0 0
Hill 1-2 20 15-45 20-80
Hill 1-2 20 65-85 20-80
Multiply 1.2 15-30 0 0
Strait 2-7 vertical 0 0
Trough 1-2 40-50 35-65 65-80
Range 1-2 20-25 15-35 20-45
Strait 2-7 vertical 0 0
Range 1-2 20-25 65-85 45-80
Multiply .9 70-100 0 0
Hill 1-2 20-25 15-45 65-80
Hill 1-2 20-25 65-85 20-45
Hill 1 20-25 15-45 45-65
Hill 1 20-25 65-85 45-65
Strait 2-7 vertical 0 0
Trough 1-2 20-50 15-45 45-65
Trough 1-2 20-50 65-85 45-65
Strait 2-7 horizontal 0 0
Multiply 0.8 70-100 0 0
Hill 1-2 20-25 35-45 45-65
Hill 1-2 20-25 65-70 45-65
Pit 2-3 40-50 45-65 30-70
Trough 1-2 40-50 15-85 65-80
Trough 1-2 40-50 15-85 10-35
Strait 2-5 vertical 0 0
Multiply 1.1 45-90 0 0
Strait 3-7 vertical 0 0 Strait 3-7 vertical 0 0
Trough 1-2 40-50 45-65 45-65`; Trough 6-8 20-50 15-85 45-65
Pit 5-6 20-30 10-90 10-90`;
return {volcano, highIsland, lowIsland, continents, archipelago, atoll, mediterranean, peninsula, peninsula, pangea, isthmus, shattered, taklamakan, oldWorld}; const fractious = `Hill 12-15 50-80 5-95 5-95
Mask -1.5 0 0 0
Mask 3 0 0 0
Add -20 30-100 0 0
Range 6-8 40-50 5-95 10-90`;
return {
volcano,
highIsland,
lowIsland,
continents,
archipelago,
atoll,
mediterranean,
peninsula,
peninsula,
pangea,
isthmus,
shattered,
taklamakan,
oldWorld,
fractious
};
})(); })();

View file

@ -34,19 +34,19 @@ function editHeightmap() {
modules.editHeightmap = true; modules.editHeightmap = true;
// add listeners // add listeners
document.getElementById("paintBrushes").addEventListener("click", openBrushesPanel); byId("paintBrushes").on("click", openBrushesPanel);
document.getElementById("applyTemplate").addEventListener("click", openTemplateEditor); byId("applyTemplate").on("click", openTemplateEditor);
document.getElementById("convertImage").addEventListener("click", openImageConverter); byId("convertImage").on("click", openImageConverter);
document.getElementById("heightmapPreview").addEventListener("click", toggleHeightmapPreview); byId("heightmapPreview").on("click", toggleHeightmapPreview);
document.getElementById("heightmap3DView").addEventListener("click", changeViewMode); byId("heightmap3DView").on("click", changeViewMode);
document.getElementById("finalizeHeightmap").addEventListener("click", finalizeHeightmap); byId("finalizeHeightmap").on("click", finalizeHeightmap);
document.getElementById("renderOcean").addEventListener("click", mockHeightmap); byId("renderOcean").on("click", mockHeightmap);
document.getElementById("templateUndo").addEventListener("click", () => restoreHistory(edits.n - 1)); byId("templateUndo").on("click", () => restoreHistory(edits.n - 1));
document.getElementById("templateRedo").addEventListener("click", () => restoreHistory(edits.n + 1)); byId("templateRedo").on("click", () => restoreHistory(edits.n + 1));
function enterHeightmapEditMode(type) { function enterHeightmapEditMode(type) {
editHeightmap.layers = Array.from(mapLayers.querySelectorAll("li:not(.buttonoff)")).map(node => node.id); // store layers preset editHeightmap.layers = Array.from(mapLayers.querySelectorAll("li:not(.buttonoff)")).map(node => node.id); // store layers preset
editHeightmap.layers.forEach(l => document.getElementById(l).click()); // turn off all layers editHeightmap.layers.forEach(l => byId(l).click()); // turn off all layers
customization = 1; customization = 1;
closeDialogs(); closeDialogs();
@ -113,7 +113,7 @@ function editHeightmap() {
if (tooltip.dataset.main) showMainTip(); if (tooltip.dataset.main) showMainTip();
// move radius circle if drag mode is active // move radius circle if drag mode is active
const pressed = document.getElementById("brushesButtons").querySelector("button.pressed"); const pressed = byId("brushesButtons").querySelector("button.pressed");
if (!pressed) return; if (!pressed) return;
moveCircle(p[0], p[1], brushRadius.valueAsNumber, "#333"); moveCircle(p[0], p[1], brushRadius.valueAsNumber, "#333");
} }
@ -137,7 +137,7 @@ function editHeightmap() {
function finalizeHeightmap() { function finalizeHeightmap() {
if (viewbox.select("#heights").selectAll("*").size() < 200) 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"); 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"); if (byId("imageConverter").offsetParent) return tip("Please exit the Image Conversion mode first", null, "error");
delete window.edits; // remove global variable delete window.edits; // remove global variable
redo.disabled = templateRedo.disabled = true; redo.disabled = templateRedo.disabled = true;
@ -145,7 +145,7 @@ function editHeightmap() {
customization = 0; customization = 0;
customizationMenu.style.display = "none"; customizationMenu.style.display = "none";
if (document.getElementById("options").querySelector(".tab > button.active").id === "toolsTab") toolsContent.style.display = "block"; if (byId("options").querySelector(".tab > button.active").id === "toolsTab") toolsContent.style.display = "block";
layersPreset.disabled = false; layersPreset.disabled = false;
exitCustomization.style.display = "none"; // hide finalize button exitCustomization.style.display = "none"; // hide finalize button
restoreDefaultEvents(); restoreDefaultEvents();
@ -153,8 +153,8 @@ function editHeightmap() {
closeDialogs(); closeDialogs();
resetZoom(); resetZoom();
if (document.getElementById("preview")) document.getElementById("preview").remove(); if (byId("preview")) byId("preview").remove();
if (document.getElementById("canvas3d")) enterStandardView(); if (byId("canvas3d")) enterStandardView();
const mode = heightmapEditMode.innerHTML; const mode = heightmapEditMode.innerHTML;
if (mode === "erase") regenerateErasedData(); if (mode === "erase") regenerateErasedData();
@ -163,7 +163,7 @@ function editHeightmap() {
// restore initial layers // restore initial layers
//viewbox.select("#heights").remove(); //viewbox.select("#heights").remove();
document.getElementById("heights").remove(); byId("heights").remove();
turnButtonOff("toggleHeight"); turnButtonOff("toggleHeight");
document document
.getElementById("mapLayers") .getElementById("mapLayers")
@ -487,8 +487,8 @@ function editHeightmap() {
function updateStatistics() { function updateStatistics() {
const landCells = grid.cells.h.reduce((s, h) => (h >= 20 ? s + 1 : s)); const landCells = grid.cells.h.reduce((s, h) => (h >= 20 ? s + 1 : s));
landmassCounter.innerHTML = /* html */ `${landCells} (${rn((landCells / grid.cells.i.length) * 100)}%)`; byId("landmassCounter").innerText = `${landCells} (${rn((landCells / grid.cells.i.length) * 100)}%)`;
landmassAverage.innerHTML = rn(d3.mean(grid.cells.h)); byId("landmassAverage").innerText = rn(d3.mean(grid.cells.h));
} }
function updateHistory(noStat) { function updateHistory(noStat) {
@ -501,8 +501,8 @@ function editHeightmap() {
redo.disabled = templateRedo.disabled = true; redo.disabled = templateRedo.disabled = true;
if (!noStat) { if (!noStat) {
updateStatistics(); updateStatistics();
if (document.getElementById("preview")) drawHeightmapPreview(); // update heightmap preview if opened if (byId("preview")) drawHeightmapPreview(); // update heightmap preview if opened
if (document.getElementById("canvas3d")) ThreeD.redraw(); // update 3d heightmap preview if opened if (byId("canvas3d")) ThreeD.redraw(); // update 3d heightmap preview if opened
} }
} }
@ -516,8 +516,8 @@ function editHeightmap() {
mockHeightmap(); mockHeightmap();
updateStatistics(); updateStatistics();
if (document.getElementById("preview")) drawHeightmapPreview(); // update heightmap preview if opened if (byId("preview")) drawHeightmapPreview(); // update heightmap preview if opened
if (document.getElementById("canvas3d")) ThreeD.redraw(); // update 3d heightmap preview if opened if (byId("canvas3d")) ThreeD.redraw(); // update 3d heightmap preview if opened
} }
// restart edits from 1st step // restart edits from 1st step
@ -543,31 +543,31 @@ function editHeightmap() {
modules.openBrushesPanel = true; modules.openBrushesPanel = true;
// add listeners // add listeners
document.getElementById("brushesButtons").addEventListener("click", e => toggleBrushMode(e)); byId("brushesButtons").on("click", e => toggleBrushMode(e));
document.getElementById("changeOnlyLand").addEventListener("click", e => changeOnlyLandClick(e)); byId("changeOnlyLand").on("click", e => changeOnlyLandClick(e));
document.getElementById("undo").addEventListener("click", () => restoreHistory(edits.n - 1)); byId("undo").on("click", () => restoreHistory(edits.n - 1));
document.getElementById("redo").addEventListener("click", () => restoreHistory(edits.n + 1)); byId("redo").on("click", () => restoreHistory(edits.n + 1));
document.getElementById("rescaleShow").addEventListener("click", () => { byId("rescaleShow").on("click", () => {
document.getElementById("modifyButtons").style.display = "none"; byId("modifyButtons").style.display = "none";
document.getElementById("rescaleSection").style.display = "block"; byId("rescaleSection").style.display = "block";
}); });
document.getElementById("rescaleHide").addEventListener("click", () => { byId("rescaleHide").on("click", () => {
document.getElementById("modifyButtons").style.display = "block"; byId("modifyButtons").style.display = "block";
document.getElementById("rescaleSection").style.display = "none"; byId("rescaleSection").style.display = "none";
}); });
document.getElementById("rescaler").addEventListener("change", e => rescale(e.target.valueAsNumber)); byId("rescaler").on("change", e => rescale(e.target.valueAsNumber));
document.getElementById("rescaleCondShow").addEventListener("click", () => { byId("rescaleCondShow").on("click", () => {
document.getElementById("modifyButtons").style.display = "none"; byId("modifyButtons").style.display = "none";
document.getElementById("rescaleCondSection").style.display = "block"; byId("rescaleCondSection").style.display = "block";
}); });
document.getElementById("rescaleCondHide").addEventListener("click", () => { byId("rescaleCondHide").on("click", () => {
document.getElementById("modifyButtons").style.display = "block"; byId("modifyButtons").style.display = "block";
document.getElementById("rescaleCondSection").style.display = "none"; byId("rescaleCondSection").style.display = "none";
}); });
document.getElementById("rescaleExecute").addEventListener("click", rescaleWithCondition); byId("rescaleExecute").on("click", rescaleWithCondition);
document.getElementById("smoothHeights").addEventListener("click", smoothAllHeights); byId("smoothHeights").on("click", smoothAllHeights);
document.getElementById("disruptHeights").addEventListener("click", disruptAllHeights); byId("disruptHeights").on("click", disruptAllHeights);
document.getElementById("brushClear").addEventListener("click", startFromScratch); byId("brushClear").on("click", startFromScratch);
function exitBrushMode() { function exitBrushMode() {
const pressed = document.querySelector("#brushesButtons > button.pressed"); const pressed = document.querySelector("#brushesButtons > button.pressed");
@ -576,7 +576,7 @@ function editHeightmap() {
viewbox.style("cursor", "default").on(".drag", null); viewbox.style("cursor", "default").on(".drag", null);
removeCircle(); removeCircle();
document.getElementById("brushesSliders").style.display = "none"; byId("brushesSliders").style.display = "none";
} }
const dragBrushThrottled = throttle(dragBrush, 100); const dragBrushThrottled = throttle(dragBrush, 100);
@ -586,7 +586,7 @@ function editHeightmap() {
return; return;
} }
exitBrushMode(); exitBrushMode();
document.getElementById("brushesSliders").style.display = "block"; byId("brushesSliders").style.display = "block";
e.target.classList.add("pressed"); e.target.classList.add("pressed");
viewbox.style("cursor", "crosshair").call(d3.drag().on("start", dragBrushThrottled)); viewbox.style("cursor", "crosshair").call(d3.drag().on("start", dragBrushThrottled));
} }
@ -613,9 +613,7 @@ function editHeightmap() {
const power = brushPower.valueAsNumber; const power = brushPower.valueAsNumber;
const interpolate = d3.interpolateRound(power, 1); const interpolate = d3.interpolateRound(power, 1);
const land = changeOnlyLand.checked; const land = changeOnlyLand.checked;
function lim(v) { const lim = v => minmax(v, land ? 20 : 0, 100);
return minmax(v, land ? 20 : 0, 100);
}
const h = grid.cells.h; const h = grid.cells.h;
const brush = document.querySelector("#brushesButtons > button.pressed").id; const brush = document.querySelector("#brushesButtons > button.pressed").id;
@ -644,21 +642,15 @@ function editHeightmap() {
const land = changeOnlyLand.checked; const land = changeOnlyLand.checked;
grid.cells.h = grid.cells.h.map(h => (land && (h < 20 || h + v < 20) ? h : lim(h + v))); grid.cells.h = grid.cells.h.map(h => (land && (h < 20 || h + v < 20) ? h : lim(h + v)));
updateHeightmap(); updateHeightmap();
document.getElementById("rescaler").value = 0; byId("rescaler").value = 0;
} }
function rescaleWithCondition() { function rescaleWithCondition() {
const range = rescaleLower.value + "-" + rescaleHigher.value; const range = rescaleLower.value + "-" + rescaleHigher.value;
const operator = conditionSign.value; const operator = conditionSign.value;
const operand = rescaleModifier.valueAsNumber; const operand = rescaleModifier.valueAsNumber;
if (Number.isNaN(operand)) { if (Number.isNaN(operand)) return tip("Operand should be a number", false, "error");
tip("Operand should be a number", false, "error"); if ((operator === "add" || operator === "subtract") && !Number.isInteger(operand)) return tip("Operand should be an integer", false, "error");
return;
}
if ((operator === "add" || operator === "subtract") && !Number.isInteger(operand)) {
tip("Operand should be an integer", false, "error");
return;
}
if (operator === "multiply") HeightmapGenerator.modify(range, 0, operand, 0); if (operator === "multiply") HeightmapGenerator.modify(range, 0, operand, 0);
else if (operator === "divide") HeightmapGenerator.modify(range, 0, 1 / operand, 0); else if (operator === "divide") HeightmapGenerator.modify(range, 0, 1 / operand, 0);
@ -680,15 +672,10 @@ function editHeightmap() {
} }
function startFromScratch() { function startFromScratch() {
if (changeOnlyLand.checked) { if (changeOnlyLand.checked) return tip("Not allowed when 'Change only land cells' mode is set", false, "error");
tip("Not allowed when 'Change only land cells' mode is set", false, "error");
return;
}
const someHeights = grid.cells.h.some(h => h); const someHeights = grid.cells.h.some(h => h);
if (!someHeights) { if (!someHeights) return tip("Heightmap is already cleared, please do not click twice if not required", false, "error");
tip("Heightmap is already cleared, please do not click twice if not required", false, "error");
return;
}
grid.cells.h = new Uint8Array(grid.cells.i.length); grid.cells.h = new Uint8Array(grid.cells.i.length);
viewbox.select("#heights").selectAll("*").remove(); viewbox.select("#heights").selectAll("*").remove();
updateHistory(); updateHistory();
@ -697,7 +684,7 @@ function editHeightmap() {
function openTemplateEditor() { function openTemplateEditor() {
if ($("#templateEditor").is(":visible")) return; if ($("#templateEditor").is(":visible")) return;
const body = document.getElementById("templateBody"); const $body = byId("templateBody");
$("#templateEditor").dialog({ $("#templateEditor").dialog({
title: "Template Editor", title: "Template Editor",
@ -713,13 +700,13 @@ function editHeightmap() {
$("#templateBody").sortable({items: "> div", handle: ".icon-resize-vertical", containment: "#templateBody", axis: "y"}); $("#templateBody").sortable({items: "> div", handle: ".icon-resize-vertical", containment: "#templateBody", axis: "y"});
// add listeners // add listeners
body.addEventListener("click", function (ev) { $body.on("click", function (ev) {
const el = ev.target; const el = ev.target;
if (el.classList.contains("icon-check")) { if (el.classList.contains("icon-check")) {
el.classList.remove("icon-check"); el.classList.remove("icon-check");
el.classList.add("icon-check-empty"); el.classList.add("icon-check-empty");
el.parentElement.style.opacity = 0.5; el.parentElement.style.opacity = 0.5;
body.dataset.changed = 1; $body.dataset.changed = 1;
return; return;
} }
if (el.classList.contains("icon-check-empty")) { if (el.classList.contains("icon-check-empty")) {
@ -734,71 +721,146 @@ function editHeightmap() {
} }
}); });
document.getElementById("templateTools").addEventListener("click", e => addStepOnClick(e)); byId("templateEditor").on("keypress", event => {
document.getElementById("templateSelect").addEventListener("change", e => selectTemplate(e)); if (event.key === "Enter") {
document.getElementById("templateRun").addEventListener("click", executeTemplate); event.preventDefault();
document.getElementById("templateSave").addEventListener("click", downloadTemplate); executeTemplate();
document.getElementById("templateLoad").addEventListener("click", () => templateToLoad.click()); }
document.getElementById("templateToLoad").addEventListener("change", function () { });
byId("templateTools").on("click", addStepOnClick);
byId("templateSelect").on("change", selectTemplate);
byId("templateRun").on("click", executeTemplate);
byId("templateSave").on("click", downloadTemplate);
byId("templateLoad").on("click", () => templateToLoad.click());
byId("templateToLoad").on("change", function () {
uploadFile(this, uploadTemplate); uploadFile(this, uploadTemplate);
}); });
function addStepOnClick(e) { function addStepOnClick(e) {
if (e.target.tagName !== "BUTTON") return; if (e.target.tagName !== "BUTTON") return;
const type = e.target.id.replace("template", ""); const type = e.target.dataset.type;
document.getElementById("templateBody").dataset.changed = 1; byId("templateBody").dataset.changed = 1;
addStep(type); addStep(type);
} }
function addStep(type, count, dist, arg4, arg5) { function addStep(type, count, dist, arg4, arg5) {
const body = document.getElementById("templateBody"); const $body = byId("templateBody");
body.insertAdjacentHTML("beforeend", getStepHTML(type, count, dist, arg4, arg5)); $body.insertAdjacentHTML("beforeend", getStepHTML(type, count, dist, arg4, arg5));
const elDist = body.querySelector("div:last-child").querySelector(".templateDist");
if (elDist) elDist.addEventListener("change", setRange); const $elDist = $body.querySelector("div:last-child > span > .templateDist");
if (dist && elDist && elDist.tagName === "SELECT") { if ($elDist) $elDist.on("change", setRange);
for (const o of elDist.options) {
if (o.value === dist) elDist.value = dist; if (dist && $elDist && $elDist.tagName === "SELECT") {
for (const option of $elDist.options) {
if (option.value === dist) $elDist.value = dist;
} }
if (elDist.value !== dist) { if ($elDist.value !== dist) {
const opt = document.createElement("option"); const opt = document.createElement("option");
opt.value = opt.innerHTML = dist; opt.value = opt.innerHTML = dist;
elDist.add(opt); $elDist.add(opt);
elDist.value = dist; $elDist.value = dist;
} }
} }
} }
function getStepHTML(type, count, arg3, arg4, arg5) { function getStepHTML(type, count, arg3, arg4, arg5) {
const Trash = `<i class="icon-trash-empty pointer" data-tip="Click to remove the step"></i>`; const Trash = /* html */ `<i class="icon-trash-empty pointer" data-tip="Click to remove the step"></i>`;
const Hide = `<div class="icon-check" data-tip="Click to skip the step"></div>`; const Hide = /* html */ `<div class="icon-check" data-tip="Click to skip the step"></div>`;
const Reorder = `<i class="icon-resize-vertical" data-tip="Drag to reorder"></i>`; const Reorder = /* html */ `<i class="icon-resize-vertical" data-tip="Drag to reorder"></i>`;
const common = `<div data-type="${type}">${Hide}<div style="width:4em">${type}</div>${Trash}${Reorder}`; const common = /* html */ `<div data-type="${type}">${Hide}<div style="width:4em">${type}</div>${Trash}${Reorder}`;
const TempY = `<span>y:<input class="templateY" data-tip="Placement range percentage along Y axis (minY-maxY)" value=${arg5 || "20-80"}></span>`; const TempY = /* html */ `<span>y:
const TempX = `<span>x:<input class="templateX" data-tip="Placement range percentage along X axis (minX-maxX)" value=${arg4 || "15-85"}></span>`; <input class="templateY" data-tip="Placement range percentage along Y axis (minY-maxY)" value=${arg5 || "20-80"} />
const Height = `<span>h:<input class="templateHeight" data-tip="Blob maximum height, use hyphen to get a random number in range" value=${ </span>`;
arg3 || "40-50"
}></span>`; const TempX = /* html */ `<span>x:
const Count = `<span>n:<input class="templateCount" data-tip="Blobs to add, use hyphen to get a random number in range" value=${count || "1-2"}></span>`; <input class="templateX" data-tip="Placement range percentage along X axis (minX-maxX)" value=${arg4 || "15-85"} />
const blob = `${common}${TempY}${TempX}${Height}${Count}</div>`; </span>`;
const Height = /* html */ `<span>h:
<input class="templateHeight" data-tip="Blob maximum height, use hyphen to get a random number in range" value=${arg3 || "40-50"} />
</span>`;
const Count = /* html */ `<span>n:
<input class="templateCount" data-tip="Blobs to add, use hyphen to get a random number in range" value=${count || "1-2"} />
</span>`;
if (type === "Hill" || type === "Pit" || type === "Range" || type === "Trough") return /* html */ `${common}${TempY}${TempX}${Height}${Count}</div>`;
if (type === "Hill" || type === "Pit" || type === "Range" || type === "Trough") return blob;
if (type === "Strait") if (type === "Strait")
return `${common}<span>d:<select class="templateDist" data-tip="Strait direction"><option value="vertical" selected>vertical</option><option value="horizontal">horizontal</option></select></span><span>w:<input class="templateCount" data-tip="Strait width, use hyphen to get a random number in range" value=${ return /* html */ `${common}
count || "2-7" <span>d:
}></span></div>`; <select class="templateDist" data-tip="Strait direction">
<option value="vertical" selected>vertical</option>
<option value="horizontal">horizontal</option>
</select>
</span>
<span>w:
<input class="templateCount" data-tip="Strait width, use hyphen to get a random number in range" value=${count || "2-7"} />
</span>
</div>`;
if (type === "Invert")
return /* html */ `${common}
<span>by:
<select class="templateDist" data-tip="Mirror heightmap along axis" style="width: 7.8em">
<option value="x" selected>x</option>
<option value="y">y</option>
<option value="xy">both</option>
</select>
</span>
<span>n:
<input class="templateCount" data-tip="Probability of inversion, range 0-1" value=${count || "0.5"} />
</span>
</div>`;
if (type === "Mask")
return /* html */ `${common}
<span>f:
<input class="templateCount"
data-tip="Set masking fraction. 1 - full insulation (prevent land on map edges), 2 - half-insulation, etc. Negative number to inverse the effect"
type="number" min=-10 max=10 value=${count || 1} />
</span>
</div>`;
if (type === "Add") if (type === "Add")
return `${common}<span>to:<select class="templateDist" data-tip="Change only land or all cells"><option value="all" selected>all cells</option><option value="land">land only</option><option value="interval">interval</option></select></span><span>v:<input class="templateCount" data-tip="Add value to height of all cells (negative values are allowed)" type="number" value=${ return /* html */ `${common}
count || -10 <span>to:
} min=-100 max=100 step=1></span></div>`; <select class="templateDist" data-tip="Change only land or all cells">
<option value="all" selected>all cells</option>
<option value="land">land only</option>
<option value="interval">interval</option>
</select>
</span>
<span>v:
<input class="templateCount" data-tip="Add value to height of all cells (negative values are allowed)"
type="number" value=${count || -10} min=-100 max=100 step=1 />
</span>
</div>`;
if (type === "Multiply") if (type === "Multiply")
return `${common}<span>to:<select class="templateDist" data-tip="Change only land or all cells"><option value="all" selected>all cells</option><option value="land">land only</option><option value="interval">interval</option></select></span><span>v:<input class="templateCount" data-tip="Multiply all cells Height by the value" type="number" value=${ return /* html */ `${common}
count || 1.1 <span>to:
} min=0 max=10 step=.1></span></div>`; <select class="templateDist" data-tip="Change only land or all cells">
<option value="all" selected>all cells</option>
<option value="land">land only</option>
<option value="interval">interval</option>
</select>
</span>
<span>v:
<input class="templateCount" data-tip="Multiply all cells Height by the value" type="number"
value=${count || 1.1} min=0 max=10 step=.1 />
</span>
</div>`;
if (type === "Smooth") if (type === "Smooth")
return `${common}<span>f:<input class="templateCount" data-tip="Set smooth fraction. 1 - full smooth, 2 - half-smooth, etc." type="number" min=1 max=10 value=${ return /* html */ `${common}
count || 2 <span>f:
}></span></div>`; <input class="templateCount" data-tip="Set smooth fraction. 1 - full smooth, 2 - half-smooth, etc."
type="number" min=1 max=10 step=1 value=${count || 2} />
</span>
</div>`;
} }
function setRange(event) { function setRange(event) {
@ -813,7 +875,7 @@ function editHeightmap() {
} }
function selectTemplate(e) { function selectTemplate(e) {
const body = document.getElementById("templateBody"); const body = byId("templateBody");
const steps = body.querySelectorAll("div").length; const steps = body.querySelectorAll("div").length;
const changed = +body.getAttribute("data-changed"); const changed = +body.getAttribute("data-changed");
const template = e.target.value; const template = e.target.value;
@ -839,7 +901,7 @@ function editHeightmap() {
} }
function changeTemplate(template) { function changeTemplate(template) {
const body = document.getElementById("templateBody"); const body = byId("templateBody");
body.setAttribute("data-changed", 0); body.setAttribute("data-changed", 0);
body.innerHTML = ""; body.innerHTML = "";
@ -856,43 +918,47 @@ function editHeightmap() {
} }
function executeTemplate() { function executeTemplate() {
const body = document.getElementById("templateBody"); const steps = byId("templateBody").querySelectorAll("#templateBody > div");
const steps = body.querySelectorAll("#templateBody > div");
if (!steps.length) return; if (!steps.length) return;
const {addHill, addPit, addRange, addTrough, addStrait, modify, smooth} = HeightmapGenerator;
grid.cells.h = new Uint8Array(grid.cells.i.length); // clean all heights grid.cells.h = new Uint8Array(grid.cells.i.length); // clean all heights
const seed = byId("templateSeed").value;
if (seed) Math.random = aleaPRNG(seed);
restartHistory();
for (const step of steps) { for (const step of steps) {
if (step.style.opacity === "0.5") continue; if (step.style.opacity === "0.5") continue;
const type = step.dataset.type;
const count = step.querySelector(".templateCount")?.value || ""; const count = step.querySelector(".templateCount")?.value || "";
const height = step.querySelector(".templateHeight")?.value || ""; const height = step.querySelector(".templateHeight")?.value || "";
const dist = step.querySelector(".templateDist")?.value || null; const dist = step.querySelector(".templateDist")?.value || null;
const x = step.querySelector(".templateX")?.value || null; const x = step.querySelector(".templateX")?.value || null;
const y = step.querySelector(".templateY")?.value || null; const y = step.querySelector(".templateY")?.value || null;
const type = step.dataset.type;
if (type === "Hill") addHill(count, height, x, y); if (type === "Hill") HeightmapGenerator.addHill(count, height, x, y);
else if (type === "Pit") addPit(count, height, x, y); else if (type === "Pit") HeightmapGenerator.addPit(count, height, x, y);
else if (type === "Range") addRange(count, height, x, y); else if (type === "Range") HeightmapGenerator.addRange(count, height, x, y);
else if (type === "Trough") addTrough(count, height, x, y); else if (type === "Trough") HeightmapGenerator.addTrough(count, height, x, y);
else if (type === "Strait") addStrait(count, dist); else if (type === "Strait") HeightmapGenerator.addStrait(count, dist);
else if (type === "Add") modify(dist, +count, 1); else if (type === "Mask") HeightmapGenerator.mask(+count);
else if (type === "Multiply") modify(dist, 0, +count); else if (type === "Invert") HeightmapGenerator.invert(+count, dist);
else if (type === "Smooth") smooth(+count); else if (type === "Add") HeightmapGenerator.modify(dist, +count, 1);
else if (type === "Multiply") HeightmapGenerator.modify(dist, 0, +count);
else if (type === "Smooth") HeightmapGenerator.smooth(+count);
updateHistory("noStat"); // update history every step updateHistory("noStat"); // update history on every step
} }
updateStatistics(); updateStatistics();
mockHeightmap(); mockHeightmap();
if (document.getElementById("preview")) drawHeightmapPreview(); // update heightmap preview if opened if (byId("preview")) drawHeightmapPreview(); // update heightmap preview if opened
if (document.getElementById("canvas3d")) ThreeD.redraw(); // update 3d heightmap preview if opened if (byId("canvas3d")) ThreeD.redraw(); // update 3d heightmap preview if opened
} }
function downloadTemplate() { function downloadTemplate() {
const body = document.getElementById("templateBody"); const body = byId("templateBody");
body.dataset.changed = 0; body.dataset.changed = 0;
const steps = body.querySelectorAll("#templateBody > div"); const steps = body.querySelectorAll("#templateBody > div");
if (!steps.length) return; if (!steps.length) return;
@ -977,18 +1043,18 @@ function editHeightmap() {
})(); })();
// add listeners // add listeners
document.getElementById("convertImageLoad").addEventListener("click", () => imageToLoad.click()); byId("convertImageLoad").on("click", () => imageToLoad.click());
document.getElementById("imageToLoad").addEventListener("change", loadImage); byId("imageToLoad").on("change", loadImage);
document.getElementById("convertAutoLum").addEventListener("click", () => autoAssing("lum")); byId("convertAutoLum").on("click", () => autoAssing("lum"));
document.getElementById("convertAutoHue").addEventListener("click", () => autoAssing("hue")); byId("convertAutoHue").on("click", () => autoAssing("hue"));
document.getElementById("convertAutoFMG").addEventListener("click", () => autoAssing("scheme")); byId("convertAutoFMG").on("click", () => autoAssing("scheme"));
document.getElementById("convertColorsButton").addEventListener("click", setConvertColorsNumber); byId("convertColorsButton").on("click", setConvertColorsNumber);
document.getElementById("convertComplete").addEventListener("click", applyConversion); byId("convertComplete").on("click", applyConversion);
document.getElementById("convertCancel").addEventListener("click", cancelConversion); byId("convertCancel").on("click", cancelConversion);
document.getElementById("convertOverlay").addEventListener("input", function () { byId("convertOverlay").on("input", function () {
setOverlayOpacity(this.value); setOverlayOpacity(this.value);
}); });
document.getElementById("convertOverlayNumber").addEventListener("input", function () { byId("convertOverlayNumber").on("input", function () {
setOverlayOpacity(this.value); setOverlayOpacity(this.value);
}); });
@ -1012,7 +1078,7 @@ function editHeightmap() {
document.body.appendChild(img); document.body.appendChild(img);
img.onload = function () { img.onload = function () {
const ctx = document.getElementById("canvas").getContext("2d"); const ctx = byId("canvas").getContext("2d");
ctx.drawImage(img, 0, 0, graphWidth, graphHeight); ctx.drawImage(img, 0, 0, graphWidth, graphHeight);
heightsFromImage(+convertColors.value); heightsFromImage(+convertColors.value);
resetZoom(); resetZoom();
@ -1023,7 +1089,7 @@ function editHeightmap() {
} }
function heightsFromImage(count) { function heightsFromImage(count) {
const sourceImage = document.getElementById("canvas"); const sourceImage = byId("canvas");
const sampleCanvas = document.createElement("canvas"); const sampleCanvas = document.createElement("canvas");
sampleCanvas.width = grid.cellsX; sampleCanvas.width = grid.cellsX;
sampleCanvas.height = grid.cellsY; sampleCanvas.height = grid.cellsY;
@ -1062,7 +1128,7 @@ function editHeightmap() {
.attr("class", "color-div") .attr("class", "color-div")
.on("click", colorClicked); .on("click", colorClicked);
document.getElementById("colorsUnassignedNumber").innerHTML = colors.length; byId("colorsUnassignedNumber").innerHTML = colors.length;
} }
function mapClicked() { function mapClicked() {
@ -1119,8 +1185,8 @@ function editHeightmap() {
colorsAssigned.appendChild(selectedColor); colorsAssigned.appendChild(selectedColor);
colorsAssigned.style.display = "block"; colorsAssigned.style.display = "block";
document.getElementById("colorsUnassignedNumber").innerHTML = colorsUnassigned.childElementCount - 2; byId("colorsUnassignedNumber").innerHTML = colorsUnassigned.childElementCount - 2;
document.getElementById("colorsAssignedNumber").innerHTML = colorsAssigned.childElementCount - 2; byId("colorsAssignedNumber").innerHTML = colorsAssigned.childElementCount - 2;
} }
} }
@ -1187,7 +1253,7 @@ function editHeightmap() {
colorsAssigned.style.display = "block"; colorsAssigned.style.display = "block";
colorsUnassigned.style.display = "none"; colorsUnassigned.style.display = "none";
document.getElementById("colorsAssignedNumber").innerHTML = colorsAssigned.childElementCount - 2; byId("colorsAssignedNumber").innerHTML = colorsAssigned.childElementCount - 2;
} }
function setConvertColorsNumber() { function setConvertColorsNumber() {
@ -1203,7 +1269,7 @@ function editHeightmap() {
function setOverlayOpacity(v) { function setOverlayOpacity(v) {
convertOverlay.value = convertOverlayNumber.value = v; convertOverlay.value = convertOverlayNumber.value = v;
document.getElementById("canvas").style.opacity = v; byId("canvas").style.opacity = v;
} }
function applyConversion() { function applyConversion() {
@ -1230,10 +1296,10 @@ function editHeightmap() {
} }
function restoreImageConverterState() { function restoreImageConverterState() {
const canvas = document.getElementById("canvas"); const canvas = byId("canvas");
if (canvas) canvas.remove(); if (canvas) canvas.remove();
const image = document.getElementById("imageToConvert"); const image = byId("imageToConvert");
if (image) image.remove(); if (image) image.remove();
d3.select("#imageConverter").selectAll("div.color-div").remove(); d3.select("#imageConverter").selectAll("div.color-div").remove();
@ -1275,8 +1341,8 @@ function editHeightmap() {
} }
function toggleHeightmapPreview() { function toggleHeightmapPreview() {
if (document.getElementById("preview")) { if (byId("preview")) {
document.getElementById("preview").remove(); byId("preview").remove();
return; return;
} }
const preview = document.createElement("canvas"); const preview = document.createElement("canvas");
@ -1284,13 +1350,13 @@ function editHeightmap() {
preview.width = grid.cellsX; preview.width = grid.cellsX;
preview.height = grid.cellsY; preview.height = grid.cellsY;
document.body.insertBefore(preview, optionsContainer); document.body.insertBefore(preview, optionsContainer);
preview.addEventListener("mouseover", () => tip("Heightmap preview. Click to download a screen-sized image")); preview.on("mouseover", () => tip("Heightmap preview. Click to download a screen-sized image"));
preview.addEventListener("click", downloadPreview); preview.on("click", downloadPreview);
drawHeightmapPreview(); drawHeightmapPreview();
} }
function drawHeightmapPreview() { function drawHeightmapPreview() {
const ctx = document.getElementById("preview").getContext("2d"); const ctx = byId("preview").getContext("2d");
const imageData = ctx.createImageData(grid.cellsX, grid.cellsY); const imageData = ctx.createImageData(grid.cellsX, grid.cellsY);
grid.cells.h.forEach((height, i) => { grid.cells.h.forEach((height, i) => {
@ -1306,7 +1372,7 @@ function editHeightmap() {
} }
function downloadPreview() { function downloadPreview() {
const preview = document.getElementById("preview"); const preview = byId("preview");
const dataURL = preview.toDataURL("image/png"); const dataURL = preview.toDataURL("image/png");
const img = new Image(); const img = new Image();

View file

@ -1,7 +1,7 @@
"use strict"; "use strict";
// version and caching control // version and caching control
const version = "1.82.05"; // generator version, update each time const version = "1.83.0"; // generator version, update each time
{ {
document.title += " v" + version; document.title += " v" + version;
@ -28,6 +28,8 @@ const version = "1.82.05"; // generator version, update each time
<ul> <ul>
<strong>Latest changes:</strong> <strong>Latest changes:</strong>
<li>New heightmap template: Fractious</li>
<li>Template Editor: mask and invert tools</li>
<li>Ability to install the App</li> <li>Ability to install the App</li>
<li>14 new default fonts</li> <li>14 new default fonts</li>
<li>Caching for faster startup</li> <li>Caching for faster startup</li>
@ -35,8 +37,6 @@ const version = "1.82.05"; // generator version, update each time
<li>Resample tool by Goteguru</li> <li>Resample tool by Goteguru</li>
<li>Pre-defined heightmaps</li> <li>Pre-defined heightmaps</li>
<li>Advanced notes editor</li> <li>Advanced notes editor</li>
<li>Zones editor: filter by type</li>
<li>Color picker: new hatchings</li>
</ul> </ul>
<p>Join our <a href="${discord}" target="_blank">Discord server</a> and <a href="${reddit}" target="_blank">Reddit community</a> to ask questions, share maps, discuss the Generator and Worlbuilding, report bugs and propose new features.</p> <p>Join our <a href="${discord}" target="_blank">Discord server</a> and <a href="${reddit}" target="_blank">Reddit community</a> to ask questions, share maps, discuss the Generator and Worlbuilding, report bugs and propose new features.</p>