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
|
||||
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 drawSVG = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
||||
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
|
||||
|
||||
// initiate 3d scene
|
||||
|
|
@ -72,19 +77,20 @@
|
|||
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;
|
||||
}
|
||||
// for (const textMesh of labels) {
|
||||
// raycaster.ray.origin.x = textMesh.position.x;
|
||||
// raycaster.ray.origin.z = textMesh.position.z;
|
||||
// textMesh.position.y = raycaster.intersectObject(mesh)[0].point.y + textMesh.elevation;
|
||||
// }
|
||||
// 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;
|
||||
// }
|
||||
|
||||
render();
|
||||
};
|
||||
|
||||
const setLightness = function (intensity) {
|
||||
|
|
@ -234,6 +240,7 @@
|
|||
if (fontCache[font] == undefined) {
|
||||
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].appendChild(svg.select(label.childNodes[0].href.baseVal).node().cloneNode(true)); // href of path in defs
|
||||
drawSVG.children[1].setAttribute("transform", `scale(${quality} ${quality})`);
|
||||
|
|
@ -253,7 +260,7 @@
|
|||
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.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
|
||||
|
|
@ -266,9 +273,9 @@
|
|||
return texture2mesh(drawCtx.canvas.toDataURL(), drawCtx.canvas.width / quality, drawCtx.canvas.height / quality);
|
||||
}
|
||||
|
||||
function get3dCoords(base_x, base_y) {
|
||||
const x = base_x - graphWidth / 2;
|
||||
const z = base_y - graphHeight / 2;
|
||||
function get3dCoords(baseX, baseY) {
|
||||
const x = baseX - graphWidth / 2;
|
||||
const z = baseY - graphHeight / 2;
|
||||
|
||||
raycaster.ray.origin.x = x;
|
||||
raycaster.ray.origin.z = z;
|
||||
|
|
@ -282,71 +289,87 @@
|
|||
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");
|
||||
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");
|
||||
// burg labels
|
||||
const cities = burgLabels.select("#cities");
|
||||
const towns = burgLabels.select("#towns");
|
||||
const city_icons = burgIcons.select("#cities");
|
||||
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++) {
|
||||
const burg = pack.burgs[i];
|
||||
const isCity = burg.capital;
|
||||
const [x, y, z] = get3dCoords(burg.x, burg.y);
|
||||
const options = isCity ? cityOptions : townOptions;
|
||||
|
||||
if (layerIsOn("toggleLabels")) {
|
||||
if (burg.capital) {
|
||||
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"));
|
||||
}
|
||||
const burgMesh = await createTextLabel({text: burg.name, ...options});
|
||||
|
||||
if (burg.capital) {
|
||||
text_mesh.position.set(x, y + 10, 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);
|
||||
}
|
||||
};
|
||||
}
|
||||
burgMesh.elevation = options.elevation;
|
||||
burgMesh.position.set(x, y + options.elevation, z);
|
||||
burgMesh.size = options.size;
|
||||
|
||||
textMeshs.push(text_mesh);
|
||||
scene.add(text_mesh);
|
||||
labels.push(burgMesh);
|
||||
scene.add(burgMesh);
|
||||
}
|
||||
|
||||
// Icon
|
||||
// icons
|
||||
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);
|
||||
icon_mesh.position.set(x, y, z);
|
||||
const geometry = isCity ? city_icon_geometry : town_icon_geometry;
|
||||
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);
|
||||
scene.add(icon_mesh);
|
||||
icons.push(iconMesh);
|
||||
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");
|
||||
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 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);
|
||||
text_mesh.base_height = 25;
|
||||
text_mesh.position.set(x, y + 20, z);
|
||||
text_mesh.elevation = 20;
|
||||
|
||||
textMeshs.push(text_mesh);
|
||||
states.push(text_mesh);
|
||||
scene.add(text_mesh);
|
||||
}
|
||||
}
|
||||
|
|
@ -357,27 +380,35 @@
|
|||
texture_loader = undefined;
|
||||
raycaster = undefined;
|
||||
|
||||
for (const [i, mesh] of textMeshs.entries()) {
|
||||
for (const mesh of labels) {
|
||||
scene.remove(mesh);
|
||||
mesh.material.map.dispose();
|
||||
mesh.material.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);
|
||||
mesh.material.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
|
||||
|
|
@ -387,7 +418,7 @@
|
|||
noWater: options.extendedWater
|
||||
};
|
||||
const url = await getMapURL("mesh", mapOptions);
|
||||
window.setTimeout(() => window.URL.revokeObjectURL(url), 3000);
|
||||
window.setTimeout(() => window.URL.revokeObjectURL(url), 5000);
|
||||
|
||||
if (texture) texture.dispose();
|
||||
texture = new THREE.TextureLoader().load(url, render);
|
||||
|
|
@ -410,9 +441,8 @@
|
|||
mesh.receiveShadow = true;
|
||||
scene.add(mesh);
|
||||
|
||||
render();
|
||||
deleteLabels();
|
||||
if (options.labels3d) {
|
||||
render();
|
||||
await createLabels();
|
||||
}
|
||||
}
|
||||
|
|
@ -538,19 +568,27 @@
|
|||
}
|
||||
|
||||
// render 3d scene and camera, do only on controls change
|
||||
const renderThrottled = throttle(doWorkOnRender, 200);
|
||||
function render() {
|
||||
console.log("render");
|
||||
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
|
||||
function animate() {
|
||||
console.log("animate");
|
||||
animationFrame = requestAnimationFrame(animate);
|
||||
controls.update();
|
||||
for (const mesh of textMeshs) {
|
||||
if (mesh.animate) mesh.animate();
|
||||
}
|
||||
}
|
||||
|
||||
function loadTHREE() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue