3d labels: improve perf and look

This commit is contained in:
Azgaar 2021-07-17 21:03:20 +03:00
parent 4cb7b55e03
commit 1d7ba8568f

View file

@ -8,11 +8,16 @@
// set variables // set variables
let Renderer, scene, camera, controls, animationFrame, material, texture, geometry, mesh, ambientLight, spotLight, waterPlane, waterMaterial, waterMesh, square_geometry, texture_loader, raycaster; let Renderer, scene, camera, controls, animationFrame, material, texture, geometry, mesh, ambientLight, spotLight, waterPlane, waterMaterial, waterMesh, square_geometry, texture_loader, raycaster;
const drawCtx = document.createElement("canvas").getContext("2d"); const drawCtx = document.createElement("canvas").getContext("2d");
const drawSVG = document.createElementNS("http://www.w3.org/2000/svg", "svg"); const drawSVG = document.createElementNS("http://www.w3.org/2000/svg", "svg");
document.body.appendChild(drawSVG); document.body.appendChild(drawSVG);
let textMeshs = [],
iconMeshs = []; let icons = [];
let labels = [];
let states = [];
let lines = [];
const fontCache = {Georgia: "", "Times New Roman": "", "Comic Sans MS": "", "Lucida Sans Unicode": "", "Courier New": "", Verdana: "", Arial: "", Impact: ""}; // default are web-safe fonts 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 // initiate 3d scene
@ -72,19 +77,20 @@
geometry.vertices.forEach((v, i) => (v.z = getMeshHeight(i))); geometry.vertices.forEach((v, i) => (v.z = getMeshHeight(i)));
geometry.verticesNeedUpdate = true; geometry.verticesNeedUpdate = true;
geometry.computeVertexNormals(); geometry.computeVertexNormals();
render();
geometry.verticesNeedUpdate = false; geometry.verticesNeedUpdate = false;
for (const textMesh of textMeshs) { // for (const textMesh of labels) {
raycaster.ray.origin.x = textMesh.position.x; // raycaster.ray.origin.x = textMesh.position.x;
raycaster.ray.origin.z = textMesh.position.z; // raycaster.ray.origin.z = textMesh.position.z;
textMesh.position.y = raycaster.intersectObject(mesh)[0].point.y + textMesh.base_height; // textMesh.position.y = raycaster.intersectObject(mesh)[0].point.y + textMesh.elevation;
} // }
for (const iconMesh of iconMeshs) { // for (const iconMesh of iconMeshs) {
raycaster.ray.origin.x = iconMesh.position.x; // raycaster.ray.origin.x = iconMesh.position.x;
raycaster.ray.origin.z = iconMesh.position.z; // raycaster.ray.origin.z = iconMesh.position.z;
iconMesh.position.y = raycaster.intersectObject(mesh)[0].point.y; // iconMesh.position.y = raycaster.intersectObject(mesh)[0].point.y;
} // }
render();
}; };
const setLightness = function (intensity) { const setLightness = function (intensity) {
@ -234,6 +240,7 @@
if (fontCache[font] == undefined) { if (fontCache[font] == undefined) {
fontCache[font] = (await GFontToDataURI(`https://fonts.googleapis.com/css?family=${font}`)).join("\n"); fontCache[font] = (await GFontToDataURI(`https://fonts.googleapis.com/css?family=${font}`)).join("\n");
} }
drawSVG.children[0].innerHTML = `<style type="text/css">${fontCache[font]}</style>`; drawSVG.children[0].innerHTML = `<style type="text/css">${fontCache[font]}</style>`;
drawSVG.children[0].appendChild(svg.select(label.childNodes[0].href.baseVal).node().cloneNode(true)); // href of path in defs 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(${quality} ${quality})`); drawSVG.children[1].setAttribute("transform", `scale(${quality} ${quality})`);
@ -253,7 +260,7 @@
return mesh; return mesh;
} }
async function createBurgText(text, font, size, color, quality = 30) { async function createTextLabel({text, font, size, color, quality}) {
drawCtx.font = `${size * quality}px ${font}`; drawCtx.font = `${size * quality}px ${font}`;
drawCtx.canvas.width = drawCtx.measureText(text).width; 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.canvas.height = size * quality * (1 + 1 / 4); // adding a margin of 1/4 of the size because text sometime overflow the font size
@ -266,9 +273,9 @@
return texture2mesh(drawCtx.canvas.toDataURL(), drawCtx.canvas.width / quality, drawCtx.canvas.height / quality); return texture2mesh(drawCtx.canvas.toDataURL(), drawCtx.canvas.width / quality, drawCtx.canvas.height / quality);
} }
function get3dCoords(base_x, base_y) { function get3dCoords(baseX, baseY) {
const x = base_x - graphWidth / 2; const x = baseX - graphWidth / 2;
const z = base_y - graphHeight / 2; const z = baseY - graphHeight / 2;
raycaster.ray.origin.x = x; raycaster.ray.origin.x = x;
raycaster.ray.origin.z = z; raycaster.ray.origin.z = z;
@ -282,71 +289,87 @@
raycaster = new THREE.Raycaster(); raycaster = new THREE.Raycaster();
raycaster.set(new THREE.Vector3(0, 1000, 0), new THREE.Vector3(0, -1, 0)); raycaster.set(new THREE.Vector3(0, 1000, 0), new THREE.Vector3(0, -1, 0));
// Burg labels // burg labels
const cities = svg.select("#viewbox #labels #burgLabels #cities"); const cities = burgLabels.select("#cities");
const towns = svg.select("#viewbox #labels #burgLabels #towns"); const towns = burgLabels.select("#towns");
const cities_icons = svg.select("#viewbox #icons #burgIcons #cities"); const city_icons = burgIcons.select("#cities");
const towns_icons = svg.select("#viewbox #icons #burgIcons #towns"); const town_icons = burgIcons.select("#towns");
const cityOptions = {
font: cities.attr("font-family"),
size: +cities.attr("data-size"),
color: cities.attr("fill"),
elevation: 10,
quality: 10,
iconSize: +city_icons.attr("size"),
iconColor: "#666",
line: 10 - cities.attr("data-size") / 2
};
const townOptions = {
font: towns.attr("font-family"),
size: +towns.attr("data-size"),
color: towns.attr("fill"),
elevation: 5,
quality: 10,
iconSize: +town_icons.attr("size"),
iconColor: "#666",
line: 5 - towns.attr("data-size") / 2
};
const city_icon_material = new THREE.MeshPhongMaterial({color: cityOptions.iconColor});
const town_icon_material = new THREE.MeshPhongMaterial({color: townOptions.iconColor});
const city_icon_geometry = new THREE.CylinderGeometry(cityOptions.iconSize * 2, cityOptions.iconSize * 2, cityOptions.iconSize, 16, 1);
const town_icon_geometry = new THREE.CylinderGeometry(townOptions.iconSize * 2, townOptions.iconSize * 2, townOptions.iconSize, 16, 1);
const line_material = new THREE.LineBasicMaterial({color: cityOptions.iconColor});
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, 8, 8);
const town_icon_geometry = new THREE.SphereGeometry(towns_icons.attr("size") * 2, 8, 8);
for (let i = 1; i < pack.burgs.length; i++) { for (let i = 1; i < pack.burgs.length; i++) {
const burg = pack.burgs[i]; const burg = pack.burgs[i];
const isCity = burg.capital;
const [x, y, z] = get3dCoords(burg.x, burg.y); const [x, y, z] = get3dCoords(burg.x, burg.y);
const options = isCity ? cityOptions : townOptions;
if (layerIsOn("toggleLabels")) { if (layerIsOn("toggleLabels")) {
if (burg.capital) { const burgMesh = await createTextLabel({text: burg.name, ...options});
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"));
}
if (burg.capital) { burgMesh.elevation = options.elevation;
text_mesh.position.set(x, y + 10, z); burgMesh.position.set(x, y + options.elevation, z);
text_mesh.base_height = 15; burgMesh.size = options.size;
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);
}
};
}
textMeshs.push(text_mesh); labels.push(burgMesh);
scene.add(text_mesh); scene.add(burgMesh);
} }
// Icon // icons
if (layerIsOn("toggleIcons")) { if (layerIsOn("toggleIcons")) {
const icon_mesh = new THREE.Mesh(burg.capital ? citie_icon_geometry : town_icon_geometry, burg.capital ? citie_icon_material : town_icon_material); const geometry = isCity ? city_icon_geometry : town_icon_geometry;
icon_mesh.position.set(x, y, z); const material = isCity ? city_icon_material : town_icon_material;
const iconMesh = new THREE.Mesh(geometry, material);
iconMesh.position.set(x, y, z);
iconMeshs.push(icon_mesh); icons.push(iconMesh);
scene.add(icon_mesh); scene.add(iconMesh);
const points = [new THREE.Vector3(x, y, z), new THREE.Vector3(x, y + options.line, z)];
const line_geometry = new THREE.BufferGeometry().setFromPoints(points);
const line = new THREE.Line(line_geometry, line_material);
lines.push(line);
scene.add(line);
} }
} }
// State labels // state labels
const state_labels = svg.select("#viewbox #labels #states"); const state_labels = svg.select("#viewbox #labels #states");
for (const label of state_labels.node().children) { for (const label of state_labels.node().children) {
const text_mesh = await 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 id = label.id.match(/\d+$/);
const pos = pack.states[id].pole; const pos = pack.states[id].pole;
const [x, y, z] = get3dCoords(pos[0], pos[1]); const [x, y, z] = get3dCoords(pos[0], pos[1]);
text_mesh.position.set(x, y + 25, z); text_mesh.position.set(x, y + 20, z);
text_mesh.base_height = 25; text_mesh.elevation = 20;
textMeshs.push(text_mesh); states.push(text_mesh);
scene.add(text_mesh); scene.add(text_mesh);
} }
} }
@ -357,27 +380,35 @@
texture_loader = undefined; texture_loader = undefined;
raycaster = undefined; raycaster = undefined;
for (const [i, mesh] of textMeshs.entries()) { for (const mesh of labels) {
scene.remove(mesh); scene.remove(mesh);
mesh.material.map.dispose(); mesh.material.map.dispose();
mesh.material.dispose(); mesh.material.dispose();
mesh.geometry.dispose(); mesh.geometry.dispose();
delete mesh.material.map;
delete mesh.material;
delete mesh.geometry;
delete textMeshs[i];
} }
textMeshs = []; labels = [];
for (const [i, mesh] of iconMeshs.entries()) { for (const mesh of states) {
scene.remove(mesh);
mesh.material.map.dispose();
mesh.material.dispose();
mesh.geometry.dispose();
}
states = [];
for (const mesh of icons) {
scene.remove(mesh); scene.remove(mesh);
mesh.material.dispose(); mesh.material.dispose();
mesh.geometry.dispose(); mesh.geometry.dispose();
delete mesh.material;
delete mesh.geometry;
delete iconMeshs[i];
} }
iconMeshs = []; icons = [];
for (const line of lines) {
scene.remove(line);
line.material.dispose();
line.geometry.dispose();
}
lines = [];
} }
// create a mesh from pixel data // create a mesh from pixel data
@ -387,7 +418,7 @@
noWater: options.extendedWater noWater: options.extendedWater
}; };
const url = await getMapURL("mesh", mapOptions); const url = await getMapURL("mesh", mapOptions);
window.setTimeout(() => window.URL.revokeObjectURL(url), 3000); window.setTimeout(() => window.URL.revokeObjectURL(url), 5000);
if (texture) texture.dispose(); if (texture) texture.dispose();
texture = new THREE.TextureLoader().load(url, render); texture = new THREE.TextureLoader().load(url, render);
@ -410,9 +441,8 @@
mesh.receiveShadow = true; mesh.receiveShadow = true;
scene.add(mesh); scene.add(mesh);
render();
deleteLabels();
if (options.labels3d) { if (options.labels3d) {
render();
await createLabels(); await createLabels();
} }
} }
@ -538,19 +568,27 @@
} }
// render 3d scene and camera, do only on controls change // render 3d scene and camera, do only on controls change
const renderThrottled = throttle(doWorkOnRender, 200);
function render() { function render() {
console.log("render"); console.log("render");
Renderer.render(scene, camera); Renderer.render(scene, camera);
renderThrottled();
}
function doWorkOnRender() {
console.log("doWorkOnRender");
for (const [i, label] of labels.entries()) {
const isVisible = label.position.distanceTo(camera.position) < 50 * label.size;
label.visible = lines[i].visible = isVisible;
if (label.visible) label.rotation.copy(camera.rotation);
}
} }
// animate 3d scene and camera // animate 3d scene and camera
function animate() { function animate() {
console.log("animate");
animationFrame = requestAnimationFrame(animate); animationFrame = requestAnimationFrame(animate);
controls.update(); controls.update();
for (const mesh of textMeshs) {
if (mesh.animate) mesh.animate();
}
} }
function loadTHREE() { function loadTHREE() {