mirror of
https://github.com/Azgaar/Fantasy-Map-Generator.git
synced 2025-12-17 09:41:24 +01:00
3d labels: improve perf and look
This commit is contained in:
parent
4cb7b55e03
commit
1d7ba8568f
1 changed files with 116 additions and 78 deletions
194
modules/ui/3d.js
194
modules/ui/3d.js
|
|
@ -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() {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue