From 5f45df7b9f30f28d00057d9f0c1b6651abc46adb Mon Sep 17 00:00:00 2001 From: Adrian Solberg Date: Sat, 20 Sep 2025 15:52:47 +0200 Subject: [PATCH 01/16] feat(#9): add cesium globe --- index.html | 165 ++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 152 insertions(+), 13 deletions(-) diff --git a/index.html b/index.html index c77bfb1..b2a119c 100644 --- a/index.html +++ b/index.html @@ -23,6 +23,11 @@ type="text/css" href="/libs/jstree/themes/mixed/style.css" /> + @@ -39,6 +44,7 @@ +
+ > +
+
From 305ec8b468b492584b8d940134bd980c53aa5e42 Mon Sep 17 00:00:00 2001 From: Adrian Solberg Date: Sat, 20 Sep 2025 17:12:27 +0200 Subject: [PATCH 02/16] refactor(#9): restructure index.html for cleaner project setup --- index.html | 160 +------------------------------------------- src/cameraSync.js | 58 ++++++++++++++++ src/cesiumViewer.js | 32 +++++++++ src/config.js | 16 +++++ src/main.js | 24 ++++++- src/potreeViewer.js | 41 ++++++++++++ 6 files changed, 172 insertions(+), 159 deletions(-) create mode 100644 src/cameraSync.js create mode 100644 src/cesiumViewer.js create mode 100644 src/config.js create mode 100644 src/potreeViewer.js diff --git a/index.html b/index.html index b2a119c..5c5e49a 100644 --- a/index.html +++ b/index.html @@ -69,163 +69,7 @@
- + + diff --git a/src/cameraSync.js b/src/cameraSync.js new file mode 100644 index 0000000..b2b9d9a --- /dev/null +++ b/src/cameraSync.js @@ -0,0 +1,58 @@ +export function syncCameras(potreeViewer, cesiumViewer) { + let camera = potreeViewer.scene.getActiveCamera() + + let pPos = new THREE.Vector3(0, 0, 0).applyMatrix4( + camera.matrixWorld + ) + let pUp = new THREE.Vector3(0, 600, 0).applyMatrix4( + camera.matrixWorld + ) + let pTarget = potreeViewer.scene.view.getPivot() + + let toCes = (pos) => { + let xy = [pos.x, pos.y] + let height = pos.z + let deg = toMap.forward(xy) + let cPos = Cesium.Cartesian3.fromDegrees(...deg, height) + + return cPos + } + + let cPos = toCes(pPos) + let cUpTarget = toCes(pUp) + let cTarget = toCes(pTarget) + + let cDir = Cesium.Cartesian3.subtract( + cTarget, + cPos, + new Cesium.Cartesian3() + ) + let cUp = Cesium.Cartesian3.subtract( + cUpTarget, + cPos, + new Cesium.Cartesian3() + ) + + cDir = Cesium.Cartesian3.normalize(cDir, new Cesium.Cartesian3()) + cUp = Cesium.Cartesian3.normalize(cUp, new Cesium.Cartesian3()) + + cesiumViewer.camera.setView({ + destination: cPos, + orientation: { + direction: cDir, + up: cUp + } + }) + + let aspect = potreeViewer.scene.getActiveCamera().aspect + if (aspect < 1) { + let fovy = + Math.PI * (potreeViewer.scene.getActiveCamera().fov / 180) + cesiumViewer.camera.frustum.fov = fovy + } else { + let fovy = + Math.PI * (potreeViewer.scene.getActiveCamera().fov / 180) + let fovx = Math.atan(Math.tan(0.5 * fovy) * aspect) * 2 + cesiumViewer.camera.frustum.fov = fovx + } +} \ No newline at end of file diff --git a/src/cesiumViewer.js b/src/cesiumViewer.js new file mode 100644 index 0000000..2b49481 --- /dev/null +++ b/src/cesiumViewer.js @@ -0,0 +1,32 @@ +export function createCesiumViewer(containerId) { + const viewer = new Cesium.Viewer(containerId, { + useDefaultRenderLoop: false, + animation: false, + baseLayerPicker: false, + fullscreenButton: false, + geocoder: false, + homeButton: false, + infoBox: false, + sceneModePicker: false, + selectionIndicator: false, + timeline: false, + navigationHelpButton: false, + imageryProvider: Cesium.createOpenStreetMapImageryProvider({ + url: 'https://a.tile.openstreetmap.org/' + }), + terrainShadows: Cesium.ShadowMode.DISABLED + }) + return viewer; +} + +export function setCesiumView(viewer, pos) { + const cp = new Cesium.Cartesian3(pos.x, pos.y, pos.z); + viewer.camera.setView({ + destination: cp, + orientation: { + heading: pos.heading, + pitch: pos.pitch, + roll: pos.roll + } + }); +} \ No newline at end of file diff --git a/src/config.js b/src/config.js new file mode 100644 index 0000000..ca47cff --- /dev/null +++ b/src/config.js @@ -0,0 +1,16 @@ +export const POTREE_POINTCLOUD_URL = "../pointclouds/data_converted/metadata.json"; + +export const INITIAL_CESIUM_POS = { + x: 4303414.154026048, + y: 552161.235598733, + z: 4660771.704035539, + heading: 10, + pitch: -Cesium.Math.PI_OVER_TWO * 0.5, + roll: 0.0 +}; + +export const POTREE_SETTINGS = { + edl: true, + fov: 60, + pointBudget: 1_000_000 +}; \ No newline at end of file diff --git a/src/main.js b/src/main.js index 79eee5f..6c918a2 100644 --- a/src/main.js +++ b/src/main.js @@ -1 +1,23 @@ -/* Empty for now, add logic later */ +import { POTREE_POINTCLOUD_URL, INITIAL_CESIUM_POS, POTREE_SETTINGS } from './config.js'; +import { createCesiumViewer, setCesiumView } from './cesiumViewer.js'; +import { createPotreeViewer } from './potreeViewer.js'; +import { syncCameras } from './cameraSync.js'; + +async function init() { + window.cesiumViewer = createCesiumViewer('cesiumContainer'); + setCesiumView(window.cesiumViewer, INITIAL_CESIUM_POS); + + window.potreeViewer = await createPotreeViewer('potree_render_area', POTREE_POINTCLOUD_URL, POTREE_SETTINGS); + + function loop(timestamp) { + requestAnimationFrame(loop); + potreeViewer.update(potreeViewer.clock.getDelta(), timestamp); + potreeViewer.render(); + if(window.toMap) syncCameras(potreeViewer, cesiumViewer); + cesiumViewer.render(); + } + + requestAnimationFrame(loop); +} + +init(); \ No newline at end of file diff --git a/src/potreeViewer.js b/src/potreeViewer.js new file mode 100644 index 0000000..751e8df --- /dev/null +++ b/src/potreeViewer.js @@ -0,0 +1,41 @@ +export async function createPotreeViewer(containerId, pointcloudUrl, settings) { + const viewer = new Potree.Viewer(document.getElementById(containerId), { + useDefaultRenderLoop: false + }); + + if(settings.edl) viewer.setEDLEnabled(true); + if(settings.fov) viewer.setFOV(settings.fov); + if(settings.pointBudget) viewer.setPointBudget(settings.pointBudget); + + viewer.loadSettingsFromURL(); + viewer.setBackground(null); + viewer.setDescription('Molloy Explorer') + + viewer.loadGUI(() => { + viewer.setLanguage('en') + $('#menu_appearance').next().show() + $('#menu_tools').next().show() + $('#menu_scene').next().show() + $('#menu_filters').next().show() + viewer.toggleSidebar() + }) + + const e = await Potree.loadPointCloud(pointcloudUrl); + const pc = e.pointcloud; + viewer.scene.addPointCloud(pc); + + pc.material.pointSizeType = Potree.PointSizeType.ADAPTIVE; + pc.material.shape = Potree.PointShape.CIRCLE + pc.material.activeAttributeName = "elevation"; + pc.material.gradient = Potree.Gradients["RAINBOW"]; + + e.pointcloud.projection = "+proj=utm +zone=32 +ellps=GRS80 +datum=ETRS89 +units=m +no_defs"; + const pointcloudProjection = e.pointcloud.projection; + const mapProjection = proj4.defs("WGS84"); + window.toMap = proj4(pointcloudProjection, mapProjection); + window.toScene = proj4(mapProjection, pointcloudProjection); + + viewer.scene.view.setView([401603.85, 8860821.54, 1000000], [401603.85, 8860821.54, -2650.47]); + + return viewer; +} \ No newline at end of file From bd4c0cc3c5406cf237baa9b0b452cc0d0d764b8d Mon Sep 17 00:00:00 2001 From: Adrian Solberg Date: Sat, 20 Sep 2025 17:34:40 +0200 Subject: [PATCH 03/16] style(#9): clean up and format code --- src/cameraSync.js | 98 +++++++++++++++++++-------------------------- src/cesiumViewer.js | 56 +++++++++++++------------- src/config.js | 25 ++++++------ src/main.js | 40 ++++++++++-------- src/potreeViewer.js | 84 ++++++++++++++++++++------------------ 5 files changed, 151 insertions(+), 152 deletions(-) diff --git a/src/cameraSync.js b/src/cameraSync.js index b2b9d9a..c2ca8b6 100644 --- a/src/cameraSync.js +++ b/src/cameraSync.js @@ -1,58 +1,44 @@ export function syncCameras(potreeViewer, cesiumViewer) { - let camera = potreeViewer.scene.getActiveCamera() - - let pPos = new THREE.Vector3(0, 0, 0).applyMatrix4( - camera.matrixWorld - ) - let pUp = new THREE.Vector3(0, 600, 0).applyMatrix4( - camera.matrixWorld - ) - let pTarget = potreeViewer.scene.view.getPivot() - - let toCes = (pos) => { - let xy = [pos.x, pos.y] - let height = pos.z - let deg = toMap.forward(xy) - let cPos = Cesium.Cartesian3.fromDegrees(...deg, height) - - return cPos - } - - let cPos = toCes(pPos) - let cUpTarget = toCes(pUp) - let cTarget = toCes(pTarget) - - let cDir = Cesium.Cartesian3.subtract( - cTarget, - cPos, - new Cesium.Cartesian3() - ) - let cUp = Cesium.Cartesian3.subtract( - cUpTarget, - cPos, - new Cesium.Cartesian3() - ) - - cDir = Cesium.Cartesian3.normalize(cDir, new Cesium.Cartesian3()) - cUp = Cesium.Cartesian3.normalize(cUp, new Cesium.Cartesian3()) - - cesiumViewer.camera.setView({ - destination: cPos, - orientation: { - direction: cDir, - up: cUp - } - }) - - let aspect = potreeViewer.scene.getActiveCamera().aspect - if (aspect < 1) { - let fovy = - Math.PI * (potreeViewer.scene.getActiveCamera().fov / 180) - cesiumViewer.camera.frustum.fov = fovy - } else { - let fovy = - Math.PI * (potreeViewer.scene.getActiveCamera().fov / 180) - let fovx = Math.atan(Math.tan(0.5 * fovy) * aspect) * 2 - cesiumViewer.camera.frustum.fov = fovx + const camera = potreeViewer.scene.getActiveCamera() + + const pPos = new THREE.Vector3(0, 0, 0).applyMatrix4(camera.matrixWorld) + const pUp = new THREE.Vector3(0, 600, 0).applyMatrix4(camera.matrixWorld) + const pTarget = potreeViewer.scene.view.getPivot() + + const toCes = (pos) => { + const xy = [pos.x, pos.y] + const height = pos.z + const deg = toMap.forward(xy) + return Cesium.Cartesian3.fromDegrees(...deg, height) + } + + const cPos = toCes(pPos) + const cUpTarget = toCes(pUp) + const cTarget = toCes(pTarget) + + const cDir = Cesium.Cartesian3.normalize( + Cesium.Cartesian3.subtract(cTarget, cPos, new Cesium.Cartesian3()), + new Cesium.Cartesian3() + ) + const cUp = Cesium.Cartesian3.normalize( + Cesium.Cartesian3.subtract(cUpTarget, cPos, new Cesium.Cartesian3()), + new Cesium.Cartesian3() + ) + + cesiumViewer.camera.setView({ + destination: cPos, + orientation: { + direction: cDir, + up: cUp } -} \ No newline at end of file + }) + + const aspect = camera.aspect + const fovy = Math.PI * (camera.fov / 180) + if (aspect < 1) { + cesiumViewer.camera.frustum.fov = fovy + } else { + const fovx = Math.atan(Math.tan(0.5 * fovy) * aspect) * 2 + cesiumViewer.camera.frustum.fov = fovx + } +} diff --git a/src/cesiumViewer.js b/src/cesiumViewer.js index 2b49481..3fdbbbd 100644 --- a/src/cesiumViewer.js +++ b/src/cesiumViewer.js @@ -1,32 +1,32 @@ export function createCesiumViewer(containerId) { - const viewer = new Cesium.Viewer(containerId, { - useDefaultRenderLoop: false, - animation: false, - baseLayerPicker: false, - fullscreenButton: false, - geocoder: false, - homeButton: false, - infoBox: false, - sceneModePicker: false, - selectionIndicator: false, - timeline: false, - navigationHelpButton: false, - imageryProvider: Cesium.createOpenStreetMapImageryProvider({ - url: 'https://a.tile.openstreetmap.org/' - }), - terrainShadows: Cesium.ShadowMode.DISABLED - }) - return viewer; + const viewer = new Cesium.Viewer(containerId, { + useDefaultRenderLoop: false, + animation: false, + baseLayerPicker: false, + fullscreenButton: false, + geocoder: false, + homeButton: false, + infoBox: false, + sceneModePicker: false, + selectionIndicator: false, + timeline: false, + navigationHelpButton: false, + imageryProvider: Cesium.createOpenStreetMapImageryProvider({ + url: 'https://a.tile.openstreetmap.org/' + }), + terrainShadows: Cesium.ShadowMode.DISABLED + }) + return viewer } export function setCesiumView(viewer, pos) { - const cp = new Cesium.Cartesian3(pos.x, pos.y, pos.z); - viewer.camera.setView({ - destination: cp, - orientation: { - heading: pos.heading, - pitch: pos.pitch, - roll: pos.roll - } - }); -} \ No newline at end of file + const cp = new Cesium.Cartesian3(pos.x, pos.y, pos.z) + viewer.camera.setView({ + destination: cp, + orientation: { + heading: pos.heading, + pitch: pos.pitch, + roll: pos.roll + } + }) +} diff --git a/src/config.js b/src/config.js index ca47cff..8245b56 100644 --- a/src/config.js +++ b/src/config.js @@ -1,16 +1,17 @@ -export const POTREE_POINTCLOUD_URL = "../pointclouds/data_converted/metadata.json"; +export const POTREE_POINTCLOUD_URL = + '../pointclouds/data_converted/metadata.json' export const INITIAL_CESIUM_POS = { - x: 4303414.154026048, - y: 552161.235598733, - z: 4660771.704035539, - heading: 10, - pitch: -Cesium.Math.PI_OVER_TWO * 0.5, - roll: 0.0 -}; + x: 4303414.154026048, + y: 552161.235598733, + z: 4660771.704035539, + heading: 10, + pitch: -Cesium.Math.PI_PVER_FOUR, + roll: 0.0 +} export const POTREE_SETTINGS = { - edl: true, - fov: 60, - pointBudget: 1_000_000 -}; \ No newline at end of file + edl: true, + fov: 60, + pointBudget: 1_000_000 +} diff --git a/src/main.js b/src/main.js index 6c918a2..8ab5b84 100644 --- a/src/main.js +++ b/src/main.js @@ -1,23 +1,31 @@ -import { POTREE_POINTCLOUD_URL, INITIAL_CESIUM_POS, POTREE_SETTINGS } from './config.js'; -import { createCesiumViewer, setCesiumView } from './cesiumViewer.js'; -import { createPotreeViewer } from './potreeViewer.js'; -import { syncCameras } from './cameraSync.js'; +import { + POTREE_POINTCLOUD_URL, + INITIAL_CESIUM_POS, + POTREE_SETTINGS +} from './config.js' +import { createCesiumViewer, setCesiumView } from './cesiumViewer.js' +import { createPotreeViewer } from './potreeViewer.js' +import { syncCameras } from './cameraSync.js' async function init() { - window.cesiumViewer = createCesiumViewer('cesiumContainer'); - setCesiumView(window.cesiumViewer, INITIAL_CESIUM_POS); + window.cesiumViewer = createCesiumViewer('cesiumContainer') + setCesiumView(window.cesiumViewer, INITIAL_CESIUM_POS) - window.potreeViewer = await createPotreeViewer('potree_render_area', POTREE_POINTCLOUD_URL, POTREE_SETTINGS); + window.potreeViewer = await createPotreeViewer( + 'potree_render_area', + POTREE_POINTCLOUD_URL, + POTREE_SETTINGS + ) - function loop(timestamp) { - requestAnimationFrame(loop); - potreeViewer.update(potreeViewer.clock.getDelta(), timestamp); - potreeViewer.render(); - if(window.toMap) syncCameras(potreeViewer, cesiumViewer); - cesiumViewer.render(); - } + function loop(timestamp) { + requestAnimationFrame(loop) + potreeViewer.update(potreeViewer.clock.getDelta(), timestamp) + potreeViewer.render() + if (window.toMap) syncCameras(potreeViewer, cesiumViewer) + cesiumViewer.render() + } - requestAnimationFrame(loop); + requestAnimationFrame(loop) } -init(); \ No newline at end of file +init() diff --git a/src/potreeViewer.js b/src/potreeViewer.js index 751e8df..2e7272e 100644 --- a/src/potreeViewer.js +++ b/src/potreeViewer.js @@ -1,41 +1,45 @@ export async function createPotreeViewer(containerId, pointcloudUrl, settings) { - const viewer = new Potree.Viewer(document.getElementById(containerId), { - useDefaultRenderLoop: false - }); - - if(settings.edl) viewer.setEDLEnabled(true); - if(settings.fov) viewer.setFOV(settings.fov); - if(settings.pointBudget) viewer.setPointBudget(settings.pointBudget); - - viewer.loadSettingsFromURL(); - viewer.setBackground(null); - viewer.setDescription('Molloy Explorer') - - viewer.loadGUI(() => { - viewer.setLanguage('en') - $('#menu_appearance').next().show() - $('#menu_tools').next().show() - $('#menu_scene').next().show() - $('#menu_filters').next().show() - viewer.toggleSidebar() - }) - - const e = await Potree.loadPointCloud(pointcloudUrl); - const pc = e.pointcloud; - viewer.scene.addPointCloud(pc); - - pc.material.pointSizeType = Potree.PointSizeType.ADAPTIVE; - pc.material.shape = Potree.PointShape.CIRCLE - pc.material.activeAttributeName = "elevation"; - pc.material.gradient = Potree.Gradients["RAINBOW"]; - - e.pointcloud.projection = "+proj=utm +zone=32 +ellps=GRS80 +datum=ETRS89 +units=m +no_defs"; - const pointcloudProjection = e.pointcloud.projection; - const mapProjection = proj4.defs("WGS84"); - window.toMap = proj4(pointcloudProjection, mapProjection); - window.toScene = proj4(mapProjection, pointcloudProjection); - - viewer.scene.view.setView([401603.85, 8860821.54, 1000000], [401603.85, 8860821.54, -2650.47]); - - return viewer; -} \ No newline at end of file + const viewer = new Potree.Viewer(document.getElementById(containerId), { + useDefaultRenderLoop: false + }) + + if (settings.edl) viewer.setEDLEnabled(true) + if (settings.fov) viewer.setFOV(settings.fov) + if (settings.pointBudget) viewer.setPointBudget(settings.pointBudget) + + viewer.loadSettingsFromURL() + viewer.setBackground(null) + viewer.setDescription('Molloy Explorer') + + viewer.loadGUI(() => { + viewer.setLanguage('en') + $('#menu_appearance').next().show() + $('#menu_tools').next().show() + $('#menu_scene').next().show() + $('#menu_filters').next().show() + viewer.toggleSidebar() + }) + + const e = await Potree.loadPointCloud(pointcloudUrl) + const pc = e.pointcloud + viewer.scene.addPointCloud(pc) + + pc.material.pointSizeType = Potree.PointSizeType.ADAPTIVE + pc.material.shape = Potree.PointShape.CIRCLE + pc.material.activeAttributeName = 'elevation' + pc.material.gradient = Potree.Gradients['RAINBOW'] + + e.pointcloud.projection = + '+proj=utm +zone=32 +ellps=GRS80 +datum=ETRS89 +units=m +no_defs' + const pointcloudProjection = e.pointcloud.projection + const mapProjection = proj4.defs('WGS84') + window.toMap = proj4(pointcloudProjection, mapProjection) + window.toScene = proj4(mapProjection, pointcloudProjection) + + viewer.scene.view.setView( + [401603.85, 8860821.54, 1000000], + [401603.85, 8860821.54, -2650.47] + ) + + return viewer +} From 251b33390544419a128a4a35a5b000210a250ee0 Mon Sep 17 00:00:00 2001 From: Adrian Solberg Date: Thu, 25 Sep 2025 19:31:03 +0200 Subject: [PATCH 04/16] feat(#9): limit zoom out --- src/potreeViewer.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/potreeViewer.js b/src/potreeViewer.js index 2e7272e..f27edc7 100644 --- a/src/potreeViewer.js +++ b/src/potreeViewer.js @@ -3,6 +3,11 @@ export async function createPotreeViewer(containerId, pointcloudUrl, settings) { useDefaultRenderLoop: false }) + // Remove original scroll listener and add new one + const oc = viewer.orbitControls; + oc.removeEventListener('mousewheel', oc._listeners?.mousewheel?.[0]); + oc.addEventListener('mousewheel', clampScrollRadius); + if (settings.edl) viewer.setEDLEnabled(true) if (settings.fov) viewer.setFOV(settings.fov) if (settings.pointBudget) viewer.setPointBudget(settings.pointBudget) @@ -43,3 +48,14 @@ export async function createPotreeViewer(containerId, pointcloudUrl, settings) { return viewer } + +function clampScrollRadius(e) { + let resolvedRadius = this.scene.view.radius + this.radiusDelta; + let newRadius = resolvedRadius - e.delta * resolvedRadius * 0.1; + + const maxRadius = 10000000; + if (newRadius > maxRadius) newRadius = maxRadius; + + this.radiusDelta = newRadius - this.scene.view.radius; + this.stopTweens(); +} From fefe11bc39c0b0e6e5768319caa697dd24545bf9 Mon Sep 17 00:00:00 2001 From: Adrian Solberg Date: Fri, 26 Sep 2025 14:00:04 +0200 Subject: [PATCH 05/16] fix(#9): hide globe when camera is below surface --- src/cameraSync.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/cameraSync.js b/src/cameraSync.js index c2ca8b6..39ca0fc 100644 --- a/src/cameraSync.js +++ b/src/cameraSync.js @@ -23,7 +23,12 @@ export function syncCameras(potreeViewer, cesiumViewer) { const cUp = Cesium.Cartesian3.normalize( Cesium.Cartesian3.subtract(cUpTarget, cPos, new Cesium.Cartesian3()), new Cesium.Cartesian3() - ) + ) + + // Hide globe when camera is below surface + const camHeight = Cesium.Cartographic.fromCartesian(cPos).height + cesiumViewer.scene.globe.show = camHeight >= 0; + cesiumViewer.scene.skyAtmosphere.show = camHeight >= 0; cesiumViewer.camera.setView({ destination: cPos, @@ -41,4 +46,4 @@ export function syncCameras(potreeViewer, cesiumViewer) { const fovx = Math.atan(Math.tan(0.5 * fovy) * aspect) * 2 cesiumViewer.camera.frustum.fov = fovx } -} +} \ No newline at end of file From 1b3a9d882bb1aed1b20481d3cad15bab481ca761 Mon Sep 17 00:00:00 2001 From: Adrian Solberg Date: Sat, 27 Sep 2025 15:08:49 +0200 Subject: [PATCH 06/16] fix(#9): hide globe when pivot is blockd by curvature --- src/cameraSync.js | 42 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/src/cameraSync.js b/src/cameraSync.js index 39ca0fc..046d5dd 100644 --- a/src/cameraSync.js +++ b/src/cameraSync.js @@ -25,10 +25,10 @@ export function syncCameras(potreeViewer, cesiumViewer) { new Cesium.Cartesian3() ) - // Hide globe when camera is below surface - const camHeight = Cesium.Cartographic.fromCartesian(cPos).height - cesiumViewer.scene.globe.show = camHeight >= 0; - cesiumViewer.scene.skyAtmosphere.show = camHeight >= 0; + // Hide globe when the camera is below the surface or blocked by the curvature of the Earth + const showGlobe = shouldShowGlobe(cPos, cTarget); + cesiumViewer.scene.globe.show = showGlobe; + cesiumViewer.scene.skyAtmosphere.show = showGlobe; cesiumViewer.camera.setView({ destination: cPos, @@ -46,4 +46,38 @@ export function syncCameras(potreeViewer, cesiumViewer) { const fovx = Math.atan(Math.tan(0.5 * fovy) * aspect) * 2 cesiumViewer.camera.frustum.fov = fovx } +} + +/** + * Determines whether the globe should be visible based on the camera position. + * + * Returns false if the camera is below the globe surface or if the pivot + * point would be blocked by the curvature of the Earth. This is handled + * in a unified way by projecting the pivot to the globe surface and + * comparing the camera and pivot positions along the axis from the Earth's + * center through the pivot. + * + * @param cameraPos - The camera position in Cesium Cartesian3 coordinates + * @param pivot - The pivot point in the point cloud (Cartesian3) + * @returns true if the globe should be visible, false if it should be hidden + */ +function shouldShowGlobe(cameraPos, pivot) { + const ellipsoid = Cesium.Ellipsoid.WGS84; + const earthCenter = Cesium.Cartesian3.ZERO; + + // Get point on globe surface directly above the pivot + const carto = Cesium.Cartographic.fromCartesian(pivot); + const pivotSurface = Cesium.Cartesian3.fromRadians(carto.longitude, carto.latitude, 0, ellipsoid); + + // Axis vector from Earth center through pivot + const axis = Cesium.Cartesian3.subtract(pivotSurface, earthCenter, new Cesium.Cartesian3()); + Cesium.Cartesian3.normalize(axis, axis); + + // Project camera and pivot onto this axis + const camProj = Cesium.Cartesian3.dot(cameraPos, axis); + const pivotProj = Cesium.Cartesian3.dot(pivotSurface, axis); + + // If camera is "above" pivot on this axis, the globe should be visible + // If camera is "below" pivot on this axis, the globe should be not be visible + return camProj >= pivotProj; } \ No newline at end of file From 5767ca1078fbdb27533069f762c6b6e3bfbe5fa3 Mon Sep 17 00:00:00 2001 From: Adrian Solberg Date: Sat, 27 Sep 2025 15:11:28 +0200 Subject: [PATCH 07/16] style(#9): prettier formatting --- src/cameraSync.js | 47 +++++++++++++++++++++++++++------------------ src/potreeViewer.js | 18 ++++++++--------- 2 files changed, 37 insertions(+), 28 deletions(-) diff --git a/src/cameraSync.js b/src/cameraSync.js index 046d5dd..173d9a0 100644 --- a/src/cameraSync.js +++ b/src/cameraSync.js @@ -23,12 +23,12 @@ export function syncCameras(potreeViewer, cesiumViewer) { const cUp = Cesium.Cartesian3.normalize( Cesium.Cartesian3.subtract(cUpTarget, cPos, new Cesium.Cartesian3()), new Cesium.Cartesian3() - ) + ) // Hide globe when the camera is below the surface or blocked by the curvature of the Earth - const showGlobe = shouldShowGlobe(cPos, cTarget); - cesiumViewer.scene.globe.show = showGlobe; - cesiumViewer.scene.skyAtmosphere.show = showGlobe; + const showGlobe = shouldShowGlobe(cPos, cTarget) + cesiumViewer.scene.globe.show = showGlobe + cesiumViewer.scene.skyAtmosphere.show = showGlobe cesiumViewer.camera.setView({ destination: cPos, @@ -52,32 +52,41 @@ export function syncCameras(potreeViewer, cesiumViewer) { * Determines whether the globe should be visible based on the camera position. * * Returns false if the camera is below the globe surface or if the pivot - * point would be blocked by the curvature of the Earth. This is handled - * in a unified way by projecting the pivot to the globe surface and - * comparing the camera and pivot positions along the axis from the Earth's + * point would be blocked by the curvature of the Earth. This is handled + * in a unified way by projecting the pivot to the globe surface and + * comparing the camera and pivot positions along the axis from the Earth's * center through the pivot. - * + * * @param cameraPos - The camera position in Cesium Cartesian3 coordinates * @param pivot - The pivot point in the point cloud (Cartesian3) * @returns true if the globe should be visible, false if it should be hidden */ function shouldShowGlobe(cameraPos, pivot) { - const ellipsoid = Cesium.Ellipsoid.WGS84; - const earthCenter = Cesium.Cartesian3.ZERO; - + const ellipsoid = Cesium.Ellipsoid.WGS84 + const earthCenter = Cesium.Cartesian3.ZERO + // Get point on globe surface directly above the pivot - const carto = Cesium.Cartographic.fromCartesian(pivot); - const pivotSurface = Cesium.Cartesian3.fromRadians(carto.longitude, carto.latitude, 0, ellipsoid); + const carto = Cesium.Cartographic.fromCartesian(pivot) + const pivotSurface = Cesium.Cartesian3.fromRadians( + carto.longitude, + carto.latitude, + 0, + ellipsoid + ) // Axis vector from Earth center through pivot - const axis = Cesium.Cartesian3.subtract(pivotSurface, earthCenter, new Cesium.Cartesian3()); - Cesium.Cartesian3.normalize(axis, axis); + const axis = Cesium.Cartesian3.subtract( + pivotSurface, + earthCenter, + new Cesium.Cartesian3() + ) + Cesium.Cartesian3.normalize(axis, axis) // Project camera and pivot onto this axis - const camProj = Cesium.Cartesian3.dot(cameraPos, axis); - const pivotProj = Cesium.Cartesian3.dot(pivotSurface, axis); + const camProj = Cesium.Cartesian3.dot(cameraPos, axis) + const pivotProj = Cesium.Cartesian3.dot(pivotSurface, axis) // If camera is "above" pivot on this axis, the globe should be visible // If camera is "below" pivot on this axis, the globe should be not be visible - return camProj >= pivotProj; -} \ No newline at end of file + return camProj >= pivotProj +} diff --git a/src/potreeViewer.js b/src/potreeViewer.js index f27edc7..58f1ab9 100644 --- a/src/potreeViewer.js +++ b/src/potreeViewer.js @@ -4,9 +4,9 @@ export async function createPotreeViewer(containerId, pointcloudUrl, settings) { }) // Remove original scroll listener and add new one - const oc = viewer.orbitControls; - oc.removeEventListener('mousewheel', oc._listeners?.mousewheel?.[0]); - oc.addEventListener('mousewheel', clampScrollRadius); + const oc = viewer.orbitControls + oc.removeEventListener('mousewheel', oc._listeners?.mousewheel?.[0]) + oc.addEventListener('mousewheel', clampScrollRadius) if (settings.edl) viewer.setEDLEnabled(true) if (settings.fov) viewer.setFOV(settings.fov) @@ -50,12 +50,12 @@ export async function createPotreeViewer(containerId, pointcloudUrl, settings) { } function clampScrollRadius(e) { - let resolvedRadius = this.scene.view.radius + this.radiusDelta; - let newRadius = resolvedRadius - e.delta * resolvedRadius * 0.1; + let resolvedRadius = this.scene.view.radius + this.radiusDelta + let newRadius = resolvedRadius - e.delta * resolvedRadius * 0.1 - const maxRadius = 10000000; - if (newRadius > maxRadius) newRadius = maxRadius; + const maxRadius = 10000000 + if (newRadius > maxRadius) newRadius = maxRadius - this.radiusDelta = newRadius - this.scene.view.radius; - this.stopTweens(); + this.radiusDelta = newRadius - this.scene.view.radius + this.stopTweens() } From 60aedc078bb64ae6108ff086ff3e07e915d54aef Mon Sep 17 00:00:00 2001 From: Adrian Solberg Date: Mon, 29 Sep 2025 10:27:31 +0200 Subject: [PATCH 08/16] refactor(#9): change background name from None to Globe --- src/potreeViewer.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/potreeViewer.js b/src/potreeViewer.js index 58f1ab9..c098d30 100644 --- a/src/potreeViewer.js +++ b/src/potreeViewer.js @@ -13,7 +13,6 @@ export async function createPotreeViewer(containerId, pointcloudUrl, settings) { if (settings.pointBudget) viewer.setPointBudget(settings.pointBudget) viewer.loadSettingsFromURL() - viewer.setBackground(null) viewer.setDescription('Molloy Explorer') viewer.loadGUI(() => { @@ -29,6 +28,14 @@ export async function createPotreeViewer(containerId, pointcloudUrl, settings) { const pc = e.pointcloud viewer.scene.addPointCloud(pc) + // Change name of default background from 'None' to 'Globe"' + $('#background_options_none') + .text('Globe') + .attr('id', 'background_options_globe') + .val('globe') + + viewer.setBackground('globe') + pc.material.pointSizeType = Potree.PointSizeType.ADAPTIVE pc.material.shape = Potree.PointShape.CIRCLE pc.material.activeAttributeName = 'elevation' From 4c69a04d4a956b552f8a7bf24d152b4050efa5ca Mon Sep 17 00:00:00 2001 From: Adrian Solberg Date: Mon, 29 Sep 2025 10:57:08 +0200 Subject: [PATCH 09/16] fix(#9): fix url and spelling mistake in config --- src/config.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/config.js b/src/config.js index 8245b56..b52bd33 100644 --- a/src/config.js +++ b/src/config.js @@ -1,12 +1,11 @@ -export const POTREE_POINTCLOUD_URL = - '../pointclouds/data_converted/metadata.json' +export const POTREE_POINTCLOUD_URL = '/pointclouds/data_converted/metadata.json' export const INITIAL_CESIUM_POS = { x: 4303414.154026048, y: 552161.235598733, z: 4660771.704035539, heading: 10, - pitch: -Cesium.Math.PI_PVER_FOUR, + pitch: -Cesium.Math.PI_OVER_FOUR, roll: 0.0 } From d882dc2103df0ec5608029f55dacdae8159dee63 Mon Sep 17 00:00:00 2001 From: Adrian Solberg Date: Sat, 4 Oct 2025 08:29:28 +0200 Subject: [PATCH 10/16] refactor(#9): change coordinate system to EPSG:4978 Makes pointclouds follow the curvature of the globe. --- src/cameraSync.js | 11 ++++------- src/main.js | 2 +- src/potreeViewer.js | 12 +++--------- 3 files changed, 8 insertions(+), 17 deletions(-) diff --git a/src/cameraSync.js b/src/cameraSync.js index 173d9a0..0e8b419 100644 --- a/src/cameraSync.js +++ b/src/cameraSync.js @@ -5,13 +5,10 @@ export function syncCameras(potreeViewer, cesiumViewer) { const pUp = new THREE.Vector3(0, 600, 0).applyMatrix4(camera.matrixWorld) const pTarget = potreeViewer.scene.view.getPivot() - const toCes = (pos) => { - const xy = [pos.x, pos.y] - const height = pos.z - const deg = toMap.forward(xy) - return Cesium.Cartesian3.fromDegrees(...deg, height) - } - + const toCes = (v) => + new Cesium.Cartesian3(v.x, v.y, v.z) + + const cPos = toCes(pPos) const cUpTarget = toCes(pUp) const cTarget = toCes(pTarget) diff --git a/src/main.js b/src/main.js index 8ab5b84..4417550 100644 --- a/src/main.js +++ b/src/main.js @@ -21,7 +21,7 @@ async function init() { requestAnimationFrame(loop) potreeViewer.update(potreeViewer.clock.getDelta(), timestamp) potreeViewer.render() - if (window.toMap) syncCameras(potreeViewer, cesiumViewer) + syncCameras(potreeViewer, cesiumViewer) cesiumViewer.render() } diff --git a/src/potreeViewer.js b/src/potreeViewer.js index c098d30..cad9ced 100644 --- a/src/potreeViewer.js +++ b/src/potreeViewer.js @@ -41,16 +41,10 @@ export async function createPotreeViewer(containerId, pointcloudUrl, settings) { pc.material.activeAttributeName = 'elevation' pc.material.gradient = Potree.Gradients['RAINBOW'] - e.pointcloud.projection = - '+proj=utm +zone=32 +ellps=GRS80 +datum=ETRS89 +units=m +no_defs' - const pointcloudProjection = e.pointcloud.projection - const mapProjection = proj4.defs('WGS84') - window.toMap = proj4(pointcloudProjection, mapProjection) - window.toScene = proj4(mapProjection, pointcloudProjection) - + e.pointcloud.projection = '+proj=geocent +datum=WGS84 +units=m +no_defs' viewer.scene.view.setView( - [401603.85, 8860821.54, 1000000], - [401603.85, 8860821.54, -2650.47] + [1993552.900, 87954.487, 7134018.721], + [1184471.630, 63828.490, 6243615.520] ) return viewer From b19d495619cd03caa42e9cb8409515a118876f0b Mon Sep 17 00:00:00 2001 From: Adrian Solberg Date: Sat, 4 Oct 2025 12:31:07 +0200 Subject: [PATCH 11/16] fix(#9): hide globe when directly above pivot Fixes globe syncing issues when directly above pivot. --- index.html | 6 ++-- src/ElevationControl/elevationControl.js | 1 - src/cameraSync.js | 36 +++++++++++++----------- src/potreeViewer.js | 4 +-- 4 files changed, 24 insertions(+), 23 deletions(-) diff --git a/index.html b/index.html index 1c65466..e2dcf9d 100644 --- a/index.html +++ b/index.html @@ -24,9 +24,9 @@ href="/libs/jstree/themes/mixed/style.css" /> diff --git a/src/ElevationControl/elevationControl.js b/src/ElevationControl/elevationControl.js index 276456b..a8f4ebb 100644 --- a/src/ElevationControl/elevationControl.js +++ b/src/ElevationControl/elevationControl.js @@ -82,7 +82,6 @@ function moveElevationContainer() { //initiate and orchestrate all funcitons to render the Evelation control section of the sidebar propperly window.initElevationControls = function initElevationControls(viewer) { - //Creates the section createElevationPanel(viewer) diff --git a/src/cameraSync.js b/src/cameraSync.js index 0e8b419..b7c2a9f 100644 --- a/src/cameraSync.js +++ b/src/cameraSync.js @@ -5,10 +5,8 @@ export function syncCameras(potreeViewer, cesiumViewer) { const pUp = new THREE.Vector3(0, 600, 0).applyMatrix4(camera.matrixWorld) const pTarget = potreeViewer.scene.view.getPivot() - const toCes = (v) => - new Cesium.Cartesian3(v.x, v.y, v.z) - - + const toCes = (v) => new Cesium.Cartesian3(v.x, v.y, v.z) + const cPos = toCes(pPos) const cUpTarget = toCes(pUp) const cTarget = toCes(pTarget) @@ -22,8 +20,8 @@ export function syncCameras(potreeViewer, cesiumViewer) { new Cesium.Cartesian3() ) - // Hide globe when the camera is below the surface or blocked by the curvature of the Earth - const showGlobe = shouldShowGlobe(cPos, cTarget) + // Hide globe when the camera is below the surface, blocked by the curvature of the Earth or directly above the pivot + const showGlobe = shouldShowGlobe(cPos, cTarget, cDir) cesiumViewer.scene.globe.show = showGlobe cesiumViewer.scene.skyAtmosphere.show = showGlobe @@ -48,17 +46,16 @@ export function syncCameras(potreeViewer, cesiumViewer) { /** * Determines whether the globe should be visible based on the camera position. * - * Returns false if the camera is below the globe surface or if the pivot - * point would be blocked by the curvature of the Earth. This is handled - * in a unified way by projecting the pivot to the globe surface and - * comparing the camera and pivot positions along the axis from the Earth's - * center through the pivot. + * Returns false if the camera is below the globe surface, if the pivot + * point would be blocked by the curvature of the Earth or if the camera + * is looking almost straight down at the pivot. * - * @param cameraPos - The camera position in Cesium Cartesian3 coordinates - * @param pivot - The pivot point in the point cloud (Cartesian3) + * @param cameraPos - The camera position in Cesium.Cartesian3 coordinates + * @param pivot - The pivot point in Cesium.Cartesian3 coordinates + * @param direction - The camera direction as a Cesium.Cartesian3 unit vector * @returns true if the globe should be visible, false if it should be hidden */ -function shouldShowGlobe(cameraPos, pivot) { +function shouldShowGlobe(cameraPos, pivot, direction) { const ellipsoid = Cesium.Ellipsoid.WGS84 const earthCenter = Cesium.Cartesian3.ZERO @@ -83,7 +80,12 @@ function shouldShowGlobe(cameraPos, pivot) { const camProj = Cesium.Cartesian3.dot(cameraPos, axis) const pivotProj = Cesium.Cartesian3.dot(pivotSurface, axis) - // If camera is "above" pivot on this axis, the globe should be visible - // If camera is "below" pivot on this axis, the globe should be not be visible - return camProj >= pivotProj + // Compute the dot product between camera direction and local vertical + // Used to detect if the camera is looking almost straight down + const targetNormal = Cesium.Ellipsoid.WGS84.geodeticSurfaceNormal(pivot) + const dotProduct = Math.abs(Cesium.Cartesian3.dot(direction, targetNormal)) + + // If camera is "above" pivot on the axis, and not looking nearly straight down, the globe should be visible + // Otherwise, the globe should not be visible + return camProj >= pivotProj && dotProduct < 0.99 } diff --git a/src/potreeViewer.js b/src/potreeViewer.js index dd52061..c05525e 100644 --- a/src/potreeViewer.js +++ b/src/potreeViewer.js @@ -47,8 +47,8 @@ export async function createPotreeViewer(containerId, pointcloudUrl, settings) { e.pointcloud.projection = '+proj=geocent +datum=WGS84 +units=m +no_defs' viewer.scene.view.setView( - [1993552.900, 87954.487, 7134018.721], - [1184471.630, 63828.490, 6243615.520] + [1993552.9, 87954.487, 7134018.721], + [1184471.63, 63828.49, 6243615.52] ) return viewer From 7a471e07384e5aff329cf00eae93411e70c8d32f Mon Sep 17 00:00:00 2001 From: Adrian Solberg Date: Sat, 4 Oct 2025 13:27:02 +0200 Subject: [PATCH 12/16] fix(#9): fix gradients for new coordinate system --- src/ElevationControl/elevationControl.js | 7 +++++ src/potreeViewer.js | 37 ++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/src/ElevationControl/elevationControl.js b/src/ElevationControl/elevationControl.js index a8f4ebb..6a16562 100644 --- a/src/ElevationControl/elevationControl.js +++ b/src/ElevationControl/elevationControl.js @@ -61,6 +61,13 @@ function rebindElevationLabel() { label.textContent = `${low.toFixed(2)} to ${high.toFixed(2)}` } + // Adjust slider limits + slider.slider({ + min: -10000, + max: 0, + values: [-10000, 0] + }) + //clear any old namespaced handlers and attach fresh ones slider.off('slide.custom slidestop.custom change.custom') slider.on('slide.custom', update) diff --git a/src/potreeViewer.js b/src/potreeViewer.js index c05525e..0b5d915 100644 --- a/src/potreeViewer.js +++ b/src/potreeViewer.js @@ -42,6 +42,8 @@ export async function createPotreeViewer(containerId, pointcloudUrl, settings) { pc.material.pointSizeType = Potree.PointSizeType.ADAPTIVE pc.material.shape = Potree.PointShape.CIRCLE + overrideShaderForGradient(pc) + pc.material.elevationRange = [-10000, 0] pc.material.activeAttributeName = 'elevation' pc.material.gradient = Potree.Gradients['VIRIDIS'] @@ -64,3 +66,38 @@ function clampScrollRadius(e) { this.radiusDelta = newRadius - this.scene.view.radius this.stopTweens() } + +function overrideShaderForGradient(pc) { + const originalUpdateShaderSource = pc.material.updateShaderSource + pc.material.updateShaderSource = function () { + // Call the original updateShaderSource first + originalUpdateShaderSource.call(this) + + // Override the shader's getElevation function to use elevation relative to sealevel + this.vertexShader = this.vertexShader.replace( + /vec3 getElevation\(\)[\s\S]*?\}/, + ` + vec3 getElevation(){ + vec4 world = modelMatrix * vec4(position, 1.0); + float radius = length(world.xyz); + float latitude = asin(world.z / radius); + + const float a = 6378137.0; + const float b = 6356752.3; + + float cosLat = cos(latitude); + float sinLat = sin(latitude); + float numerator = (a*a * cosLat) * (a*a * cosLat) + (b*b * sinLat) * (b*b * sinLat); + float denominator = (a * cosLat) * (a * cosLat) + (b * sinLat) * (b * sinLat); + float radiusAtLatitude = sqrt(numerator / denominator); + + float w = (radius - radiusAtLatitude - elevationRange.x) / (elevationRange.y - elevationRange.x); + return texture2D(gradient, vec2(w, 1.0-w)).rgb; + } + ` + ) + + // Mark the material as needing recompilation + this.needsUpdate = true + } +} From 1e7b214a5a40698c769197109bfaa7aaa3812351 Mon Sep 17 00:00:00 2001 From: Adrian Solberg Date: Sat, 4 Oct 2025 14:05:38 +0200 Subject: [PATCH 13/16] refactor(#9): remove unnecessary code and add comments --- src/cameraSync.js | 10 ++++++++++ src/cesiumViewer.js | 18 ++++++------------ src/config.js | 9 --------- src/main.js | 9 ++------- src/potreeViewer.js | 18 ++++++++++++++++++ 5 files changed, 36 insertions(+), 28 deletions(-) diff --git a/src/cameraSync.js b/src/cameraSync.js index b7c2a9f..35fcd5e 100644 --- a/src/cameraSync.js +++ b/src/cameraSync.js @@ -1,6 +1,13 @@ +/** + * Syncs Potree's point cloud with Cesium's globe. + * + * @param potreeViewer - used for point cloud + * @param cesiumViewer - used for globe + */ export function syncCameras(potreeViewer, cesiumViewer) { const camera = potreeViewer.scene.getActiveCamera() + // Compute camera position, up vector, and target (pivot) in world coordinates const pPos = new THREE.Vector3(0, 0, 0).applyMatrix4(camera.matrixWorld) const pUp = new THREE.Vector3(0, 600, 0).applyMatrix4(camera.matrixWorld) const pTarget = potreeViewer.scene.view.getPivot() @@ -11,6 +18,7 @@ export function syncCameras(potreeViewer, cesiumViewer) { const cUpTarget = toCes(pUp) const cTarget = toCes(pTarget) + // Compute Cesium camera direction and up vectors const cDir = Cesium.Cartesian3.normalize( Cesium.Cartesian3.subtract(cTarget, cPos, new Cesium.Cartesian3()), new Cesium.Cartesian3() @@ -25,6 +33,7 @@ export function syncCameras(potreeViewer, cesiumViewer) { cesiumViewer.scene.globe.show = showGlobe cesiumViewer.scene.skyAtmosphere.show = showGlobe + // Sync Cesium camera position and orientation with Potree cesiumViewer.camera.setView({ destination: cPos, orientation: { @@ -33,6 +42,7 @@ export function syncCameras(potreeViewer, cesiumViewer) { } }) + // Match FOV const aspect = camera.aspect const fovy = Math.PI * (camera.fov / 180) if (aspect < 1) { diff --git a/src/cesiumViewer.js b/src/cesiumViewer.js index 3fdbbbd..af8336e 100644 --- a/src/cesiumViewer.js +++ b/src/cesiumViewer.js @@ -1,3 +1,9 @@ +/** + * Initializes the Cesium viewer used to visualize the globe. + * + * @param containerId - id of the container + * @returns Cesium viewer + */ export function createCesiumViewer(containerId) { const viewer = new Cesium.Viewer(containerId, { useDefaultRenderLoop: false, @@ -18,15 +24,3 @@ export function createCesiumViewer(containerId) { }) return viewer } - -export function setCesiumView(viewer, pos) { - const cp = new Cesium.Cartesian3(pos.x, pos.y, pos.z) - viewer.camera.setView({ - destination: cp, - orientation: { - heading: pos.heading, - pitch: pos.pitch, - roll: pos.roll - } - }) -} diff --git a/src/config.js b/src/config.js index b52bd33..fa55e71 100644 --- a/src/config.js +++ b/src/config.js @@ -1,14 +1,5 @@ export const POTREE_POINTCLOUD_URL = '/pointclouds/data_converted/metadata.json' -export const INITIAL_CESIUM_POS = { - x: 4303414.154026048, - y: 552161.235598733, - z: 4660771.704035539, - heading: 10, - pitch: -Cesium.Math.PI_OVER_FOUR, - roll: 0.0 -} - export const POTREE_SETTINGS = { edl: true, fov: 60, diff --git a/src/main.js b/src/main.js index 4417550..82f7e1e 100644 --- a/src/main.js +++ b/src/main.js @@ -1,15 +1,10 @@ -import { - POTREE_POINTCLOUD_URL, - INITIAL_CESIUM_POS, - POTREE_SETTINGS -} from './config.js' -import { createCesiumViewer, setCesiumView } from './cesiumViewer.js' +import { POTREE_POINTCLOUD_URL, POTREE_SETTINGS } from './config.js' +import { createCesiumViewer } from './cesiumViewer.js' import { createPotreeViewer } from './potreeViewer.js' import { syncCameras } from './cameraSync.js' async function init() { window.cesiumViewer = createCesiumViewer('cesiumContainer') - setCesiumView(window.cesiumViewer, INITIAL_CESIUM_POS) window.potreeViewer = await createPotreeViewer( 'potree_render_area', diff --git a/src/potreeViewer.js b/src/potreeViewer.js index 0b5d915..c6a20a3 100644 --- a/src/potreeViewer.js +++ b/src/potreeViewer.js @@ -1,3 +1,11 @@ +/** + * Initializes the Potree viewer used to visualize the point cloud. + * + * @param containerId - id of the container + * @param pointcloudUrl - url path to the point cloud + * @param settings - other settings + * @returns Potree viewer + */ export async function createPotreeViewer(containerId, pointcloudUrl, settings) { const viewer = new Potree.Viewer(document.getElementById(containerId), { useDefaultRenderLoop: false @@ -56,6 +64,11 @@ export async function createPotreeViewer(containerId, pointcloudUrl, settings) { return viewer } +/** + * Replacement for original scroll function which limits how far you can zoom out. + * + * @param e - the given event + */ function clampScrollRadius(e) { let resolvedRadius = this.scene.view.radius + this.radiusDelta let newRadius = resolvedRadius - e.delta * resolvedRadius * 0.1 @@ -67,6 +80,11 @@ function clampScrollRadius(e) { this.stopTweens() } +/** + * Adjust shader to use elevation relative to sealevel for EPSG:4978 coordinates. + * + * @param pc - the point cloud + */ function overrideShaderForGradient(pc) { const originalUpdateShaderSource = pc.material.updateShaderSource pc.material.updateShaderSource = function () { From c3bfa5474766efa941d9650ffb1b9407c737259c Mon Sep 17 00:00:00 2001 From: Adrian Solberg Date: Sat, 4 Oct 2025 15:09:06 +0200 Subject: [PATCH 14/16] refactor(#9): change elevationControl handling --- README.md | 2 +- index.html | 1 - src/ElevationControl/elevationControl.js | 2 +- src/potreeViewer.js | 6 +++--- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index d48ceab..518effe 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Molloy Explorer is a 3D seabed visualization tool built with **Potree**. It allo ### Add point cloud data -Place the point cloud data (in Potree format) in `public/pointclouds/data_converted`. +Place the point cloud data (in Potree format with EPSG:4978 coordinates) in `public/pointclouds/data_converted`. **Note:** Point cloud files should not be committed to Git. diff --git a/index.html b/index.html index e2dcf9d..09b18c3 100644 --- a/index.html +++ b/index.html @@ -70,7 +70,6 @@
- diff --git a/src/ElevationControl/elevationControl.js b/src/ElevationControl/elevationControl.js index 6a16562..c4f28ae 100644 --- a/src/ElevationControl/elevationControl.js +++ b/src/ElevationControl/elevationControl.js @@ -88,7 +88,7 @@ function moveElevationContainer() { } //initiate and orchestrate all funcitons to render the Evelation control section of the sidebar propperly -window.initElevationControls = function initElevationControls(viewer) { +export function initElevationControls(viewer) { //Creates the section createElevationPanel(viewer) diff --git a/src/potreeViewer.js b/src/potreeViewer.js index c6a20a3..06aaedf 100644 --- a/src/potreeViewer.js +++ b/src/potreeViewer.js @@ -1,3 +1,5 @@ +import { initElevationControls } from './ElevationControl/elevationControl.js' + /** * Initializes the Potree viewer used to visualize the point cloud. * @@ -31,9 +33,7 @@ export async function createPotreeViewer(containerId, pointcloudUrl, settings) { $('#menu_filters').next().show() viewer.toggleSidebar() - if (window.initElevationControls) { - window.initElevationControls(viewer) - } + initElevationControls(viewer) }) const e = await Potree.loadPointCloud(pointcloudUrl) From 2b8f6b68b2f45471bb87015541200c3a487916aa Mon Sep 17 00:00:00 2001 From: Adrian Solberg Date: Sun, 5 Oct 2025 19:09:08 +0200 Subject: [PATCH 15/16] docs(#9): add some explanatory comments --- src/potreeViewer.js | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/potreeViewer.js b/src/potreeViewer.js index 06aaedf..3f04686 100644 --- a/src/potreeViewer.js +++ b/src/potreeViewer.js @@ -56,9 +56,11 @@ export async function createPotreeViewer(containerId, pointcloudUrl, settings) { pc.material.gradient = Potree.Gradients['VIRIDIS'] e.pointcloud.projection = '+proj=geocent +datum=WGS84 +units=m +no_defs' + + // Initialize camera position and target point (manually chosen) viewer.scene.view.setView( - [1993552.9, 87954.487, 7134018.721], - [1184471.63, 63828.49, 6243615.52] + [1993552.9, 87954.487, 7134018.721], // Initial camera position + [1184471.63, 63828.49, 6243615.52] // Initial target point ) return viewer @@ -96,20 +98,30 @@ function overrideShaderForGradient(pc) { /vec3 getElevation\(\)[\s\S]*?\}/, ` vec3 getElevation(){ + // Transform the vertex position into world coordinates vec4 world = modelMatrix * vec4(position, 1.0); + + // Compute distance from Earth's center and latitude float radius = length(world.xyz); float latitude = asin(world.z / radius); - const float a = 6378137.0; - const float b = 6356752.3; + const float a = 6378137.0; // Equatorial radius + const float b = 6356752.3; // Polar radius + // Compute distance from Earth's center to the surface at the given latitude float cosLat = cos(latitude); float sinLat = sin(latitude); float numerator = (a*a * cosLat) * (a*a * cosLat) + (b*b * sinLat) * (b*b * sinLat); float denominator = (a * cosLat) * (a * cosLat) + (b * sinLat) * (b * sinLat); float radiusAtLatitude = sqrt(numerator / denominator); - float w = (radius - radiusAtLatitude - elevationRange.x) / (elevationRange.y - elevationRange.x); + // Compute depth below the ellipsoid (sea level) + float depth = radius - radiusAtLatitude; + + // Normalize depth to a [0, 1] range for coloring + float w = (depth - elevationRange.x) / (elevationRange.y - elevationRange.x); + + // Sample color from gradient texture based on normalized depth return texture2D(gradient, vec2(w, 1.0-w)).rgb; } ` From 66aa93453f50266634cff95dab584c9eebce4814 Mon Sep 17 00:00:00 2001 From: Adrian Solberg Date: Sun, 5 Oct 2025 19:11:37 +0200 Subject: [PATCH 16/16] chore(#9): remove old potree background image --- index.html | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/index.html b/index.html index 09b18c3..432ff20 100644 --- a/index.html +++ b/index.html @@ -49,23 +49,17 @@
-
-
+
+