From 5cc245476572ebd20cacdc7bf0f46c38d87fe61b Mon Sep 17 00:00:00 2001 From: Rayzeq Date: Mon, 21 Jun 2021 16:15:27 +0200 Subject: [PATCH 01/18] Better cities and towns labels in 3D view --- modules/save.js | 4 ++ modules/ui/3d.js | 139 ++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 142 insertions(+), 1 deletion(-) diff --git a/modules/save.js b/modules/save.js index 1a2e910db..479f78c9c 100644 --- a/modules/save.js +++ b/modules/save.js @@ -82,6 +82,10 @@ async function getMapURL(type, subtype) { const svgDefs = document.getElementById("defElements"); const isFirefox = navigator.userAgent.toLowerCase().indexOf("firefox") > -1; + if (type === "mesh") { + clone.select("#labels #burgLabels").remove(); + clone.select("#icons #burgIcons").remove(); + } if (isFirefox && type === "mesh") clone.select("#oceanPattern").remove(); if (subtype === "globe") clone.select("#scaleBar").remove(); if (subtype === "noWater") { diff --git a/modules/ui/3d.js b/modules/ui/3d.js index e43f48795..c4e520a81 100644 --- a/modules/ui/3d.js +++ b/modules/ui/3d.js @@ -12,6 +12,8 @@ const options = {scale: 50, lightness: .7, shadow: .5, sun: {x: 100, y: 600, z: let Renderer, scene, camera, controls, animationFrame, material, texture, geometry, mesh, ambientLight, spotLight, waterPlane, waterMaterial, waterMesh, objexporter; +let drawCtx = document.createElement('canvas').getContext('2d'); +let textMeshs = [], iconMeshs = []; // initiate 3d scene const create = async function(canvas, type = "viewMesh") { @@ -42,6 +44,17 @@ const stop = function() { material.dispose(); if (waterPlane) waterPlane.dispose(); if (waterMaterial) waterMaterial.dispose(); + for (const mesh of textMeshs) { + mesh.material.map.dispose(); + mesh.material.dispose(); + mesh.geometry.dispose(); + scene.remove(mesh); + } + for (const mesh of iconMeshs) { + mesh.material.dispose(); + mesh.geometry.dispose(); + scene.remove(mesh); + } Renderer.renderLists.dispose(); // is it required? Renderer.dispose(); @@ -166,12 +179,50 @@ async function newMesh(canvas) { controls.maxPolarAngle = Math.PI/2; controls.autoRotate = Boolean(options.rotateMesh); controls.autoRotateSpeed = options.rotateMesh; - if (controls.autoRotate) animate(); + animate(); controls.addEventListener("change", render); return true; } +function createTextMesh(text, font, size) { + drawCtx.clearRect(0, 0, drawCtx.canvas.width, drawCtx.canvas.height); + drawCtx.font = "50px " + font; + + drawCtx.canvas.width = drawCtx.measureText(text).width; + drawCtx.canvas.height = 50 + 5; + drawCtx.font = "50px " + font; + + drawCtx.fillStyle = "rgba(0,0,0,1)"; + drawCtx.fillText(text, 0, 50); + + // canvas contents will be used for a texture + let text_texture = new THREE.TextureLoader().load(drawCtx.canvas.toDataURL()); + text_texture.minFilter = THREE.LinearFilter + text_texture.needsUpdate = true; + + let text_material = new THREE.MeshBasicMaterial({map: text_texture/*, side:THREE.DoubleSide*/, depthWrite: false}); + text_material.transparent = true; + + let text_mesh = new THREE.Mesh( + new THREE.PlaneGeometry(drawCtx.canvas.width*(size/100), drawCtx.canvas.height*(size/100)), + text_material + ); + text_mesh.renderOrder = 1; + + return text_mesh +} + +function get3dCoords(x, base_y) { + const svg = $('svg#map')[0]; + + let y = getMeshHeight(findGridCell(x, base_y)); + x = x - svg.width.baseVal.value/2; + let z = base_y - svg.height.baseVal.value/2; + + return [x, y, z]; +} + // create a mesh from pixel data async function createMesh(width, height, segmentsX, segmentsY) { const url = await getMapURL("mesh", options.extendedWater ? "noWater" : null); @@ -196,6 +247,87 @@ async function createMesh(width, height, segmentsX, segmentsY) { mesh.castShadow = true; mesh.receiveShadow = true; scene.add(mesh); + + for (const mesh of textMeshs) { + mesh.material.map.dispose(); + mesh.material.dispose(); + mesh.geometry.dispose(); + scene.remove(mesh); + } + textMeshs = [] + + for (const mesh of iconMeshs) { + mesh.material.dispose(); + mesh.geometry.dispose(); + scene.remove(mesh); + } + iconMeshs = [] + + const svg = $('svg#map')[0]; + if(layerIsOn("toggleLabels")) { + const cities_labels = $('svg #viewbox #labels #burgLabels #cities')[0] + + for (const label of cities_labels.childNodes) { + var text_mesh = createTextMesh(label.innerHTML, "Almendra SC", 25) + + const [x, y, z] = get3dCoords(label.x.baseVal[0].value, label.y.baseVal[0].value) + text_mesh.position.set(x, y + 25, z); + text_mesh.animate = function () { + this.lookAt(camera.position); + } + + textMeshs.push(text_mesh) + scene.add(text_mesh); + } + + const towns_labels = $('svg #viewbox #labels #burgLabels #towns')[0] + for (const label of towns_labels.childNodes) { + var text_mesh = createTextMesh(label.innerHTML, "Almendra SC", 7) + + const [x, y, z] = get3dCoords(label.x.baseVal[0].value, label.y.baseVal[0].value) + text_mesh.position.set(x, y + 5, z); + text_mesh.animate = function () { + this.lookAt(camera.position); + if(this.position.distanceTo(camera.position) > 200) { + this.visible = false; + } else { + this.visible = true; + } + } + + textMeshs.push(text_mesh) + scene.add(text_mesh); + } + } + if(layerIsOn("toggleIcons")) { + const cities_icon = $('svg #viewbox #icons #burgIcons #cities')[0] + for (const icon of cities_icon.childNodes) { + var icon_material = new THREE.MeshBasicMaterial({color: 0xcccccc}); + var icon_mesh = new THREE.Mesh( + new THREE.SphereGeometry(2, 16, 16), + icon_material + ); + + icon_mesh.position.set(...get3dCoords(icon.cx.baseVal.value, icon.cy.baseVal.value)) + + iconMeshs.push(icon_mesh); + scene.add(icon_mesh); + } + + const town_icon = $('svg #viewbox #icons #burgIcons #towns')[0] + for (const icon of town_icon.childNodes) { + var icon_material = new THREE.MeshBasicMaterial({color: 0xcccccc}); + var icon_mesh = new THREE.Mesh( + new THREE.SphereGeometry(1, 16, 16), + icon_material + ); + + icon_mesh.position.set(...get3dCoords(icon.cx.baseVal.value, icon.cy.baseVal.value)) + + iconMeshs.push(icon_mesh); + scene.add(icon_mesh); + } + } } function getMeshHeight(i) { @@ -317,6 +449,11 @@ function render() { function animate() { animationFrame = requestAnimationFrame(animate); controls.update(); + for(const mesh of textMeshs) { + if(mesh.animate) { + mesh.animate(); + } + } Renderer.render(scene, camera); } From 91c1de425b4bfbadabca671211f48838f20aa7a6 Mon Sep 17 00:00:00 2001 From: Rayzeq Date: Mon, 21 Jun 2021 18:20:46 +0200 Subject: [PATCH 02/18] Small changes (let -> const) --- modules/ui/3d.js | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/modules/ui/3d.js b/modules/ui/3d.js index c4e520a81..302c4b3a8 100644 --- a/modules/ui/3d.js +++ b/modules/ui/3d.js @@ -197,14 +197,14 @@ function createTextMesh(text, font, size) { drawCtx.fillText(text, 0, 50); // canvas contents will be used for a texture - let text_texture = new THREE.TextureLoader().load(drawCtx.canvas.toDataURL()); + const text_texture = new THREE.TextureLoader().load(drawCtx.canvas.toDataURL()); text_texture.minFilter = THREE.LinearFilter text_texture.needsUpdate = true; - let text_material = new THREE.MeshBasicMaterial({map: text_texture/*, side:THREE.DoubleSide*/, depthWrite: false}); + const text_material = new THREE.MeshBasicMaterial({map: text_texture/*, side:THREE.DoubleSide*/, depthWrite: false}); text_material.transparent = true; - let text_mesh = new THREE.Mesh( + const text_mesh = new THREE.Mesh( new THREE.PlaneGeometry(drawCtx.canvas.width*(size/100), drawCtx.canvas.height*(size/100)), text_material ); @@ -216,9 +216,9 @@ function createTextMesh(text, font, size) { function get3dCoords(x, base_y) { const svg = $('svg#map')[0]; - let y = getMeshHeight(findGridCell(x, base_y)); + const y = getMeshHeight(findGridCell(x, base_y)); x = x - svg.width.baseVal.value/2; - let z = base_y - svg.height.baseVal.value/2; + const z = base_y - svg.height.baseVal.value/2; return [x, y, z]; } @@ -264,11 +264,13 @@ async function createMesh(width, height, segmentsX, segmentsY) { iconMeshs = [] const svg = $('svg#map')[0]; + // Labels if(layerIsOn("toggleLabels")) { + // Cities labels const cities_labels = $('svg #viewbox #labels #burgLabels #cities')[0] for (const label of cities_labels.childNodes) { - var text_mesh = createTextMesh(label.innerHTML, "Almendra SC", 25) + const text_mesh = createTextMesh(label.innerHTML, "Almendra SC", 25) const [x, y, z] = get3dCoords(label.x.baseVal[0].value, label.y.baseVal[0].value) text_mesh.position.set(x, y + 25, z); @@ -280,9 +282,10 @@ async function createMesh(width, height, segmentsX, segmentsY) { scene.add(text_mesh); } + // Town labels const towns_labels = $('svg #viewbox #labels #burgLabels #towns')[0] for (const label of towns_labels.childNodes) { - var text_mesh = createTextMesh(label.innerHTML, "Almendra SC", 7) + const text_mesh = createTextMesh(label.innerHTML, "Almendra SC", 7) const [x, y, z] = get3dCoords(label.x.baseVal[0].value, label.y.baseVal[0].value) text_mesh.position.set(x, y + 5, z); @@ -299,11 +302,12 @@ async function createMesh(width, height, segmentsX, segmentsY) { scene.add(text_mesh); } } + // Icons if(layerIsOn("toggleIcons")) { const cities_icon = $('svg #viewbox #icons #burgIcons #cities')[0] for (const icon of cities_icon.childNodes) { - var icon_material = new THREE.MeshBasicMaterial({color: 0xcccccc}); - var icon_mesh = new THREE.Mesh( + const icon_material = new THREE.MeshBasicMaterial({color: 0xcccccc}); + const icon_mesh = new THREE.Mesh( new THREE.SphereGeometry(2, 16, 16), icon_material ); @@ -316,8 +320,8 @@ async function createMesh(width, height, segmentsX, segmentsY) { const town_icon = $('svg #viewbox #icons #burgIcons #towns')[0] for (const icon of town_icon.childNodes) { - var icon_material = new THREE.MeshBasicMaterial({color: 0xcccccc}); - var icon_mesh = new THREE.Mesh( + const icon_material = new THREE.MeshBasicMaterial({color: 0xcccccc}); + const icon_mesh = new THREE.Mesh( new THREE.SphereGeometry(1, 16, 16), icon_material ); From 6ceb9497ae5f9eb794a745e387618cbac1a35c62 Mon Sep 17 00:00:00 2001 From: Rayzeq Date: Mon, 21 Jun 2021 22:26:52 +0200 Subject: [PATCH 03/18] Lower city labels --- modules/ui/3d.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ui/3d.js b/modules/ui/3d.js index 302c4b3a8..42a667fd2 100644 --- a/modules/ui/3d.js +++ b/modules/ui/3d.js @@ -273,7 +273,7 @@ async function createMesh(width, height, segmentsX, segmentsY) { const text_mesh = createTextMesh(label.innerHTML, "Almendra SC", 25) const [x, y, z] = get3dCoords(label.x.baseVal[0].value, label.y.baseVal[0].value) - text_mesh.position.set(x, y + 25, z); + text_mesh.position.set(x, y + 15, z); text_mesh.animate = function () { this.lookAt(camera.position); } From 280c71bbf2e6a3cd71cd4c5807675324c99d51ab Mon Sep 17 00:00:00 2001 From: Rayzeq Date: Mon, 21 Jun 2021 22:55:08 +0200 Subject: [PATCH 04/18] Change the way the label faces the camera --- modules/ui/3d.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ui/3d.js b/modules/ui/3d.js index 42a667fd2..4ea8c3e48 100644 --- a/modules/ui/3d.js +++ b/modules/ui/3d.js @@ -275,7 +275,7 @@ async function createMesh(width, height, segmentsX, segmentsY) { const [x, y, z] = get3dCoords(label.x.baseVal[0].value, label.y.baseVal[0].value) text_mesh.position.set(x, y + 15, z); text_mesh.animate = function () { - this.lookAt(camera.position); + this.rotation.copy(camera.rotation); } textMeshs.push(text_mesh) @@ -290,11 +290,11 @@ async function createMesh(width, height, segmentsX, segmentsY) { const [x, y, z] = get3dCoords(label.x.baseVal[0].value, label.y.baseVal[0].value) text_mesh.position.set(x, y + 5, z); text_mesh.animate = function () { - this.lookAt(camera.position); if(this.position.distanceTo(camera.position) > 200) { this.visible = false; } else { this.visible = true; + this.rotation.copy(camera.rotation); } } From ca77e1008c3b1c67cf41e5d3b53ece996ad309b4 Mon Sep 17 00:00:00 2001 From: Rayzeq Date: Mon, 21 Jun 2021 22:59:24 +0200 Subject: [PATCH 05/18] Use font from svg instead of hardcoded --- modules/ui/3d.js | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/modules/ui/3d.js b/modules/ui/3d.js index 4ea8c3e48..e0aa1af3f 100644 --- a/modules/ui/3d.js +++ b/modules/ui/3d.js @@ -267,10 +267,9 @@ async function createMesh(width, height, segmentsX, segmentsY) { // Labels if(layerIsOn("toggleLabels")) { // Cities labels - const cities_labels = $('svg #viewbox #labels #burgLabels #cities')[0] - - for (const label of cities_labels.childNodes) { - const text_mesh = createTextMesh(label.innerHTML, "Almendra SC", 25) + const cities = $('#viewbox #labels #burgLabels #cities', svg) + for (const label of cities[0].childNodes) { + const text_mesh = createTextMesh(label.innerHTML, cities.css('font-family'), 25) // cities.data('size') const [x, y, z] = get3dCoords(label.x.baseVal[0].value, label.y.baseVal[0].value) text_mesh.position.set(x, y + 15, z); @@ -283,9 +282,9 @@ async function createMesh(width, height, segmentsX, segmentsY) { } // Town labels - const towns_labels = $('svg #viewbox #labels #burgLabels #towns')[0] - for (const label of towns_labels.childNodes) { - const text_mesh = createTextMesh(label.innerHTML, "Almendra SC", 7) + const towns = $('#viewbox #labels #burgLabels #towns', svg) + for (const label of towns[0].childNodes) { + const text_mesh = createTextMesh(label.innerHTML, towns.css('font-family'), 7) // towns.data('size') const [x, y, z] = get3dCoords(label.x.baseVal[0].value, label.y.baseVal[0].value) text_mesh.position.set(x, y + 5, z); @@ -304,7 +303,7 @@ async function createMesh(width, height, segmentsX, segmentsY) { } // Icons if(layerIsOn("toggleIcons")) { - const cities_icon = $('svg #viewbox #icons #burgIcons #cities')[0] + const cities_icon = $('#viewbox #icons #burgIcons #cities', svg)[0] for (const icon of cities_icon.childNodes) { const icon_material = new THREE.MeshBasicMaterial({color: 0xcccccc}); const icon_mesh = new THREE.Mesh( @@ -318,7 +317,7 @@ async function createMesh(width, height, segmentsX, segmentsY) { scene.add(icon_mesh); } - const town_icon = $('svg #viewbox #icons #burgIcons #towns')[0] + const town_icon = $('#viewbox #icons #burgIcons #towns', svg)[0] for (const icon of town_icon.childNodes) { const icon_material = new THREE.MeshBasicMaterial({color: 0xcccccc}); const icon_mesh = new THREE.Mesh( From 52383267a72d51f1f7e8cb8248613c882608b9a7 Mon Sep 17 00:00:00 2001 From: Rayzeq Date: Tue, 22 Jun 2021 09:36:53 +0200 Subject: [PATCH 06/18] Use color from svg --- modules/ui/3d.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/ui/3d.js b/modules/ui/3d.js index e0aa1af3f..aa005196f 100644 --- a/modules/ui/3d.js +++ b/modules/ui/3d.js @@ -185,7 +185,7 @@ async function newMesh(canvas) { return true; } -function createTextMesh(text, font, size) { +function createTextMesh(text, font, size, color) { drawCtx.clearRect(0, 0, drawCtx.canvas.width, drawCtx.canvas.height); drawCtx.font = "50px " + font; @@ -193,7 +193,7 @@ function createTextMesh(text, font, size) { drawCtx.canvas.height = 50 + 5; drawCtx.font = "50px " + font; - drawCtx.fillStyle = "rgba(0,0,0,1)"; + drawCtx.fillStyle = color; drawCtx.fillText(text, 0, 50); // canvas contents will be used for a texture @@ -264,12 +264,13 @@ async function createMesh(width, height, segmentsX, segmentsY) { iconMeshs = [] const svg = $('svg#map')[0]; + console.log(svg) // Labels if(layerIsOn("toggleLabels")) { // Cities labels const cities = $('#viewbox #labels #burgLabels #cities', svg) for (const label of cities[0].childNodes) { - const text_mesh = createTextMesh(label.innerHTML, cities.css('font-family'), 25) // cities.data('size') + const text_mesh = createTextMesh(label.innerHTML, cities.css('font-family'), 25, cities.css('fill')) // cities.data('size') const [x, y, z] = get3dCoords(label.x.baseVal[0].value, label.y.baseVal[0].value) text_mesh.position.set(x, y + 15, z); @@ -284,7 +285,7 @@ async function createMesh(width, height, segmentsX, segmentsY) { // Town labels const towns = $('#viewbox #labels #burgLabels #towns', svg) for (const label of towns[0].childNodes) { - const text_mesh = createTextMesh(label.innerHTML, towns.css('font-family'), 7) // towns.data('size') + const text_mesh = createTextMesh(label.innerHTML, towns.css('font-family'), 7, towns.css('fill')) // towns.data('size') const [x, y, z] = get3dCoords(label.x.baseVal[0].value, label.y.baseVal[0].value) text_mesh.position.set(x, y + 5, z); From 6b9210efa5e3e603b6e25e08f6b3248695766839 Mon Sep 17 00:00:00 2001 From: Rayzeq Date: Tue, 22 Jun 2021 11:24:43 +0200 Subject: [PATCH 07/18] Optionalize 3d labels --- index.html | 5 ++ modules/save.js | 10 +-- modules/ui/3d.js | 142 ++++++++++++++++++++++-------------------- modules/ui/options.js | 6 ++ 4 files changed, 91 insertions(+), 72 deletions(-) diff --git a/index.html b/index.html index 4d9f90cb6..927a499c2 100644 --- a/index.html +++ b/index.html @@ -3341,6 +3341,11 @@ +
+ + +
+
diff --git a/modules/save.js b/modules/save.js index 479f78c9c..4066532e4 100644 --- a/modules/save.js +++ b/modules/save.js @@ -71,7 +71,7 @@ async function saveJPEG() { } // parse map svg to object url -async function getMapURL(type, subtype) { +async function getMapURL(type, options=[]) { const cloneEl = document.getElementById("map").cloneNode(true); // clone svg cloneEl.id = "fantasyMap"; document.body.appendChild(cloneEl); @@ -82,13 +82,13 @@ async function getMapURL(type, subtype) { const svgDefs = document.getElementById("defElements"); const isFirefox = navigator.userAgent.toLowerCase().indexOf("firefox") > -1; - if (type === "mesh") { + if (isFirefox && type === "mesh") clone.select("#oceanPattern").remove(); + if (options.includes("globe")) clone.select("#scaleBar").remove(); + if (options.includes("noLabels")) { clone.select("#labels #burgLabels").remove(); clone.select("#icons #burgIcons").remove(); } - if (isFirefox && type === "mesh") clone.select("#oceanPattern").remove(); - if (subtype === "globe") clone.select("#scaleBar").remove(); - if (subtype === "noWater") { + if (options.includes("noWater")) { clone.select("#oceanBase").attr("opacity", 0); clone.select("#oceanPattern").attr("opacity", 0); } diff --git a/modules/ui/3d.js b/modules/ui/3d.js index aa005196f..3529f477c 100644 --- a/modules/ui/3d.js +++ b/modules/ui/3d.js @@ -6,7 +6,7 @@ // set default options const options = {scale: 50, lightness: .7, shadow: .5, sun: {x: 100, y: 600, z: 1000}, rotateMesh: 0, rotateGlobe: .5, - skyColor: "#9ecef5", waterColor: "#466eab", extendedWater: 0, resolution: 2}; + skyColor: "#9ecef5", waterColor: "#466eab", extendedWater: 0, labels3d: 0, resolution: 2}; // set variables let Renderer, scene, camera, controls, animationFrame, material, texture, @@ -116,6 +116,11 @@ const toggleSky = function() { redraw(); } +const toggleLabels = function() { + options.labels3d = !options.labels3d; + redraw(); +} + const setColors = function(sky, water) { options.skyColor = sky; scene.background = scene.fog.color = new THREE.Color(sky); @@ -225,8 +230,12 @@ function get3dCoords(x, base_y) { // create a mesh from pixel data async function createMesh(width, height, segmentsX, segmentsY) { - const url = await getMapURL("mesh", options.extendedWater ? "noWater" : null); + const mapOptions = [] + if (options.labels3d) mapOptions.push("noLabels"); + if (options.extendedWater) mapOptions.push("noWater"); + const url = await getMapURL("mesh", mapOptions); window.setTimeout(() => window.URL.revokeObjectURL(url), 3000); + if (texture) texture.dispose(); texture = new THREE.TextureLoader().load(url, render); texture.needsUpdate = true; @@ -263,73 +272,72 @@ async function createMesh(width, height, segmentsX, segmentsY) { } iconMeshs = [] - const svg = $('svg#map')[0]; - console.log(svg) - // Labels - if(layerIsOn("toggleLabels")) { - // Cities labels - const cities = $('#viewbox #labels #burgLabels #cities', svg) - for (const label of cities[0].childNodes) { - const text_mesh = createTextMesh(label.innerHTML, cities.css('font-family'), 25, cities.css('fill')) // cities.data('size') - - const [x, y, z] = get3dCoords(label.x.baseVal[0].value, label.y.baseVal[0].value) - text_mesh.position.set(x, y + 15, z); - text_mesh.animate = function () { - this.rotation.copy(camera.rotation); - } - - textMeshs.push(text_mesh) - scene.add(text_mesh); - } - - // Town labels - const towns = $('#viewbox #labels #burgLabels #towns', svg) - for (const label of towns[0].childNodes) { - const text_mesh = createTextMesh(label.innerHTML, towns.css('font-family'), 7, towns.css('fill')) // towns.data('size') - - const [x, y, z] = get3dCoords(label.x.baseVal[0].value, label.y.baseVal[0].value) - text_mesh.position.set(x, y + 5, z); - text_mesh.animate = function () { - if(this.position.distanceTo(camera.position) > 200) { - this.visible = false; - } else { - this.visible = true; + if (options.labels3d) { + const svg = $('svg#map')[0]; + // Labels + if(layerIsOn("toggleLabels")) { + const cities = $('#viewbox #labels #burgLabels #cities', svg) + for (const label of cities[0].childNodes) { + const text_mesh = createTextMesh(label.innerHTML, cities.css('font-family'), 25, cities.css('fill')) // cities.data('size') + + const [x, y, z] = get3dCoords(label.x.baseVal[0].value, label.y.baseVal[0].value) + text_mesh.position.set(x, y + 15, z); + text_mesh.animate = function () { this.rotation.copy(camera.rotation); } + + textMeshs.push(text_mesh) + scene.add(text_mesh); + } + + const towns = $('#viewbox #labels #burgLabels #towns', svg) + for (const label of towns[0].childNodes) { + const text_mesh = createTextMesh(label.innerHTML, towns.css('font-family'), 7, towns.css('fill')) // towns.data('size') + + const [x, y, z] = get3dCoords(label.x.baseVal[0].value, label.y.baseVal[0].value) + text_mesh.position.set(x, y + 5, z); + text_mesh.animate = function () { + if(this.position.distanceTo(camera.position) > 200) { + this.visible = false; + } else { + this.visible = true; + this.rotation.copy(camera.rotation); + } + } + + textMeshs.push(text_mesh) + scene.add(text_mesh); } - - textMeshs.push(text_mesh) - scene.add(text_mesh); - } - } - // Icons - if(layerIsOn("toggleIcons")) { - const cities_icon = $('#viewbox #icons #burgIcons #cities', svg)[0] - for (const icon of cities_icon.childNodes) { - const icon_material = new THREE.MeshBasicMaterial({color: 0xcccccc}); - const icon_mesh = new THREE.Mesh( - new THREE.SphereGeometry(2, 16, 16), - icon_material - ); - - icon_mesh.position.set(...get3dCoords(icon.cx.baseVal.value, icon.cy.baseVal.value)) - - iconMeshs.push(icon_mesh); - scene.add(icon_mesh); } - - const town_icon = $('#viewbox #icons #burgIcons #towns', svg)[0] - for (const icon of town_icon.childNodes) { - const icon_material = new THREE.MeshBasicMaterial({color: 0xcccccc}); - const icon_mesh = new THREE.Mesh( - new THREE.SphereGeometry(1, 16, 16), - icon_material - ); - - icon_mesh.position.set(...get3dCoords(icon.cx.baseVal.value, icon.cy.baseVal.value)) - - iconMeshs.push(icon_mesh); - scene.add(icon_mesh); + // Icons + if(layerIsOn("toggleIcons")) { + const cities_icon = $('#viewbox #icons #burgIcons #cities', svg)[0] + for (const icon of cities_icon.childNodes) { + const icon_material = new THREE.MeshBasicMaterial({color: 0xcccccc}); + const icon_mesh = new THREE.Mesh( + new THREE.SphereGeometry(2, 16, 16), + icon_material + ); + + icon_mesh.position.set(...get3dCoords(icon.cx.baseVal.value, icon.cy.baseVal.value)) + + iconMeshs.push(icon_mesh); + scene.add(icon_mesh); + } + + const town_icon = $('#viewbox #icons #burgIcons #towns', svg)[0] + for (const icon of town_icon.childNodes) { + const icon_material = new THREE.MeshBasicMaterial({color: 0xcccccc}); + const icon_mesh = new THREE.Mesh( + new THREE.SphereGeometry(1, 16, 16), + icon_material + ); + + icon_mesh.position.set(...get3dCoords(icon.cx.baseVal.value, icon.cy.baseVal.value)) + + iconMeshs.push(icon_mesh); + scene.add(icon_mesh); + } } } } @@ -427,7 +435,7 @@ async function updateGlobeTexure(addMesh) { material.map = texture; if (addMesh) addGlobe3dMesh(); }; - img2.src = await getMapURL("mesh", "globe");; + img2.src = await getMapURL("mesh", ["globe"]); } async function getOBJ() { @@ -497,6 +505,6 @@ function OBJExporter() { }); } -return {create, redraw, update, stop, options, setScale, setLightness, setSun, setRotation, toggleSky, setResolution, setColors, saveScreenshot, saveOBJ}; +return {create, redraw, update, stop, options, setScale, setLightness, setSun, setRotation, toggleLabels, toggleSky, setResolution, setColors, saveScreenshot, saveOBJ}; }))); diff --git a/modules/ui/options.js b/modules/ui/options.js index 48efa169a..ca224fa07 100644 --- a/modules/ui/options.js +++ b/modules/ui/options.js @@ -825,6 +825,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); @@ -841,6 +842,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; @@ -871,6 +873,10 @@ function toggle3dOptions() { ThreeD.setRotation(speed); } + function toggleLabels3d() { + ThreeD.toggleLabels(); + } + function toggleSkyMode() { const hide = ThreeD.options.extendedWater; options3dColorSection.style.display = hide ? "none" : "block"; From c51f3c6a8464e87a468e1437a30b0a657b99bedd Mon Sep 17 00:00:00 2001 From: Rayzeq Date: Tue, 22 Jun 2021 13:26:10 +0200 Subject: [PATCH 08/18] Use pack.burgs instead of svg + Icon use style from svg --- modules/ui/3d.js | 100 ++++++++++++++++++----------------------------- 1 file changed, 38 insertions(+), 62 deletions(-) diff --git a/modules/ui/3d.js b/modules/ui/3d.js index 3529f477c..17f58d169 100644 --- a/modules/ui/3d.js +++ b/modules/ui/3d.js @@ -218,16 +218,6 @@ function createTextMesh(text, font, size, color) { return text_mesh } -function get3dCoords(x, base_y) { - const svg = $('svg#map')[0]; - - const y = getMeshHeight(findGridCell(x, base_y)); - x = x - svg.width.baseVal.value/2; - const z = base_y - svg.height.baseVal.value/2; - - return [x, y, z]; -} - // create a mesh from pixel data async function createMesh(width, height, segmentsX, segmentsY) { const mapOptions = [] @@ -274,67 +264,53 @@ async function createMesh(width, height, segmentsX, segmentsY) { if (options.labels3d) { const svg = $('svg#map')[0]; - // Labels - if(layerIsOn("toggleLabels")) { - const cities = $('#viewbox #labels #burgLabels #cities', svg) - for (const label of cities[0].childNodes) { - const text_mesh = createTextMesh(label.innerHTML, cities.css('font-family'), 25, cities.css('fill')) // cities.data('size') - - const [x, y, z] = get3dCoords(label.x.baseVal[0].value, label.y.baseVal[0].value) - text_mesh.position.set(x, y + 15, z); - text_mesh.animate = function () { - this.rotation.copy(camera.rotation); + const cities = $('#viewbox #labels #burgLabels #cities', svg); + const towns = $('#viewbox #labels #burgLabels #towns', svg); + const cities_icons = $('#viewbox #icons #burgIcons #cities', svg); + const towns_icons = $('#viewbox #icons #burgIcons #towns', svg); + + for (const burg of pack.burgs) { + const x = burg.x - svg.width.baseVal.value/2; + const y = getMeshHeight(findGridCell(burg.x, burg.y)); // work better than getMeshHeight(burg.cell) but I don't know why + const z = burg.y - svg.height.baseVal.value/2; + + if(layerIsOn("toggleLabels")) { + if (burg.capital) { + var text_mesh = createTextMesh(burg.name, cities.css('font-family'), 25, cities.css('fill')); // cities.data('size') + } else { + var text_mesh = createTextMesh(burg.name, towns.css('font-family'), 7, towns.css('fill')); // towns.data('size') } - - textMeshs.push(text_mesh) - scene.add(text_mesh); - } - - const towns = $('#viewbox #labels #burgLabels #towns', svg) - for (const label of towns[0].childNodes) { - const text_mesh = createTextMesh(label.innerHTML, towns.css('font-family'), 7, towns.css('fill')) // towns.data('size') - - const [x, y, z] = get3dCoords(label.x.baseVal[0].value, label.y.baseVal[0].value) - text_mesh.position.set(x, y + 5, z); - text_mesh.animate = function () { - if(this.position.distanceTo(camera.position) > 200) { - this.visible = false; - } else { - this.visible = true; + + if (burg.capital) { + text_mesh.position.set(x, y + 15, z); + text_mesh.animate = function () { this.rotation.copy(camera.rotation); } + } else { + text_mesh.position.set(x, y + 5, z); + text_mesh.animate = function () { + if(this.position.distanceTo(camera.position) > 200) { + this.visible = false; + } else { + this.visible = true; + this.rotation.copy(camera.rotation); + } + } } - - textMeshs.push(text_mesh) + + textMeshs.push(text_mesh); scene.add(text_mesh); } - } - // Icons - if(layerIsOn("toggleIcons")) { - const cities_icon = $('#viewbox #icons #burgIcons #cities', svg)[0] - for (const icon of cities_icon.childNodes) { - const icon_material = new THREE.MeshBasicMaterial({color: 0xcccccc}); - const icon_mesh = new THREE.Mesh( - new THREE.SphereGeometry(2, 16, 16), - icon_material - ); - - icon_mesh.position.set(...get3dCoords(icon.cx.baseVal.value, icon.cy.baseVal.value)) - - iconMeshs.push(icon_mesh); - scene.add(icon_mesh); - } - - const town_icon = $('#viewbox #icons #burgIcons #towns', svg)[0] - for (const icon of town_icon.childNodes) { - const icon_material = new THREE.MeshBasicMaterial({color: 0xcccccc}); + + // Icon + if(layerIsOn("toggleIcons")) { + const icon_material = new THREE.MeshBasicMaterial({color: burg.capital ? cities_icons.attr('fill') : towns_icons.attr('fill')}); const icon_mesh = new THREE.Mesh( - new THREE.SphereGeometry(1, 16, 16), + new THREE.SphereGeometry((burg.capital ? cities_icons.attr("size") : towns_icons.attr("size")) * 2, 16, 16), icon_material ); - - icon_mesh.position.set(...get3dCoords(icon.cx.baseVal.value, icon.cy.baseVal.value)) - + icon_mesh.position.set(x, y, z) + iconMeshs.push(icon_mesh); scene.add(icon_mesh); } From d874f9a3c4ee570b4946dcdd6c7f4bbbe5414137 Mon Sep 17 00:00:00 2001 From: Rayzeq Date: Tue, 22 Jun 2021 15:50:15 +0200 Subject: [PATCH 09/18] Replace jQuery with d3 --- modules/ui/3d.js | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/modules/ui/3d.js b/modules/ui/3d.js index 17f58d169..fd8b211e9 100644 --- a/modules/ui/3d.js +++ b/modules/ui/3d.js @@ -263,22 +263,21 @@ async function createMesh(width, height, segmentsX, segmentsY) { iconMeshs = [] if (options.labels3d) { - const svg = $('svg#map')[0]; - const cities = $('#viewbox #labels #burgLabels #cities', svg); - const towns = $('#viewbox #labels #burgLabels #towns', svg); - const cities_icons = $('#viewbox #icons #burgIcons #cities', svg); - const towns_icons = $('#viewbox #icons #burgIcons #towns', svg); + const cities = svg.select("#viewbox #labels #burgLabels #cities"); + const towns = svg.select('#viewbox #labels #burgLabels #towns'); + const cities_icons = svg.select('#viewbox #icons #burgIcons #cities'); + const towns_icons = svg.select('#viewbox #icons #burgIcons #towns'); for (const burg of pack.burgs) { - const x = burg.x - svg.width.baseVal.value/2; + const x = burg.x - svg.attr("width")/2; const y = getMeshHeight(findGridCell(burg.x, burg.y)); // work better than getMeshHeight(burg.cell) but I don't know why - const z = burg.y - svg.height.baseVal.value/2; + const z = burg.y - svg.attr("height")/2; if(layerIsOn("toggleLabels")) { if (burg.capital) { - var text_mesh = createTextMesh(burg.name, cities.css('font-family'), 25, cities.css('fill')); // cities.data('size') + var text_mesh = createTextMesh(burg.name, cities.attr('font-family'), 25, cities.attr('fill')); // cities.data('size') } else { - var text_mesh = createTextMesh(burg.name, towns.css('font-family'), 7, towns.css('fill')); // towns.data('size') + var text_mesh = createTextMesh(burg.name, towns.attr('font-family'), 7, towns.attr('fill')); // towns.data('size') } if (burg.capital) { From ab61c30e20b23b945c044ab4b4e3d821375f38eb Mon Sep 17 00:00:00 2001 From: Rayzeq Date: Tue, 22 Jun 2021 16:42:54 +0200 Subject: [PATCH 10/18] Labels adapts to height scale --- modules/ui/3d.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/modules/ui/3d.js b/modules/ui/3d.js index fd8b211e9..7c43838db 100644 --- a/modules/ui/3d.js +++ b/modules/ui/3d.js @@ -78,6 +78,14 @@ const stop = function() { const setScale = function(scale) { options.scale = scale; + + for (const mesh of textMeshs) { + mesh.position.y = getMeshHeight(findGridCell(mesh.base_x, mesh.base_y)) + mesh.base_height; + } + for (const mesh of iconMeshs) { + mesh.position.y = getMeshHeight(findGridCell(mesh.base_x, mesh.base_y)); + + } geometry.vertices.forEach((v, i) => v.z = getMeshHeight(i)); geometry.verticesNeedUpdate = true; geometry.computeVertexNormals(); @@ -282,11 +290,13 @@ async function createMesh(width, height, segmentsX, segmentsY) { if (burg.capital) { text_mesh.position.set(x, y + 15, z); + text_mesh.base_height = 15 text_mesh.animate = function () { this.rotation.copy(camera.rotation); } } else { text_mesh.position.set(x, y + 5, z); + text_mesh.base_height = 5 text_mesh.animate = function () { if(this.position.distanceTo(camera.position) > 200) { this.visible = false; @@ -296,6 +306,8 @@ async function createMesh(width, height, segmentsX, segmentsY) { } } } + text_mesh.base_x = burg.x + text_mesh.base_y = burg.y textMeshs.push(text_mesh); scene.add(text_mesh); @@ -309,6 +321,8 @@ async function createMesh(width, height, segmentsX, segmentsY) { icon_material ); icon_mesh.position.set(x, y, z) + icon_mesh.base_x = burg.x + icon_mesh.base_y = burg.y iconMeshs.push(icon_mesh); scene.add(icon_mesh); From b871b63c7ea1d66546ed06867d7c31175cfb167c Mon Sep 17 00:00:00 2001 From: Rayzeq Date: Thu, 24 Jun 2021 10:37:06 +0200 Subject: [PATCH 11/18] Fix bug: options are not applied when changing layer --- modules/ui/3d.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/ui/3d.js b/modules/ui/3d.js index 7c43838db..2ca03d3ab 100644 --- a/modules/ui/3d.js +++ b/modules/ui/3d.js @@ -351,7 +351,10 @@ function extendWater(width, height) { async function update3dTexture() { if (texture) texture.dispose(); - const url = await getMapURL("mesh"); + const mapOptions = [] + if (options.labels3d) mapOptions.push("noLabels"); + if (options.extendedWater) mapOptions.push("noWater"); + const url = await getMapURL("mesh", mapOptions); window.setTimeout(() => window.URL.revokeObjectURL(url), 3000); texture = new THREE.TextureLoader().load(url, render); material.map = texture; From 30ab1217a81d197ad1c711265c60acec0bc05a7b Mon Sep 17 00:00:00 2001 From: Rayzeq Date: Fri, 25 Jun 2021 00:15:45 +0200 Subject: [PATCH 12/18] Add states labels + Replace canvas by svg --- modules/save.js | 1 + modules/ui/3d.js | 260 +++++++++++++++++++++++++++-------------------- 2 files changed, 152 insertions(+), 109 deletions(-) diff --git a/modules/save.js b/modules/save.js index 4066532e4..138928e15 100644 --- a/modules/save.js +++ b/modules/save.js @@ -85,6 +85,7 @@ async function getMapURL(type, options=[]) { if (isFirefox && type === "mesh") clone.select("#oceanPattern").remove(); if (options.includes("globe")) clone.select("#scaleBar").remove(); if (options.includes("noLabels")) { + clone.select("#labels #states").remove(); clone.select("#labels #burgLabels").remove(); clone.select("#icons #burgIcons").remove(); } diff --git a/modules/ui/3d.js b/modules/ui/3d.js index 2ca03d3ab..c80a63cc3 100644 --- a/modules/ui/3d.js +++ b/modules/ui/3d.js @@ -12,7 +12,8 @@ const options = {scale: 50, lightness: .7, shadow: .5, sun: {x: 100, y: 600, z: let Renderer, scene, camera, controls, animationFrame, material, texture, geometry, mesh, ambientLight, spotLight, waterPlane, waterMaterial, waterMesh, objexporter; -let drawCtx = document.createElement('canvas').getContext('2d'); +const drawSVG = document.createElementNS("http://www.w3.org/2000/svg", "svg"); +document.body.appendChild(drawSVG); let textMeshs = [], iconMeshs = []; // initiate 3d scene @@ -44,17 +45,7 @@ const stop = function() { material.dispose(); if (waterPlane) waterPlane.dispose(); if (waterMaterial) waterMaterial.dispose(); - for (const mesh of textMeshs) { - mesh.material.map.dispose(); - mesh.material.dispose(); - mesh.geometry.dispose(); - scene.remove(mesh); - } - for (const mesh of iconMeshs) { - mesh.material.dispose(); - mesh.geometry.dispose(); - scene.remove(mesh); - } + deleteLabels(); Renderer.renderLists.dispose(); // is it required? Renderer.dispose(); @@ -84,7 +75,6 @@ const setScale = function(scale) { } for (const mesh of iconMeshs) { mesh.position.y = getMeshHeight(findGridCell(mesh.base_x, mesh.base_y)); - } geometry.vertices.forEach((v, i) => v.z = getMeshHeight(i)); geometry.verticesNeedUpdate = true; @@ -125,8 +115,10 @@ const toggleSky = function() { } const toggleLabels = function() { + if (options.labels3d) deleteLabels(); else createLabels(); + options.labels3d = !options.labels3d; - redraw(); + update(); } const setColors = function(sky, water) { @@ -198,32 +190,149 @@ async function newMesh(canvas) { return true; } -function createTextMesh(text, font, size, color) { - drawCtx.clearRect(0, 0, drawCtx.canvas.width, drawCtx.canvas.height); - drawCtx.font = "50px " + font; - - drawCtx.canvas.width = drawCtx.measureText(text).width; - drawCtx.canvas.height = 50 + 5; - drawCtx.font = "50px " + font; - - drawCtx.fillStyle = color; - drawCtx.fillText(text, 0, 50); - - // canvas contents will be used for a texture - const text_texture = new THREE.TextureLoader().load(drawCtx.canvas.toDataURL()); - text_texture.minFilter = THREE.LinearFilter - text_texture.needsUpdate = true; - - const text_material = new THREE.MeshBasicMaterial({map: text_texture/*, side:THREE.DoubleSide*/, depthWrite: false}); - text_material.transparent = true; - - const text_mesh = new THREE.Mesh( - new THREE.PlaneGeometry(drawCtx.canvas.width*(size/100), drawCtx.canvas.height*(size/100)), - text_material +function svg2base64(svg) { + const str_xml = new XMLSerializer().serializeToString(svg); + return 'data:image/svg+xml;base64,' + btoa(str_xml); +} + +function svg2mesh(svg, sx=1, sy=1) { + svg.removeAttribute("viewBox"); + const bbox = svg.getBBox(); + svg.setAttribute("viewBox", [bbox.x, bbox.y, bbox.width, bbox.height].join(" ")); + svg.setAttribute("width", bbox.width); + svg.setAttribute("height", bbox.height); + + const texture = new THREE.TextureLoader().load(svg2base64(svg)); + texture.minFilter = THREE.LinearFilter; // remove `texture has been resized` warning + + const material = new THREE.MeshBasicMaterial({map: texture, side:THREE.DoubleSide, depthWrite: false}); + material.transparent = true; + + const mesh = new THREE.Mesh( + new THREE.PlaneGeometry(1, 1), + material ); - text_mesh.renderOrder = 1; + mesh.scale.x = bbox.width * (sx / 100); + mesh.scale.y = bbox.height * (sy / 100); + mesh.renderOrder = 1; - return text_mesh + return mesh; +} + +function createStateText(font, size, color, label) { + drawSVG.innerHTML = ""; + drawSVG.appendChild(label.cloneNode(true)); + drawSVG.children[0].innerHTML = ``; + drawSVG.children[0].appendChild(svg.select(label.childNodes[0].href.baseVal).node().cloneNode(true)); // href of path in defs + drawSVG.children[1].setAttribute("transform", `scale(${5} ${5})`) + drawSVG.children[1].setAttribute('font-family', font); + drawSVG.children[1].setAttribute('font-size', size); + drawSVG.children[1].setAttribute('fill', color); + + const mesh = svg2mesh(drawSVG, 20, 20); + mesh.rotation.set(THREE.Math.degToRad(-90), 0, 0); + + return mesh; +} + +function createBurgText(text, font, size, color) { + drawSVG.innerHTML = `${text} +`; + return svg2mesh(drawSVG, 7*2, 7*2); +} + +function get3dCoords(base_x, base_y) { + const x = base_x - svg.attr("width")/2; + const y = getMeshHeight(findGridCell(base_x, base_y)); // work better than getMeshHeight(burg.cell) but I don't know why + const z = base_y - svg.attr("height")/2; + return [x, y, z]; +} + +function createLabels() { + // Burg labels + const cities = svg.select("#viewbox #labels #burgLabels #cities"); + const towns = svg.select('#viewbox #labels #burgLabels #towns'); + const cities_icons = svg.select('#viewbox #icons #burgIcons #cities'); + const towns_icons = svg.select('#viewbox #icons #burgIcons #towns'); + + for (const burg of pack.burgs) { + const [x, y, z] = get3dCoords(burg.x, burg.y) + + if(layerIsOn("toggleLabels")) { + if (burg.capital) { + var text_mesh = createBurgText(burg.name, cities.attr('font-family'), cities.attr('font-size'), cities.attr('fill')); // cities.attr('font-size') + } else { + var text_mesh = createBurgText(burg.name, towns.attr('font-family'), towns.attr('font-size'), towns.attr('fill')); // towns.attr('font-size') + } + + if (burg.capital) { + text_mesh.position.set(x, y + 15, z); + text_mesh.base_height = 15; + text_mesh.animate = function () { + this.rotation.copy(camera.rotation); + } + } else { + text_mesh.position.set(x, y + 5, z); + text_mesh.base_height = 5; + text_mesh.animate = function () { + if(this.position.distanceTo(camera.position) > 200) { + this.visible = false; + } else { + this.visible = true; + this.rotation.copy(camera.rotation); + } + } + } + + text_mesh.base_x = burg.x; + text_mesh.base_y = burg.y; + + textMeshs.push(text_mesh); + scene.add(text_mesh); + } + + // Icon + if(layerIsOn("toggleIcons")) { + const icon_material = new THREE.MeshBasicMaterial({color: burg.capital ? cities_icons.attr('fill') : towns_icons.attr('fill')}); + const icon_mesh = new THREE.Mesh( + new THREE.SphereGeometry((burg.capital ? cities_icons.attr("size") : towns_icons.attr("size")) * 2, 16, 16), + icon_material + ); + icon_mesh.position.set(x, y, z) + icon_mesh.base_x = burg.x + icon_mesh.base_y = burg.y + + iconMeshs.push(icon_mesh); + scene.add(icon_mesh); + } + } + + // State labels + const state_labels = svg.select("#viewbox #labels #states") + for (const label of state_labels.node().children) { + const text_mesh = createStateText(state_labels.attr("font-family"), state_labels.attr("font-size"), state_labels.attr("fill"), label); + const id = label.id.match(/\d+$/); + const pos = pack.states[id].pole + const [x, y, z] = get3dCoords(pos[0], pos[1]) + text_mesh.position.set(x, y + 25, z); + textMeshs.push(text_mesh) + scene.add(text_mesh); + } +} + +function deleteLabels() { + for (const mesh of textMeshs) { + mesh.material.map.dispose(); + mesh.material.dispose(); + mesh.geometry.dispose(); + scene.remove(mesh); + } + + for (const mesh of iconMeshs) { + mesh.material.dispose(); + mesh.geometry.dispose(); + scene.remove(mesh); + } } // create a mesh from pixel data @@ -255,79 +364,12 @@ async function createMesh(width, height, segmentsX, segmentsY) { mesh.receiveShadow = true; scene.add(mesh); - for (const mesh of textMeshs) { - mesh.material.map.dispose(); - mesh.material.dispose(); - mesh.geometry.dispose(); - scene.remove(mesh); - } - textMeshs = [] - - for (const mesh of iconMeshs) { - mesh.material.dispose(); - mesh.geometry.dispose(); - scene.remove(mesh); - } - iconMeshs = [] + deleteLabels(); + textMeshs = []; + iconMeshs = []; if (options.labels3d) { - const cities = svg.select("#viewbox #labels #burgLabels #cities"); - const towns = svg.select('#viewbox #labels #burgLabels #towns'); - const cities_icons = svg.select('#viewbox #icons #burgIcons #cities'); - const towns_icons = svg.select('#viewbox #icons #burgIcons #towns'); - - for (const burg of pack.burgs) { - const x = burg.x - svg.attr("width")/2; - const y = getMeshHeight(findGridCell(burg.x, burg.y)); // work better than getMeshHeight(burg.cell) but I don't know why - const z = burg.y - svg.attr("height")/2; - - if(layerIsOn("toggleLabels")) { - if (burg.capital) { - var text_mesh = createTextMesh(burg.name, cities.attr('font-family'), 25, cities.attr('fill')); // cities.data('size') - } else { - var text_mesh = createTextMesh(burg.name, towns.attr('font-family'), 7, towns.attr('fill')); // towns.data('size') - } - - if (burg.capital) { - text_mesh.position.set(x, y + 15, z); - text_mesh.base_height = 15 - text_mesh.animate = function () { - this.rotation.copy(camera.rotation); - } - } else { - text_mesh.position.set(x, y + 5, z); - text_mesh.base_height = 5 - text_mesh.animate = function () { - if(this.position.distanceTo(camera.position) > 200) { - this.visible = false; - } else { - this.visible = true; - this.rotation.copy(camera.rotation); - } - } - } - text_mesh.base_x = burg.x - text_mesh.base_y = burg.y - - textMeshs.push(text_mesh); - scene.add(text_mesh); - } - - // Icon - if(layerIsOn("toggleIcons")) { - const icon_material = new THREE.MeshBasicMaterial({color: burg.capital ? cities_icons.attr('fill') : towns_icons.attr('fill')}); - const icon_mesh = new THREE.Mesh( - new THREE.SphereGeometry((burg.capital ? cities_icons.attr("size") : towns_icons.attr("size")) * 2, 16, 16), - icon_material - ); - icon_mesh.position.set(x, y, z) - icon_mesh.base_x = burg.x - icon_mesh.base_y = burg.y - - iconMeshs.push(icon_mesh); - scene.add(icon_mesh); - } - } + createLabels(); } } From 58ad13cbfd89fce2a48b86cdbcf458de87e8412c Mon Sep 17 00:00:00 2001 From: Rayzeq Date: Fri, 25 Jun 2021 09:28:11 +0200 Subject: [PATCH 13/18] Fix: States labels adapts to height scale --- modules/ui/3d.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/ui/3d.js b/modules/ui/3d.js index c80a63cc3..474c50c1d 100644 --- a/modules/ui/3d.js +++ b/modules/ui/3d.js @@ -315,6 +315,10 @@ function createLabels() { const pos = pack.states[id].pole const [x, y, z] = get3dCoords(pos[0], pos[1]) text_mesh.position.set(x, y + 25, z); + text_mesh.base_x = pos[0]; + text_mesh.base_y = pos[1]; + text_mesh.base_height = 25; + textMeshs.push(text_mesh) scene.add(text_mesh); } From 143e1dfbd761993ae8626b8d206d7e2528d1ae69 Mon Sep 17 00:00:00 2001 From: Rayzeq Date: Wed, 30 Jun 2021 10:33:19 +0200 Subject: [PATCH 14/18] Requested fixes --- modules/save.js | 8 ++++---- modules/ui/3d.js | 47 +++++++++++++++++++++++++++-------------------- 2 files changed, 31 insertions(+), 24 deletions(-) diff --git a/modules/save.js b/modules/save.js index 138928e15..84d0aaaa0 100644 --- a/modules/save.js +++ b/modules/save.js @@ -71,7 +71,7 @@ async function saveJPEG() { } // parse map svg to object url -async function getMapURL(type, options=[]) { +async function getMapURL(type, {globe=false, noLabels=false, noWater=false}) { const cloneEl = document.getElementById("map").cloneNode(true); // clone svg cloneEl.id = "fantasyMap"; document.body.appendChild(cloneEl); @@ -83,13 +83,13 @@ async function getMapURL(type, options=[]) { const isFirefox = navigator.userAgent.toLowerCase().indexOf("firefox") > -1; if (isFirefox && type === "mesh") clone.select("#oceanPattern").remove(); - if (options.includes("globe")) clone.select("#scaleBar").remove(); - if (options.includes("noLabels")) { + if (globe) clone.select("#scaleBar").remove(); + if (noLabels) { clone.select("#labels #states").remove(); clone.select("#labels #burgLabels").remove(); clone.select("#icons #burgIcons").remove(); } - if (options.includes("noWater")) { + if (noWater) { clone.select("#oceanBase").attr("opacity", 0); clone.select("#oceanPattern").attr("opacity", 0); } diff --git a/modules/ui/3d.js b/modules/ui/3d.js index 474c50c1d..42552f2db 100644 --- a/modules/ui/3d.js +++ b/modules/ui/3d.js @@ -15,6 +15,7 @@ let Renderer, scene, camera, controls, animationFrame, material, texture, const drawSVG = document.createElementNS("http://www.w3.org/2000/svg", "svg"); document.body.appendChild(drawSVG); let textMeshs = [], iconMeshs = []; +const fontCache = {"Georgia": "", "Times New Roman": "", "Comic Sans MS": "", "Lucida Sans Unicode": "", "Courier New": "", "Verdana": "", "Arial": "", "Impact": ""} // default are web-safe fonts // initiate 3d scene const create = async function(canvas, type = "viewMesh") { @@ -115,10 +116,14 @@ const toggleSky = function() { } const toggleLabels = function() { - if (options.labels3d) deleteLabels(); else createLabels(); + if (options.labels3d) { + deleteLabels(); + update(); + } else { + createLabels().then(() => update()); + } options.labels3d = !options.labels3d; - update(); } const setColors = function(sky, water) { @@ -219,10 +224,11 @@ function svg2mesh(svg, sx=1, sy=1) { return mesh; } -function createStateText(font, size, color, label) { +async function createStateText(font, size, color, label) { drawSVG.innerHTML = ""; drawSVG.appendChild(label.cloneNode(true)); - drawSVG.children[0].innerHTML = ``; + if (fontCache[font] == undefined) {fontCache[font] = await GFontToDataURI(`https://fonts.googleapis.com/css?family=${font}`)} + drawSVG.children[0].innerHTML = ``; drawSVG.children[0].appendChild(svg.select(label.childNodes[0].href.baseVal).node().cloneNode(true)); // href of path in defs drawSVG.children[1].setAttribute("transform", `scale(${5} ${5})`) drawSVG.children[1].setAttribute('font-family', font); @@ -235,20 +241,21 @@ function createStateText(font, size, color, label) { return mesh; } -function createBurgText(text, font, size, color) { +async function createBurgText(text, font, size, color) { + if (fontCache[font] == undefined) {fontCache[font] = await GFontToDataURI(`https://fonts.googleapis.com/css?family=${font}`)} drawSVG.innerHTML = `${text} -`; +`; return svg2mesh(drawSVG, 7*2, 7*2); } function get3dCoords(base_x, base_y) { - const x = base_x - svg.attr("width")/2; + const x = base_x - graphWidth/2; const y = getMeshHeight(findGridCell(base_x, base_y)); // work better than getMeshHeight(burg.cell) but I don't know why - const z = base_y - svg.attr("height")/2; + const z = base_y - graphHeight/2; return [x, y, z]; } -function createLabels() { +async function createLabels() { // Burg labels const cities = svg.select("#viewbox #labels #burgLabels #cities"); const towns = svg.select('#viewbox #labels #burgLabels #towns'); @@ -260,9 +267,9 @@ function createLabels() { if(layerIsOn("toggleLabels")) { if (burg.capital) { - var text_mesh = createBurgText(burg.name, cities.attr('font-family'), cities.attr('font-size'), cities.attr('fill')); // cities.attr('font-size') + var text_mesh = await createBurgText(burg.name, cities.attr('font-family'), cities.attr('font-size'), cities.attr('fill')); // cities.attr('font-size') } else { - var text_mesh = createBurgText(burg.name, towns.attr('font-family'), towns.attr('font-size'), towns.attr('fill')); // towns.attr('font-size') + var text_mesh = await createBurgText(burg.name, towns.attr('font-family'), towns.attr('font-size'), towns.attr('fill')); // towns.attr('font-size') } if (burg.capital) { @@ -310,7 +317,7 @@ function createLabels() { // State labels const state_labels = svg.select("#viewbox #labels #states") for (const label of state_labels.node().children) { - const text_mesh = createStateText(state_labels.attr("font-family"), state_labels.attr("font-size"), state_labels.attr("fill"), label); + const text_mesh = await createStateText(state_labels.attr("font-family"), state_labels.attr("font-size"), state_labels.attr("fill"), label); const id = label.id.match(/\d+$/); const pos = pack.states[id].pole const [x, y, z] = get3dCoords(pos[0], pos[1]) @@ -341,9 +348,9 @@ function deleteLabels() { // create a mesh from pixel data async function createMesh(width, height, segmentsX, segmentsY) { - const mapOptions = [] - if (options.labels3d) mapOptions.push("noLabels"); - if (options.extendedWater) mapOptions.push("noWater"); + const mapOptions = {} + if (options.labels3d) mapOptions.noLabels = true; + if (options.extendedWater) mapOptions.noWater = true; const url = await getMapURL("mesh", mapOptions); window.setTimeout(() => window.URL.revokeObjectURL(url), 3000); @@ -373,7 +380,7 @@ async function createMesh(width, height, segmentsX, segmentsY) { iconMeshs = []; if (options.labels3d) { - createLabels(); + await createLabels(); } } @@ -397,9 +404,9 @@ function extendWater(width, height) { async function update3dTexture() { if (texture) texture.dispose(); - const mapOptions = [] - if (options.labels3d) mapOptions.push("noLabels"); - if (options.extendedWater) mapOptions.push("noWater"); + const mapOptions = {} + if (options.labels3d) mapOptions.noLabels = true; + if (options.extendedWater) mapOptions.noWater = true; const url = await getMapURL("mesh", mapOptions); window.setTimeout(() => window.URL.revokeObjectURL(url), 3000); texture = new THREE.TextureLoader().load(url, render); @@ -473,7 +480,7 @@ async function updateGlobeTexure(addMesh) { material.map = texture; if (addMesh) addGlobe3dMesh(); }; - img2.src = await getMapURL("mesh", ["globe"]); + img2.src = await getMapURL("mesh", {globe: true}); } async function getOBJ() { From bd4deaac7e2dd3a461022185723a95d62d3f282e Mon Sep 17 00:00:00 2001 From: Rayzeq Date: Fri, 2 Jul 2021 10:59:09 +0200 Subject: [PATCH 15/18] All fonts working properly + Big memory optimization + Minor fixes --- modules/ui/3d.js | 61 +++++++++++++++++++++++++++++++----------------- 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/modules/ui/3d.js b/modules/ui/3d.js index 42552f2db..898e02f2e 100644 --- a/modules/ui/3d.js +++ b/modules/ui/3d.js @@ -11,7 +11,7 @@ const options = {scale: 50, lightness: .7, shadow: .5, sun: {x: 100, y: 600, z: // set variables let Renderer, scene, camera, controls, animationFrame, material, texture, geometry, mesh, ambientLight, spotLight, waterPlane, waterMaterial, waterMesh, - objexporter; + objexporter, square_geometry, texture_loader; const drawSVG = document.createElementNS("http://www.w3.org/2000/svg", "svg"); document.body.appendChild(drawSVG); let textMeshs = [], iconMeshs = []; @@ -116,14 +116,14 @@ const toggleSky = function() { } const toggleLabels = function() { + options.labels3d = !options.labels3d; + if (options.labels3d) { + createLabels().then(() => update()); + } else { deleteLabels(); update(); - } else { - createLabels().then(() => update()); } - - options.labels3d = !options.labels3d; } const setColors = function(sky, water) { @@ -207,14 +207,14 @@ function svg2mesh(svg, sx=1, sy=1) { svg.setAttribute("width", bbox.width); svg.setAttribute("height", bbox.height); - const texture = new THREE.TextureLoader().load(svg2base64(svg)); + const texture = new texture_loader.load(svg2base64(svg)); texture.minFilter = THREE.LinearFilter; // remove `texture has been resized` warning - const material = new THREE.MeshBasicMaterial({map: texture, side:THREE.DoubleSide, depthWrite: false}); + const material = new THREE.MeshBasicMaterial({map: texture, side: THREE.DoubleSide, depthWrite: false}); material.transparent = true; const mesh = new THREE.Mesh( - new THREE.PlaneGeometry(1, 1), + square_geometry, material ); mesh.scale.x = bbox.width * (sx / 100); @@ -227,7 +227,7 @@ function svg2mesh(svg, sx=1, sy=1) { async function createStateText(font, size, color, label) { drawSVG.innerHTML = ""; drawSVG.appendChild(label.cloneNode(true)); - if (fontCache[font] == undefined) {fontCache[font] = await GFontToDataURI(`https://fonts.googleapis.com/css?family=${font}`)} + if (fontCache[font] == undefined) {fontCache[font] = (await GFontToDataURI(`https://fonts.googleapis.com/css?family=${font}`)).join('\n');} drawSVG.children[0].innerHTML = ``; drawSVG.children[0].appendChild(svg.select(label.childNodes[0].href.baseVal).node().cloneNode(true)); // href of path in defs drawSVG.children[1].setAttribute("transform", `scale(${5} ${5})`) @@ -241,11 +241,11 @@ async function createStateText(font, size, color, label) { return mesh; } -async function createBurgText(text, font, size, color) { - if (fontCache[font] == undefined) {fontCache[font] = await GFontToDataURI(`https://fonts.googleapis.com/css?family=${font}`)} - drawSVG.innerHTML = `${text} +async function createBurgText(text, font, size, color, quality=1) { // for quality: lower value mean higher quality + if (fontCache[font] == undefined) {fontCache[font] = (await GFontToDataURI(`https://fonts.googleapis.com/css?family=${font}`)).join("\n");} + drawSVG.innerHTML = `${text} `; - return svg2mesh(drawSVG, 7*2, 7*2); + return svg2mesh(drawSVG, 7*quality, 7*quality); } function get3dCoords(base_x, base_y) { @@ -256,12 +256,19 @@ function get3dCoords(base_x, base_y) { } async function createLabels() { + square_geometry = new THREE.PlaneGeometry(1, 1); + texture_loader = new THREE.TextureLoader(); + // Burg labels const cities = svg.select("#viewbox #labels #burgLabels #cities"); const towns = svg.select('#viewbox #labels #burgLabels #towns'); const cities_icons = svg.select('#viewbox #icons #burgIcons #cities'); const towns_icons = svg.select('#viewbox #icons #burgIcons #towns'); + const citie_icon_material = new THREE.MeshBasicMaterial({color: cities_icons.attr('fill')}); + const town_icon_material = new THREE.MeshBasicMaterial({color: towns_icons.attr('fill')}); + const citie_icon_geometry = new THREE.SphereGeometry(cities_icons.attr("size") * 2, 16, 16); + const town_icon_geometry = new THREE.SphereGeometry(towns_icons.attr("size") * 2, 16, 16); for (const burg of pack.burgs) { const [x, y, z] = get3dCoords(burg.x, burg.y) @@ -300,10 +307,9 @@ async function createLabels() { // Icon if(layerIsOn("toggleIcons")) { - const icon_material = new THREE.MeshBasicMaterial({color: burg.capital ? cities_icons.attr('fill') : towns_icons.attr('fill')}); const icon_mesh = new THREE.Mesh( - new THREE.SphereGeometry((burg.capital ? cities_icons.attr("size") : towns_icons.attr("size")) * 2, 16, 16), - icon_material + burg.capital ? citie_icon_geometry : town_icon_geometry, + burg.capital ? citie_icon_material : town_icon_material ); icon_mesh.position.set(x, y, z) icon_mesh.base_x = burg.x @@ -332,18 +338,31 @@ async function createLabels() { } function deleteLabels() { - for (const mesh of textMeshs) { + if (square_geometry) square_geometry.dispose(); + square_geometry = undefined; + texture_loader = undefined; + + for (const [i, mesh] of textMeshs.entries()) { + scene.remove(mesh); mesh.material.map.dispose(); mesh.material.dispose(); mesh.geometry.dispose(); - scene.remove(mesh); + delete mesh.material.map; + delete mesh.material; + delete mesh.geometry; + delete textMeshs[i]; } + textMeshs = []; - for (const mesh of iconMeshs) { + for (const [i, mesh] of iconMeshs.entries()) { + scene.remove(mesh); mesh.material.dispose(); mesh.geometry.dispose(); - scene.remove(mesh); + delete mesh.material; + delete mesh.geometry; + delete iconMeshs[i]; } + iconMeshs = []; } // create a mesh from pixel data @@ -376,8 +395,6 @@ async function createMesh(width, height, segmentsX, segmentsY) { scene.add(mesh); deleteLabels(); - textMeshs = []; - iconMeshs = []; if (options.labels3d) { await createLabels(); From ba10da785a538bb41b08a7b5d2fbb32b7e19b1af Mon Sep 17 00:00:00 2001 From: Rayzeq Date: Fri, 2 Jul 2021 14:59:02 +0200 Subject: [PATCH 16/18] Fix rotating animation + More optimization --- modules/ui/3d.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/modules/ui/3d.js b/modules/ui/3d.js index 898e02f2e..b3dd52481 100644 --- a/modules/ui/3d.js +++ b/modules/ui/3d.js @@ -97,11 +97,9 @@ const setSun = function(x, y, z) { } const setRotation = function(speed) { - cancelAnimationFrame(animationFrame); if (options.isGlobe) options.rotateGlobe = speed; else options.rotateMesh = speed; controls.autoRotateSpeed = speed; controls.autoRotate = Boolean(controls.autoRotateSpeed); - if (controls.autoRotate) animate(); } const toggleSky = function() { @@ -267,8 +265,8 @@ async function createLabels() { const citie_icon_material = new THREE.MeshBasicMaterial({color: cities_icons.attr('fill')}); const town_icon_material = new THREE.MeshBasicMaterial({color: towns_icons.attr('fill')}); - const citie_icon_geometry = new THREE.SphereGeometry(cities_icons.attr("size") * 2, 16, 16); - const town_icon_geometry = new THREE.SphereGeometry(towns_icons.attr("size") * 2, 16, 16); + const citie_icon_geometry = new THREE.SphereGeometry(cities_icons.attr("size") * 2, 8, 8); + const town_icon_geometry = new THREE.SphereGeometry(towns_icons.attr("size") * 2, 8, 8); for (const burg of pack.burgs) { const [x, y, z] = get3dCoords(burg.x, burg.y) From 011c58cf891e8cd4fa2594a643da009bcadc0614 Mon Sep 17 00:00:00 2001 From: Rayzeq Date: Mon, 5 Jul 2021 23:04:00 +0200 Subject: [PATCH 17/18] use Raycaster for positioning the labels --- modules/ui/3d.js | 47 ++++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/modules/ui/3d.js b/modules/ui/3d.js index b3dd52481..32f09f8e3 100644 --- a/modules/ui/3d.js +++ b/modules/ui/3d.js @@ -11,7 +11,7 @@ const options = {scale: 50, lightness: .7, shadow: .5, sun: {x: 100, y: 600, z: // set variables let Renderer, scene, camera, controls, animationFrame, material, texture, geometry, mesh, ambientLight, spotLight, waterPlane, waterMaterial, waterMesh, - objexporter, square_geometry, texture_loader; + objexporter, square_geometry, texture_loader, raycaster; const drawSVG = document.createElementNS("http://www.w3.org/2000/svg", "svg"); document.body.appendChild(drawSVG); let textMeshs = [], iconMeshs = []; @@ -71,17 +71,20 @@ const stop = function() { const setScale = function(scale) { options.scale = scale; - for (const mesh of textMeshs) { - mesh.position.y = getMeshHeight(findGridCell(mesh.base_x, mesh.base_y)) + mesh.base_height; - } - for (const mesh of iconMeshs) { - mesh.position.y = getMeshHeight(findGridCell(mesh.base_x, mesh.base_y)); - } geometry.vertices.forEach((v, i) => v.z = getMeshHeight(i)); geometry.verticesNeedUpdate = true; geometry.computeVertexNormals(); render(); geometry.verticesNeedUpdate = false; + + for (const textMesh of textMeshs) { + raycaster.ray.origin.x = textMesh.position.x; raycaster.ray.origin.z = textMesh.position.z; + textMesh.position.y = raycaster.intersectObject(mesh)[0].point.y + textMesh.base_height; + } + for (const iconMesh of iconMeshs) { + raycaster.ray.origin.x = iconMesh.position.x; raycaster.ray.origin.z = iconMesh.position.z; + iconMesh.position.y = raycaster.intersectObject(mesh)[0].point.y; + } } const setLightness = function(intensity) { @@ -198,7 +201,7 @@ function svg2base64(svg) { return 'data:image/svg+xml;base64,' + btoa(str_xml); } -function svg2mesh(svg, sx=1, sy=1) { +function svg2mesh(svg, sx=1, sy=1, backface=false) { svg.removeAttribute("viewBox"); const bbox = svg.getBBox(); svg.setAttribute("viewBox", [bbox.x, bbox.y, bbox.width, bbox.height].join(" ")); @@ -208,7 +211,7 @@ function svg2mesh(svg, sx=1, sy=1) { const texture = new texture_loader.load(svg2base64(svg)); texture.minFilter = THREE.LinearFilter; // remove `texture has been resized` warning - const material = new THREE.MeshBasicMaterial({map: texture, side: THREE.DoubleSide, depthWrite: false}); + const material = new THREE.MeshBasicMaterial({map: texture, side: backface ? THREE.DoubleSide : THREE.FrontSide, depthWrite: false}); material.transparent = true; const mesh = new THREE.Mesh( @@ -233,7 +236,7 @@ async function createStateText(font, size, color, label) { drawSVG.children[1].setAttribute('font-size', size); drawSVG.children[1].setAttribute('fill', color); - const mesh = svg2mesh(drawSVG, 20, 20); + const mesh = svg2mesh(drawSVG, 20, 20, true); mesh.rotation.set(THREE.Math.degToRad(-90), 0, 0); return mesh; @@ -241,21 +244,25 @@ async function createStateText(font, size, color, label) { async function createBurgText(text, font, size, color, quality=1) { // for quality: lower value mean higher quality if (fontCache[font] == undefined) {fontCache[font] = (await GFontToDataURI(`https://fonts.googleapis.com/css?family=${font}`)).join("\n");} - drawSVG.innerHTML = `${text} -`; + drawSVG.innerHTML = ` +${text}`; return svg2mesh(drawSVG, 7*quality, 7*quality); } function get3dCoords(base_x, base_y) { const x = base_x - graphWidth/2; - const y = getMeshHeight(findGridCell(base_x, base_y)); // work better than getMeshHeight(burg.cell) but I don't know why const z = base_y - graphHeight/2; + + raycaster.ray.origin.x = x; raycaster.ray.origin.z = z; + const y = raycaster.intersectObject(mesh)[0].point.y; return [x, y, z]; } async function createLabels() { square_geometry = new THREE.PlaneGeometry(1, 1); texture_loader = new THREE.TextureLoader(); + raycaster = new THREE.Raycaster(); + raycaster.set(new THREE.Vector3(0, 1000, 0), new THREE.Vector3(0, -1, 0)); // Burg labels const cities = svg.select("#viewbox #labels #burgLabels #cities"); @@ -267,7 +274,7 @@ async function createLabels() { const town_icon_material = new THREE.MeshBasicMaterial({color: towns_icons.attr('fill')}); const citie_icon_geometry = new THREE.SphereGeometry(cities_icons.attr("size") * 2, 8, 8); const town_icon_geometry = new THREE.SphereGeometry(towns_icons.attr("size") * 2, 8, 8); - for (const burg of pack.burgs) { + for (const burg of pack.burgs.slice(1)) { const [x, y, z] = get3dCoords(burg.x, burg.y) if(layerIsOn("toggleLabels")) { @@ -296,9 +303,6 @@ async function createLabels() { } } - text_mesh.base_x = burg.x; - text_mesh.base_y = burg.y; - textMeshs.push(text_mesh); scene.add(text_mesh); } @@ -310,8 +314,6 @@ async function createLabels() { burg.capital ? citie_icon_material : town_icon_material ); icon_mesh.position.set(x, y, z) - icon_mesh.base_x = burg.x - icon_mesh.base_y = burg.y iconMeshs.push(icon_mesh); scene.add(icon_mesh); @@ -326,8 +328,6 @@ async function createLabels() { const pos = pack.states[id].pole const [x, y, z] = get3dCoords(pos[0], pos[1]) text_mesh.position.set(x, y + 25, z); - text_mesh.base_x = pos[0]; - text_mesh.base_y = pos[1]; text_mesh.base_height = 25; textMeshs.push(text_mesh) @@ -339,6 +339,7 @@ function deleteLabels() { if (square_geometry) square_geometry.dispose(); square_geometry = undefined; texture_loader = undefined; + raycaster = undefined; for (const [i, mesh] of textMeshs.entries()) { scene.remove(mesh); @@ -392,8 +393,8 @@ async function createMesh(width, height, segmentsX, segmentsY) { mesh.receiveShadow = true; scene.add(mesh); + render(); // needed for Raycaster to work, but why ? deleteLabels(); - if (options.labels3d) { await createLabels(); } @@ -446,7 +447,7 @@ async function newGlobe(canvas) { updateGlobeTexure(true); // camera - camera = new THREE.PerspectiveCamera(45, canvas.width / canvas.height, 0.1, 1000).translateZ(5); + camera = new THREE.PerspectiveCamera(45, canvas.width / canvas.height, 0.1, 1000).translateZ(5); // controls controls = await OrbitControls(camera, Renderer.domElement); From 28f3481ba05b7be3efa65b9d338cc4f1fbf197df Mon Sep 17 00:00:00 2001 From: Rayzeq Date: Tue, 6 Jul 2021 19:40:51 +0200 Subject: [PATCH 18/18] Use canvas to render burg labels + Some tweaks --- modules/ui/3d.js | 52 ++++++++++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/modules/ui/3d.js b/modules/ui/3d.js index 32f09f8e3..408b9c785 100644 --- a/modules/ui/3d.js +++ b/modules/ui/3d.js @@ -12,6 +12,7 @@ const options = {scale: 50, lightness: .7, shadow: .5, sun: {x: 100, y: 600, z: let Renderer, scene, camera, controls, animationFrame, material, texture, geometry, mesh, ambientLight, spotLight, waterPlane, waterMaterial, waterMesh, objexporter, square_geometry, texture_loader, raycaster; +const drawCtx = document.createElement("canvas").getContext('2d'); const drawSVG = document.createElementNS("http://www.w3.org/2000/svg", "svg"); document.body.appendChild(drawSVG); let textMeshs = [], iconMeshs = []; @@ -201,14 +202,8 @@ function svg2base64(svg) { return 'data:image/svg+xml;base64,' + btoa(str_xml); } -function svg2mesh(svg, sx=1, sy=1, backface=false) { - svg.removeAttribute("viewBox"); - const bbox = svg.getBBox(); - svg.setAttribute("viewBox", [bbox.x, bbox.y, bbox.width, bbox.height].join(" ")); - svg.setAttribute("width", bbox.width); - svg.setAttribute("height", bbox.height); - - const texture = new texture_loader.load(svg2base64(svg)); +function texture2mesh(texture, width=1, height=1, backface=false) { + texture = new texture_loader.load(texture); texture.minFilter = THREE.LinearFilter; // remove `texture has been resized` warning const material = new THREE.MeshBasicMaterial({map: texture, side: backface ? THREE.DoubleSide : THREE.FrontSide, depthWrite: false}); @@ -218,35 +213,47 @@ function svg2mesh(svg, sx=1, sy=1, backface=false) { square_geometry, material ); - mesh.scale.x = bbox.width * (sx / 100); - mesh.scale.y = bbox.height * (sy / 100); + mesh.scale.x = width; + mesh.scale.y = height; mesh.renderOrder = 1; return mesh; } -async function createStateText(font, size, color, label) { +async function createStateText(font, size, color, label, quality=10) { drawSVG.innerHTML = ""; drawSVG.appendChild(label.cloneNode(true)); if (fontCache[font] == undefined) {fontCache[font] = (await GFontToDataURI(`https://fonts.googleapis.com/css?family=${font}`)).join('\n');} drawSVG.children[0].innerHTML = ``; drawSVG.children[0].appendChild(svg.select(label.childNodes[0].href.baseVal).node().cloneNode(true)); // href of path in defs - drawSVG.children[1].setAttribute("transform", `scale(${5} ${5})`) + drawSVG.children[1].setAttribute("transform", `scale(${quality} ${quality})`) drawSVG.children[1].setAttribute('font-family', font); drawSVG.children[1].setAttribute('font-size', size); drawSVG.children[1].setAttribute('fill', color); - const mesh = svg2mesh(drawSVG, 20, 20, true); + drawSVG.removeAttribute("viewBox"); + const bbox = drawSVG.getBBox(); + drawSVG.setAttribute("viewBox", [bbox.x, bbox.y, bbox.width, bbox.height].join(" ")); + drawSVG.setAttribute("width", bbox.width); + drawSVG.setAttribute("height", bbox.height); + + const mesh = texture2mesh(svg2base64(drawSVG), bbox.width / quality, bbox.height / quality, true); mesh.rotation.set(THREE.Math.degToRad(-90), 0, 0); return mesh; } -async function createBurgText(text, font, size, color, quality=1) { // for quality: lower value mean higher quality - if (fontCache[font] == undefined) {fontCache[font] = (await GFontToDataURI(`https://fonts.googleapis.com/css?family=${font}`)).join("\n");} - drawSVG.innerHTML = ` -${text}`; - return svg2mesh(drawSVG, 7*quality, 7*quality); +async function createBurgText(text, font, size, color, quality=30) { + drawCtx.font = `${size * quality}px ${font}`; + drawCtx.canvas.width = drawCtx.measureText(text).width; + drawCtx.canvas.height = size*quality * (1 + 1/4); // adding a margin of 1/4 of the size because text sometime overflow the font size + drawCtx.clearRect(0, 0, drawCtx.canvas.width, drawCtx.canvas.height); + + drawCtx.font = `${size * quality}px ${font}`; + drawCtx.fillStyle = color; + drawCtx.fillText(text, 0, size * quality); + + return texture2mesh(drawCtx.canvas.toDataURL(), drawCtx.canvas.width / quality, drawCtx.canvas.height / quality); } function get3dCoords(base_x, base_y) { @@ -274,18 +281,19 @@ async function createLabels() { const town_icon_material = new THREE.MeshBasicMaterial({color: towns_icons.attr('fill')}); const citie_icon_geometry = new THREE.SphereGeometry(cities_icons.attr("size") * 2, 8, 8); const town_icon_geometry = new THREE.SphereGeometry(towns_icons.attr("size") * 2, 8, 8); - for (const burg of pack.burgs.slice(1)) { + for (let i = 1; i < pack.burgs.length; i++) { + const burg = pack.burgs[i]; const [x, y, z] = get3dCoords(burg.x, burg.y) if(layerIsOn("toggleLabels")) { if (burg.capital) { - var text_mesh = await createBurgText(burg.name, cities.attr('font-family'), cities.attr('font-size'), cities.attr('fill')); // cities.attr('font-size') + var text_mesh = await createBurgText(burg.name, cities.attr('font-family'), cities.attr('font-size'), cities.attr('fill')); } else { - var text_mesh = await createBurgText(burg.name, towns.attr('font-family'), towns.attr('font-size'), towns.attr('fill')); // towns.attr('font-size') + var text_mesh = await createBurgText(burg.name, towns.attr('font-family'), towns.attr('font-size'), towns.attr('fill')); } if (burg.capital) { - text_mesh.position.set(x, y + 15, z); + text_mesh.position.set(x, y + 10, z); text_mesh.base_height = 15; text_mesh.animate = function () { this.rotation.copy(camera.rotation);