3d preview to Heightmap editor (#329)

This commit is contained in:
evolvedexperiment 2019-10-17 22:12:16 +02:00 committed by Azgaar
parent efd9159737
commit 0e308079aa
8 changed files with 51152 additions and 2 deletions

View file

@ -2011,6 +2011,15 @@ svg.button {
stroke-width: 1.4; stroke-width: 1.4;
} }
#_3dpreview {
}
#_3dpreviewEditor {
overflow-y: initial;
overflow-x: initial;
padding: 0px;
}
#legend { #legend {
cursor: move; cursor: move;
-moz-user-select: none; -moz-user-select: none;
@ -2062,4 +2071,4 @@ svg.button {
.drag-trigger { .drag-trigger {
display: none; display: none;
} }
} }

View file

@ -1838,6 +1838,7 @@
<button data-tip="Open template editor" id="applyTemplate" style="display: none">Template Editor</button> <button data-tip="Open template editor" id="applyTemplate" style="display: none">Template Editor</button>
<button data-tip="Open Image Converter" id="convertImage" style="display: none">Image Converter</button> <button data-tip="Open Image Converter" id="convertImage" style="display: none">Image Converter</button>
<button data-tip="Render heightmap data as a small monochrome image" id="heightmapPreview">Heightmap Preview</button> <button data-tip="Render heightmap data as a small monochrome image" id="heightmapPreview">Heightmap Preview</button>
<button data-tip="View heightmap data in 3D" id="heightmap3DView">3D View</button>
</div> </div>
<div id="customizeOptions"> <div id="customizeOptions">
@ -2586,6 +2587,13 @@
</div> </div>
<div id="_3dpreviewEditor" class="dialog stable" style="display: none">
<div id="_3dpreviewBody">
<div id="_3dpreviewContainer"></div>
</div>
</div>
<div id="statesEditor" class="dialog stable" style="display: none"> <div id="statesEditor" class="dialog stable" style="display: none">
<div id="statesHeader" class="header"> <div id="statesHeader" class="header">
<div style="left:1.4em" data-tip="Click to sort by state name" class="sortable alphabetically" data-sortby="name">State&nbsp;</div> <div style="left:1.4em" data-tip="Click to sort by state name" class="sortable alphabetically" data-sortby="name">State&nbsp;</div>
@ -3222,6 +3230,8 @@
<input type="file" accept=".json" id="styleToLoad"> <input type="file" accept=".json" id="styleToLoad">
</div> </div>
<canvas id="renderCanvas"></canvas>
<script src="libs/jquery-3.1.1.min.js"></script> <script src="libs/jquery-3.1.1.min.js"></script>
<script src="libs/d3.min.js"></script> <script src="libs/d3.min.js"></script>
<script src="libs/priority-queue.min.js"></script> <script src="libs/priority-queue.min.js"></script>
@ -3240,6 +3250,9 @@
<script src="libs/jquery-ui.min.js"></script> <script src="libs/jquery-ui.min.js"></script>
<script src="libs/seedrandom.min.js"></script> <script src="libs/seedrandom.min.js"></script>
<script src="modules/ui/layers.js"></script> <script src="modules/ui/layers.js"></script>
<script src="libs/three.js"></script>
<script src="libs/OrbitControls.js"></script>
<script src="modules/ui/3dpreview.js"></script>
<script defer src="modules/ui/general.js"></script> <script defer src="modules/ui/general.js"></script>
<script defer src="modules/ui/options.js"></script> <script defer src="modules/ui/options.js"></script>
@ -3250,6 +3263,7 @@
<script defer src="modules/relief-icons.js"></script> <script defer src="modules/relief-icons.js"></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/3dutils.js"></script>
<script defer src="modules/ui/heightmap-editor.js"></script> <script defer src="modules/ui/heightmap-editor.js"></script>
<script defer src="modules/ui/states-editor.js"></script> <script defer src="modules/ui/states-editor.js"></script>
<script defer src="modules/ui/provinces-editor.js"></script> <script defer src="modules/ui/provinces-editor.js"></script>

1172
libs/OrbitControls.js Normal file

File diff suppressed because it is too large Load diff

49623
libs/three.js Normal file

File diff suppressed because one or more lines are too long

105
modules/ui/3dpreview.js Normal file
View file

@ -0,0 +1,105 @@
// Define variables - these make it easy to work with from the console
var _3dpreviewScale = 50;
var _3dpreviewCamera = null;
var _3dpreviewScene = null;
var _3dpreviewRenderer = null;
var _3danimationFrame = null;
var _3dmaterial = null;
var _3dtexture = null;
var _3dmesh = null;
// Create a mesh from pixel data
function addMesh(width, height, segmentsX, segmentsY) {
_3dgeometry = new THREE.PlaneGeometry(width, height, segmentsX-1, segmentsY-1);
_3dmaterial = new THREE.MeshBasicMaterial({
wireframe: false,
});
_3dtexture = new THREE.TextureLoader().load( getPreviewTexture(width, height) );
// _3dtexture.minFilter = THREE.LinearFilter;
_3dmaterial.map = _3dtexture;
var terrain = getHeightmap();
var l = _3dgeometry.vertices.length;
for (var i = 0; i < l; i++) // For each vertex
{
var terrainValue = terrain[i] / 255;
_3dgeometry.vertices[i].z = _3dgeometry.vertices[i].z + terrainValue * _3dpreviewScale;
}
_3dmesh = new THREE.Mesh(_3dgeometry, _3dmaterial);
_3dmesh.rotation.x = -Math.PI / 2;
_3dpreviewScene.add(_3dmesh);
}
// Function to render scene and camera
function render() {
_3danimationFrame = requestAnimationFrame(render);
_3dpreviewRenderer.render(_3dpreviewScene, _3dpreviewCamera);
}
function check3dCamera(canvas) {
// workaround to fix camera problems
var resetCamera = 0;
if (!_3dpreviewCamera) {
resetCamera = 1;
} else if (isNaN(_3dpreviewCamera.position.x)) {
_3dpreviewCamera = null;
resetCamera = 1;
}
if (resetCamera) {
_3dpreviewCamera = new THREE.PerspectiveCamera(70, canvas.width / canvas.height, 0.1, 10000); //Field-of-View, Aspect Ratio, Near Render, Far Render
_3dpreviewCamera.position.z = 800;
_3dpreviewCamera.position.y = 1000;
}
}
function removeMesh() {
_3dpreviewScene.remove(_3dmesh);
_3dmesh.geometry.dispose();
_3dmesh.material.dispose();
_3dmesh = undefined;
_3dgeometry = undefined;
_3dmaterial = undefined;
_3dtexture = undefined;
}
function start3dpreview(canvas) {
_3dpreviewScene = new THREE.Scene();
check3dCamera(canvas);
_3dpreviewRenderer = new THREE.WebGLRenderer({ canvas: canvas });
_3dpreviewControls = new THREE.OrbitControls( _3dpreviewCamera, _3dpreviewRenderer.domElement );
_3dpreviewRenderer.setSize(canvas.width, canvas.height);
addMesh(graphWidth, graphHeight, grid.cellsX, grid.cellsY);
document.body.appendChild(_3dpreviewRenderer.domElement);
_3danimationFrame = requestAnimationFrame(render);
}
function stop3dpreview() {
if (_3danimationFrame) {
cancelAnimationFrame(_3danimationFrame);
_3danimationFrame = null;
}
removeMesh();
_3dpreviewScene = null;
_3dpreviewRenderer = null;
_3dpreviewControls = null;
}
function update3dpreview(canvas) {
removeMesh();
check3dCamera(canvas);
_3dpreviewRenderer.setSize(canvas.width, canvas.height);
addMesh(graphWidth, graphHeight, grid.cellsX, grid.cellsY);
}

150
modules/ui/3dutils.js Normal file
View file

@ -0,0 +1,150 @@
var _3dheightMapExponent = null;
var _3dmaxHeight = null;
var _3dcolorLookup = null;
function _2dto1d(source) {
var r = [];
var w = source[0].length;
var h = source.length;
var i=0;
for (var y=0; y < h; y++) {
for (var x=0; x < w; x++) {
r[i] = source[y][x];
i++;
}
}
return r;
}
function _1dto2d(source, w, h) {
var r = [];
var i=0;
for (var y=0; y < h; y++) {
r[y] = [];
for (var x=0; x < w; x++) {
r[y][x] = source[i];
i++;
}
}
return r;
}
function getSVGImage(type, width, height) {
// clone svg
const cloneEl = document.getElementById("map").cloneNode(true);
cloneEl.id = "fantasyMap";
document.getElementsByTagName("body")[0].appendChild(cloneEl);
const clone = d3.select("#fantasyMap");
if (type === "svg") {
clone.select("#viewbox").attr("transform", null); // reset transform to show whole map
}
clone.attr("width", width).attr("height", height);
// remove unused elements
if (!clone.select("#terrain").selectAll("use").size()) clone.select("#defs-relief").remove();
if (!clone.select("#prec").selectAll("circle").size()) clone.select("#prec").remove();
if (!clone.select("#scaleBar").selectAll("use").size()) clone.select("#scaleBar").remove();
// default to water - on Firefox, ocean pattern appears as alternating blocks of ocean and water pattern
const isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
if (isFirefox) {
if (!clone.select("#oceanPattern").selectAll("use").size()) clone.select("#oceanPattern").remove();
}
const removeEmptyGroups = function() {
let empty = 0;
clone.selectAll("g").each(function() {
if (!this.hasChildNodes() || this.style.display === "none") {empty++; this.remove();}
if (this.hasAttribute("display") && this.style.display === "inline") this.removeAttribute("display");
});
return empty;
}
while(removeEmptyGroups()) {removeEmptyGroups();}
// for each g element get inline style
const emptyG = clone.append("g").node();
const defaultStyles = window.getComputedStyle(emptyG);
clone.selectAll("g, #ruler > g > *, #scaleBar > text").each(function(d) {
const compStyle = window.getComputedStyle(this);
let style = "";
for (let i=0; i < compStyle.length; i++) {
const key = compStyle[i];
const value = compStyle.getPropertyValue(key);
// Firefox mask hack
if (key === "mask-image" && value !== defaultStyles.getPropertyValue(key)) {
style += "mask-image: url('#land');";
continue;
}
if (key === "cursor") continue; // cursor should be default
if (this.hasAttribute(key)) continue; // don't add style if there is the same attribute
if (value === defaultStyles.getPropertyValue(key)) continue;
style += key + ':' + value + ';';
}
if (style != "") this.setAttribute('style', style);
});
emptyG.remove();
// load fonts as dataURI so they will be available in downloaded svg/png
const svg_xml = (new XMLSerializer()).serializeToString(clone.node());
clone.remove();
const blob = new Blob([svg_xml], {type: 'image/svg+xml;charset=utf-8'});
const url = window.URL.createObjectURL(blob);
return url;
}
function getHeightmap() {
var mh = 0;
grid.cells.h.forEach((height, i) => {
h = grid.cells.h[i];
if (h > mh) mh = h;
});
if (mh > 82) { mh = 82; }
_3dheightMapExponent = heightExponentInput.value;
// _3dmaxHeight = Math.pow(mh, _3dheightMapExponent);
_3dmaxHeight = Math.pow(82, _3dheightMapExponent);
var heightMap = [];
for (var y=0; y < grid.cellsY; y++) {
heightMap[y] = [];
}
var y = 0, x = 0;
// convert height from map heights to a linear height
grid.cells.h.forEach((height, i) => {
h = grid.cells.h[i];
// add heightmap exponent
if (h >= 20) height = Math.pow(h - 18, _3dheightMapExponent);
// else if (h > 0) height = (h - 20) / h * 50;
else height = 0;
// get heightmap as a percentage
let v = (height / _3dmaxHeight);
// convert to 0-255
v = (v * 255);
heightMap[y][x] = Math.floor(v);
x++;
if (x >= grid.cellsX) { x = 0; y++ }
});
var hm = _2dto1d(heightMap);
return hm;
}
function getPreviewTexture(width, height) {
return getSVGImage("svg", width, height);
}

View file

@ -70,6 +70,20 @@ function removeCircle() {
if (document.getElementById("brushCircle")) document.getElementById("brushCircle").remove(); if (document.getElementById("brushCircle")) document.getElementById("brushCircle").remove();
} }
function hideCircle() {
let circle = document.getElementById("brushCircle");
if (circle) {
circle.style.display = "none";
}
}
function showCircle() {
let circle = document.getElementById("brushCircle");
if (circle) {
circle.style.display = "";
}
}
// get browser-defined fit-content // get browser-defined fit-content
function fitContent() { function fitContent() {
return !window.chrome ? "-moz-max-content" : "fit-content"; return !window.chrome ? "-moz-max-content" : "fit-content";
@ -557,4 +571,4 @@ function uploadFile(el, callback) {
fileReader.readAsText(el.files[0], "UTF-8"); fileReader.readAsText(el.files[0], "UTF-8");
el.value = ""; el.value = "";
fileReader.onload = loaded => callback(loaded.target.result); fileReader.onload = loaded => callback(loaded.target.result);
} }

View file

@ -40,6 +40,7 @@ function editHeightmap() {
document.getElementById("applyTemplate").addEventListener("click", openTemplateEditor); document.getElementById("applyTemplate").addEventListener("click", openTemplateEditor);
document.getElementById("convertImage").addEventListener("click", openImageConverter); document.getElementById("convertImage").addEventListener("click", openImageConverter);
document.getElementById("heightmapPreview").addEventListener("click", toggleHeightmapPreview); document.getElementById("heightmapPreview").addEventListener("click", toggleHeightmapPreview);
document.getElementById("heightmap3DView").addEventListener("click", toggleHeightmap3dView);
document.getElementById("finalizeHeightmap").addEventListener("click", finalizeHeightmap); document.getElementById("finalizeHeightmap").addEventListener("click", finalizeHeightmap);
document.getElementById("renderOcean").addEventListener("click", mockHeightmap); document.getElementById("renderOcean").addEventListener("click", mockHeightmap);
document.getElementById("templateUndo").addEventListener("click", () => restoreHistory(edits.n-1)); document.getElementById("templateUndo").addEventListener("click", () => restoreHistory(edits.n-1));
@ -126,6 +127,8 @@ function getHeight(h) {
restartHistory(); restartHistory();
if (document.getElementById("preview")) document.getElementById("preview").remove(); if (document.getElementById("preview")) document.getElementById("preview").remove();
if (document.getElementById("_3dpreview")) { toggleHeightmap3dView(); };
const mode = heightmapEditMode.innerHTML; const mode = heightmapEditMode.innerHTML;
if (mode === "erase") regenerateErasedData(); if (mode === "erase") regenerateErasedData();
@ -372,6 +375,7 @@ function getHeight(h) {
mockHeightmap(); mockHeightmap();
updateHistory(); updateHistory();
draw3dPreview();
} }
// draw or update heightmap // draw or update heightmap
@ -412,6 +416,7 @@ function getHeight(h) {
if (!noStat) updateStatistics(); if (!noStat) updateStatistics();
if (document.getElementById("preview")) drawHeightmapPreview(); // update heightmap preview if opened if (document.getElementById("preview")) drawHeightmapPreview(); // update heightmap preview if opened
if (document.getElementById("_3dpreview")) draw3dPreview(); // update 3d heightmap preview if opened
} }
// restoreHistory // restoreHistory
@ -425,6 +430,7 @@ function getHeight(h) {
updateStatistics(); updateStatistics();
if (document.getElementById("preview")) drawHeightmapPreview(); // update heightmap preview if opened if (document.getElementById("preview")) drawHeightmapPreview(); // update heightmap preview if opened
if (document.getElementById("_3dpreview")) draw3dPreview(); // update 3d heightmap preview if opened
} }
// restart edits from 1st step // restart edits from 1st step
@ -864,6 +870,7 @@ function getHeight(h) {
updateStatistics(); updateStatistics();
mockHeightmap(); mockHeightmap();
update3dpreview(document.getElementById("_3dpreview"));
} }
function downloadTemplate() { function downloadTemplate() {
@ -1207,4 +1214,60 @@ function getHeight(h) {
} }
} }
function draw3dPreview() {
var _3dpreview = document.getElementById("_3dpreview");
hideCircle();
update3dpreview(_3dpreview);
showCircle();
}
function close3dPreview() {
if (document.getElementById("_3dpreview")) {
stop3dpreview();
document.getElementById("_3dpreview").remove();
}
}
function toggleHeightmap3dView() {
if (document.getElementById("_3dpreview")) {
close3dPreview();
$("#_3dpreviewEditor").dialog("close");
return;
}
var _3dpreview = document.createElement("canvas");
_3dpreview.id = "_3dpreview";
_3dpreview.top = 0;
_3dpreview.left = 0;
_3dpreview.width = 800;
_3dpreview.height = 800 / (graphWidth / graphHeight);
$("#_3dpreviewEditor").dialog({
title: "3D Preview", width: _3dpreview.width, height: _3dpreview.height, resizable: true
}).on('dialogclose', close3dPreview);
var titleBar = document.getElementById("_3dpreviewEditor").previousSibling;
$("#_3dpreviewEditor").dialog( "option", "height", _3dpreview.height + titleBar.clientHeight + 3);
$("#_3dpreviewEditor").on("dialogresizestop", function(event, ui) {
var titleBar = document.getElementById("_3dpreviewEditor").previousSibling;
var _3dpreview = document.getElementById("_3dpreview");
_3dpreview.width = ui.size.width;
_3dpreview.height = (ui.size.height - (titleBar.clientHeight + 3));
hideCircle();
update3dpreview(_3dpreview);
showCircle();
});
hideCircle();
start3dpreview(_3dpreview);
showCircle();
var _3dpreviewContainer = document.getElementById("_3dpreviewContainer");
_3dpreviewContainer.appendChild(_3dpreview);
}
} }