Skip to content

#9 Coastline overlay #22

Merged
merged 17 commits into from
Oct 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
64 changes: 17 additions & 47 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@
type="text/css"
href="/libs/jstree/themes/mixed/style.css"
/>
<link
rel="stylesheet"
type="text/css"
href="/libs/Cesium/Widgets/CesiumWidget/CesiumWidget.css"
/>
<link rel="stylesheet" type="text/css" href="src/style.css" />
</head>

Expand All @@ -40,60 +45,25 @@
<script src="/build/potree/potree.js"></script>
<script src="/libs/plasio/js/laslaz.js"></script>
<script src="/libs/three.js/build/three.js"></script>
<script src="/libs/Cesium/Cesium.js"></script>

<div
class="potree_container"
style="position: absolute; width: 100%; height: 100%; left: 0px; top: 0px"
style="
position: absolute;
width: 100%;
height: 100%;
left: 0px;
top: 0px;
background-color: black;
"
>
<div
id="potree_render_area"
style="
background-image: url('/build/potree/resources/images/background.jpg');
"
></div>
<div id="potree_render_area">
<div id="cesiumContainer"></div>
</div>
<div id="potree_sidebar_container"></div>
</div>

<script src="src/ElevationControl/elevationControl.js"></script>
<script type="module">
window.viewer = new Potree.Viewer(
document.getElementById('potree_render_area')
)

viewer.setEDLEnabled(true)
viewer.setFOV(60)
viewer.setPointBudget(5_000_000)
viewer.loadSettingsFromURL()

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()

if (window.initElevationControls) {
window.initElevationControls(viewer)
}
})

let url = './pointclouds/data_converted/metadata.json'
Potree.loadPointCloud(url).then((e) => {
let pointcloud = e.pointcloud
let material = pointcloud.material

material.pointSizeType = Potree.PointSizeType.ADAPTIVE
material.shape = Potree.PointShape.CIRCLE
material.activeAttributeName = 'elevation'
material.gradient = Potree.Gradients['VIRIDIS']

viewer.scene.addPointCloud(pointcloud)
viewer.fitToScreen()
})
</script>
<script type="module" src="src/main.js"></script>
</body>
</html>
10 changes: 8 additions & 2 deletions src/ElevationControl/elevationControl.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -81,8 +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)

Expand Down
101 changes: 101 additions & 0 deletions src/cameraSync.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/**
* 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()

const toCes = (v) => new Cesium.Cartesian3(v.x, v.y, v.z)

const cPos = toCes(pPos)
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()
)
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, 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

// Sync Cesium camera position and orientation with Potree
cesiumViewer.camera.setView({
destination: cPos,
orientation: {
direction: cDir,
up: cUp
}
})

// Match FOV
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
}
}

/**
* Determines whether the globe should be visible based on the camera position.
*
* 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 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, direction) {
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)

// 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
}
26 changes: 26 additions & 0 deletions src/cesiumViewer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* 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,
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
}
7 changes: 7 additions & 0 deletions src/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const POTREE_POINTCLOUD_URL = '/pointclouds/data_converted/metadata.json'

export const POTREE_SETTINGS = {
edl: true,
fov: 60,
pointBudget: 1_000_000
}
26 changes: 26 additions & 0 deletions src/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
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')

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()
syncCameras(potreeViewer, cesiumViewer)
cesiumViewer.render()
}

requestAnimationFrame(loop)
}

init()
Loading