Add terrain heights

This commit is contained in:
Fuhrmann 2025-03-13 15:11:08 +01:00
parent 3e6504d6b0
commit d2987d07c4
5 changed files with 108 additions and 98 deletions

View file

@ -118,10 +118,7 @@ export function Form() {
function handleCheckboxChange(name: string) { function handleCheckboxChange(name: string) {
if (!sceneView) return; if (!sceneView) return;
const mesh = sceneView.model.getObjectByName(name); sceneView.toggleLayerVisibility(name);
if (mesh) {
mesh.visible = !mesh.visible;
}
} }
function handleChangeTopography() { function handleChangeTopography() {

View file

@ -84,9 +84,6 @@ export class CustomMapHeightNodeShader extends MapHeightNode {
new MeshPhongMaterial({ new MeshPhongMaterial({
map: MapNode.defaultTexture, map: MapNode.defaultTexture,
color: 0xffffff, color: 0xffffff,
transparent: true,
opacity: 1.0,
depthTest: true,
}) })
); );
@ -109,7 +106,7 @@ export class CustomMapHeightNodeShader extends MapHeightNode {
material material
); );
this.frustumCulled = false; this.frustumCulled = true;
} }
/** /**
@ -147,9 +144,10 @@ export class CustomMapHeightNodeShader extends MapHeightNode {
vec4 _theight = texture(heightMap, vMapUv); vec4 _theight = texture(heightMap, vMapUv);
float _height = ((_theight.r * 255.0 * 65536.0 + _theight.g * 255.0 * 256.0 + _theight.b * 255.0) * 0.1) - 10000.0; float _height = ((_theight.r * 255.0 * 65536.0 + _theight.g * 255.0 * 256.0 + _theight.b * 255.0) * 0.1) - 10000.0;
// Apply height displacement
vec3 _transformed = position + _height * normal; vec3 _transformed = position + _height * normal;
// Vertex position based on height
gl_Position = projectionMatrix * modelViewMatrix * vec4(_transformed, 1.0); gl_Position = projectionMatrix * modelViewMatrix * vec4(_transformed, 1.0);
` `
); );

View file

@ -1,4 +1,4 @@
import { Group, Mesh, MeshStandardMaterial, Scene } from "three"; import { Group, Material, Mesh, MeshStandardMaterial, Scene } from "three";
import { buildMeshes } from "./utils/build-meshes"; import { buildMeshes } from "./utils/build-meshes";
import { Extent, buildScene } from "./utils/build-scene"; import { Extent, buildScene } from "./utils/build-scene";
import { getCenter3D, getMetadata, transform } from "./utils/utils"; import { getCenter3D, getMetadata, transform } from "./utils/utils";
@ -12,6 +12,7 @@ import { DragControls } from "three/examples/jsm/Addons.js";
import { import {
DebugProvider, DebugProvider,
HeightDebugProvider, HeightDebugProvider,
MapHeightNode,
MapTilerProvider, MapTilerProvider,
MapView, MapView,
OpenStreetMapsProvider, OpenStreetMapsProvider,
@ -61,6 +62,20 @@ export class SceneView {
if (mesh) { if (mesh) {
mesh.visible = !mesh.visible; mesh.visible = !mesh.visible;
} }
// Set visibility for any existing cap meshes
for (const key of Object.values(Orientation)) {
const name = `cap-mesh-group-${key}`;
const capMeshGroup = this._scene.getObjectByName(name);
if (capMeshGroup) {
for (const m of capMeshGroup.children) {
if (m.name === layerName && m) {
m.visible = !m.visible;
}
}
}
}
} }
toggleCoordinateGrid() { toggleCoordinateGrid() {
@ -166,9 +181,10 @@ async function init(container: HTMLElement, modelId = MODEL_ID) {
// Create the map view for OSM topography // Create the map view for OSM topography
const map = new MapView(MapView.PLANAR, provider, heightProvider); const map = new MapView(MapView.PLANAR, provider, heightProvider);
const customNode = new CustomMapHeightNodeShader(null, map); const customNode = new CustomMapHeightNodeShader(undefined, map);
map.setRoot(customNode); map.setRoot(customNode);
map.rotateX(Math.PI / 2); map.rotateX(Math.PI / 2);
map.name = "topography"; map.name = "topography";
scene.add(map); scene.add(map);

View file

@ -470,94 +470,84 @@ function generateCapMeshes(
// Iterate over the list of geologic meshes // Iterate over the list of geologic meshes
for (const mesh of meshes) { for (const mesh of meshes) {
// Slice visible meshes only // Slice visible meshes only
if (mesh.visible) {
const position = mesh.geometry.attributes.position.array;
const indices = mesh.geometry.index ? mesh.geometry.index.array : null;
const edges: Array<[Vector3, Vector3]> = [];
for ( const position = mesh.geometry.attributes.position.array;
let i = 0; const indices = mesh.geometry.index ? mesh.geometry.index.array : null;
i < (indices ? indices.length : position.length / 3); const edges: Array<[Vector3, Vector3]> = [];
i += 3
) {
const i1 = indices ? indices[i] * 3 : i * 3;
const i2 = indices ? indices[i + 1] * 3 : (i + 1) * 3;
const i3 = indices ? indices[i + 2] * 3 : (i + 2) * 3;
const v1 = new Vector3( for (
position[i1], let i = 0;
position[i1 + 1], i < (indices ? indices.length : position.length / 3);
position[i1 + 2] i += 3
); ) {
const v2 = new Vector3( const i1 = indices ? indices[i] * 3 : i * 3;
position[i2], const i2 = indices ? indices[i + 1] * 3 : (i + 1) * 3;
position[i2 + 1], const i3 = indices ? indices[i + 2] * 3 : (i + 2) * 3;
position[i2 + 2]
);
const v3 = new Vector3(
position[i3],
position[i3 + 1],
position[i3 + 2]
);
// Check if the triangle is cut by the plane const v1 = new Vector3(position[i1], position[i1 + 1], position[i1 + 2]);
const d1 = plane.distanceToPoint(v1); const v2 = new Vector3(position[i2], position[i2 + 1], position[i2 + 2]);
const d2 = plane.distanceToPoint(v2); const v3 = new Vector3(position[i3], position[i3 + 1], position[i3 + 2]);
const d3 = plane.distanceToPoint(v3);
// Compute intersection points // Check if the triangle is cut by the plane
const intersections = []; const d1 = plane.distanceToPoint(v1);
const d2 = plane.distanceToPoint(v2);
const d3 = plane.distanceToPoint(v3);
if (d1 * d2 < 0) intersections.push(intersectEdge(v1, v2, d1, d2)); // Compute intersection points
if (d2 * d3 < 0) intersections.push(intersectEdge(v2, v3, d2, d3)); const intersections = [];
if (d3 * d1 < 0) intersections.push(intersectEdge(v3, v1, d3, d1));
if (intersections.length === 2) { if (d1 * d2 < 0) intersections.push(intersectEdge(v1, v2, d1, d2));
edges.push([intersections[0], intersections[1]]); if (d2 * d3 < 0) intersections.push(intersectEdge(v2, v3, d2, d3));
} if (d3 * d1 < 0) intersections.push(intersectEdge(v3, v1, d3, d1));
if (intersections.length === 2) {
edges.push([intersections[0], intersections[1]]);
} }
// Intersection surface can be a multipolygon consisting of disconnected polygons
const polygons: Vector3[][] = buildPolygons(edges);
// Clip cap surfaces with clipping planes
const clippingPlanes = planes.filter(
(p) => !p.normal.equals(plane.normal)
);
const offset = orientation === Orientation.Z ? 1 : -1;
const material = new MeshStandardMaterial({
color: (mesh.material as MeshStandardMaterial).color,
side: DoubleSide,
polygonOffset: true,
polygonOffsetFactor: offset,
polygonOffsetUnits: offset,
clippingPlanes,
wireframe: scene.userData.wireframe,
});
const localMeshes = polygons.map((polygon) => {
const geometry = triangulatePolygon(polygon, plane);
const capMesh = new Mesh(geometry, material);
// Offset mesh to avoid flickering
const normal = plane.normal.clone().multiplyScalar(offset);
const positionAttr = capMesh.geometry.attributes.position;
for (let i = 0; i < positionAttr.count; i++) {
const x = positionAttr.getX(i) + normal.x;
const y = positionAttr.getY(i) + normal.y;
const z = positionAttr.getZ(i) + normal.z;
positionAttr.setXYZ(i, x, y, z);
}
positionAttr.needsUpdate = true;
return capMesh;
});
capMeshes.push(...localMeshes);
} }
// Intersection surface can be a multipolygon consisting of disconnected polygons
const polygons: Vector3[][] = buildPolygons(edges);
// Clip cap surfaces with clipping planes
const clippingPlanes = planes.filter((p) => !p.normal.equals(plane.normal));
const offset = orientation === Orientation.Z ? 1 : -1;
const material = new MeshStandardMaterial({
color: (mesh.material as MeshStandardMaterial).color,
side: DoubleSide,
metalness: 0.0,
roughness: 0.75,
flatShading: true,
polygonOffset: true,
polygonOffsetFactor: offset,
polygonOffsetUnits: offset,
clippingPlanes,
wireframe: scene.userData.wireframe,
});
const localMeshes = polygons.map((polygon) => {
const geometry = triangulatePolygon(polygon, plane);
const capMesh = new Mesh(geometry, material);
capMesh.visible = mesh.visible;
capMesh.name = mesh.name;
// Offset mesh to avoid flickering
const normal = plane.normal.clone().multiplyScalar(offset);
const positionAttr = capMesh.geometry.attributes.position;
for (let i = 0; i < positionAttr.count; i++) {
const x = positionAttr.getX(i) + normal.x;
const y = positionAttr.getY(i) + normal.y;
const z = positionAttr.getZ(i) + normal.z;
positionAttr.setXYZ(i, x, y, z);
}
positionAttr.needsUpdate = true;
return capMesh;
});
capMeshes.push(...localMeshes);
} }
return capMeshes; return capMeshes;

View file

@ -7,6 +7,7 @@ import {
Group, Group,
Object3D, Object3D,
Vector3, Vector3,
HemisphereLight,
} from "three"; } from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js"; import { OrbitControls } from "three/addons/controls/OrbitControls.js";
@ -62,7 +63,8 @@ export function buildScene(container: HTMLElement, extent: Extent) {
controls = new OrbitControls(camera, renderer.domElement); controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(center.x, center.y, center.z); // Focus on the center controls.target.set(center.x, center.y, center.z); // Focus on the center
controls.enableDamping = true; // Smooth camera movement controls.enableDamping = true; // Smooth camera movement
controls.maxDistance = maxSize * 5; controls.maxDistance = maxSize * 3;
controls.minDistance = maxSize / 5;
controls.update(); controls.update();
// Scene // Scene
@ -97,17 +99,24 @@ function animate() {
function buildDefaultLights(scene: Scene, extent: Extent) { function buildDefaultLights(scene: Scene, extent: Extent) {
const center = getCenter3D(extent); const center = getCenter3D(extent);
const lightPosition = {
x: center.x,
y: center.y - 200000,
z: extent.zmax + 100000,
};
const lights = []; const lights = [];
// Ambient light // Ambient light
const ambient = new AmbientLight(0xffffff, 1.0); const ambient = new AmbientLight(0xffffff, 2);
lights.push(ambient); lights.push(ambient);
// Directional lights // Directional lights
const directionalLight = new DirectionalLight(0xffffff, 1); const directionalLight = new DirectionalLight(0xffffff, 2);
directionalLight.position.set( directionalLight.position.set(
center.x, lightPosition.x,
center.y - 200000, lightPosition.y,
extent.zmax + 100000 lightPosition.z
); );
// Create a target for the ligth // Create a target for the ligth