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) {
if (!sceneView) return;
const mesh = sceneView.model.getObjectByName(name);
if (mesh) {
mesh.visible = !mesh.visible;
}
sceneView.toggleLayerVisibility(name);
}
function handleChangeTopography() {

View file

@ -84,9 +84,6 @@ export class CustomMapHeightNodeShader extends MapHeightNode {
new MeshPhongMaterial({
map: MapNode.defaultTexture,
color: 0xffffff,
transparent: true,
opacity: 1.0,
depthTest: true,
})
);
@ -109,7 +106,7 @@ export class CustomMapHeightNodeShader extends MapHeightNode {
material
);
this.frustumCulled = false;
this.frustumCulled = true;
}
/**
@ -147,9 +144,10 @@ export class CustomMapHeightNodeShader extends MapHeightNode {
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;
// Apply height displacement
vec3 _transformed = position + _height * normal;
// Vertex position based on height
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 { Extent, buildScene } from "./utils/build-scene";
import { getCenter3D, getMetadata, transform } from "./utils/utils";
@ -12,6 +12,7 @@ import { DragControls } from "three/examples/jsm/Addons.js";
import {
DebugProvider,
HeightDebugProvider,
MapHeightNode,
MapTilerProvider,
MapView,
OpenStreetMapsProvider,
@ -61,6 +62,20 @@ export class SceneView {
if (mesh) {
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() {
@ -166,9 +181,10 @@ async function init(container: HTMLElement, modelId = MODEL_ID) {
// Create the map view for OSM topography
const map = new MapView(MapView.PLANAR, provider, heightProvider);
const customNode = new CustomMapHeightNodeShader(null, map);
const customNode = new CustomMapHeightNodeShader(undefined, map);
map.setRoot(customNode);
map.rotateX(Math.PI / 2);
map.name = "topography";
scene.add(map);

View file

@ -470,7 +470,7 @@ function generateCapMeshes(
// Iterate over the list of geologic meshes
for (const mesh of meshes) {
// 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]> = [];
@ -484,21 +484,9 @@ function generateCapMeshes(
const i2 = indices ? indices[i + 1] * 3 : (i + 1) * 3;
const i3 = indices ? indices[i + 2] * 3 : (i + 2) * 3;
const v1 = new Vector3(
position[i1],
position[i1 + 1],
position[i1 + 2]
);
const v2 = new Vector3(
position[i2],
position[i2 + 1],
position[i2 + 2]
);
const v3 = new Vector3(
position[i3],
position[i3 + 1],
position[i3 + 2]
);
const v1 = new Vector3(position[i1], position[i1 + 1], position[i1 + 2]);
const v2 = new Vector3(position[i2], position[i2 + 1], 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 d1 = plane.distanceToPoint(v1);
@ -521,14 +509,15 @@ function generateCapMeshes(
const polygons: Vector3[][] = buildPolygons(edges);
// Clip cap surfaces with clipping planes
const clippingPlanes = planes.filter(
(p) => !p.normal.equals(plane.normal)
);
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,
@ -540,6 +529,8 @@ function generateCapMeshes(
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);
@ -558,7 +549,6 @@ function generateCapMeshes(
capMeshes.push(...localMeshes);
}
}
return capMeshes;
}

View file

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