Skip to content

Commit

Permalink
Merge branch 'dev' of git.ntnu.no:TDT4290-group-4/MolloyExplorer into…
Browse files Browse the repository at this point in the history
… 10-show-coordinates-on-screen
  • Loading branch information
kleinc committed Oct 6, 2025
2 parents 0b5b038 + c32efa6 commit 9ed3eec
Show file tree
Hide file tree
Showing 8 changed files with 429 additions and 50 deletions.
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
67 changes: 22 additions & 45 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,16 @@
href="/libs/jstree/themes/mixed/style.css"
/>
<link rel="stylesheet" type="text/css" href="src/style.css" />
<link rel="stylesheet" type="text/css" href="src/coordinateShowing/coordinateShowing.css" />
<link
rel="stylesheet"
type="text/css"
href="src/coordinateShowing/coordinateShowing.css"
/>
<link
rel="stylesheet"
type="text/css"
href="/libs/Cesium/Widgets/CesiumWidget/CesiumWidget.css"
/>
</head>

<body>
Expand All @@ -42,60 +51,28 @@
<script src="/libs/plasio/js/laslaz.js"></script>
<script src="/libs/three.js/build/three.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/proj4js/2.7.6/proj4.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 id="potree_render_area">
<div id="cesiumContainer"></div>
<canvas id="posCanvas" width="200" height="30"></canvas>
<canvas id="elevationCanvas" width="200" height="30"></canvas>
</div>

</div>
<div id="potree_sidebar_container"></div>
</div>

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

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['RAINBOW']

viewer.scene.addPointCloud(pointcloud)
viewer.fitToScreen()
})
</script>
<script type="module" src="src/main.js"></script>
</body>
</html>
113 changes: 113 additions & 0 deletions src/ElevationControl/elevationControl.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
//Cerating a customized section "Elevation Control"
window.createElevationPanel = function createElevationPanel(viewer) {
const container = document.getElementById('elevation_list')
let targetContainer = container
if (!targetContainer) {
// Create a new accordion section for Elevation Control
const menu = document.getElementById('potree_menu')
if (menu) {
const header = document.createElement('h3')
header.id = 'menu_elevation'
header.innerHTML = '<span>Elevation Control</span>'
const panel = document.createElement('div')
panel.className = 'pv-menu-list'
panel.innerHTML = '<div id="elevation_list" class="auto"></div>'
const about = document.getElementById('menu_appearance')
if (about) {
menu.insertBefore(panel, about)
menu.insertBefore(header, panel)
} else {
menu.appendChild(header)
menu.appendChild(panel)
}
// Activate accordion behavior if jQuery UI accordion already initialized
if ($(menu).accordion) {
try {
$(menu).accordion('refresh')
} catch (e) {}
}
// Toggle on header click if not managed by accordion refresh
header.addEventListener(
'click',
() =>
(panel.style.display = panel.style.display === 'none' ? '' : 'none')
)
targetContainer = panel.querySelector('#elevation_list')
}
}
}

//Select the fist pointcloud in the sidebar so that the Elevation section is built
function autoSelectFirstPointCloud() {
const cloudIcon = document.querySelector(
'#scene_objects i.jstree-themeicon-custom'
)
if (cloudIcon) {
cloudIcon.dispatchEvent(new MouseEvent('click', { bubbles: true }))
return true
}
return false
}

//(re)connect the elevation labels to the slider after the container is moved (was not handled by default)
function rebindElevationLabel() {
const slider = window.jQuery ? window.jQuery('#sldHeightRange') : null
const label = document.getElementById('lblHeightRange')
if (!slider || !slider.length || !label) return

const update = () => {
const low = slider.slider('values', 0)
const high = slider.slider('values', 1)
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)
slider.on('slidestop.custom change.custom', update)
update()
}

//Move the elevation range section to the customised "Elevation Control" section
function moveElevationContainer() {
const target = document.getElementById('elevation_list')
const elevationContainer = document.querySelector(
'#materials\\.elevation_container'
)
if (!elevationContainer) return false
target.appendChild(elevationContainer)
rebindElevationLabel()
return true
}

//initiate and orchestrate all funcitons to render the Evelation control section of the sidebar propperly
export function initElevationControls(viewer) {
//Creates the section
createElevationPanel(viewer)

//Only move the ElevationContainer if the source container to exist
const menu =
document.getElementById('potree_menu') ||
document.getElementById('menu') ||
document.body
const observer = new MutationObserver(() => {
const found = document.querySelector('#materials\\.elevation_container')
if (found) {
observer.disconnect()
//Move and rebind once it exists
const ok = moveElevationContainer()
if (!ok) console.warn('[Elevation] moveElevationContainer failed')
}
})
observer.observe(menu, { childList: true, subtree: true })

//Trigger Potree to build Materials UI by selecting the first point cloud (if nothing selected yet)
autoSelectFirstPointCloud()
}
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
}
30 changes: 26 additions & 4 deletions src/main.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,35 @@
import { POTREE_POINTCLOUD_URL, POTREE_SETTINGS } from './config.js'
import { createCesiumViewer } from './cesiumViewer.js'
import { createPotreeViewer } from './potreeViewer.js'
import { syncCameras } from './cameraSync.js'
import { initCanvases, updatePosition, updateTargetElevation } from "./coordinateShowing/coordinateShowing.js";

function init() {
initCanvases();

async function init() {
window.cesiumViewer = createCesiumViewer('cesiumContainer')

window.potreeViewer = await createPotreeViewer(
'potree_render_area',
POTREE_POINTCLOUD_URL,
POTREE_SETTINGS
)

initCanvases();

viewer.addEventListener("update", updatePosition);
viewer.addEventListener("update", updateTargetElevation);

window.addEventListener('resize', initCanvases);
}

init();
function loop(timestamp) {
requestAnimationFrame(loop)
potreeViewer.update(potreeViewer.clock.getDelta(), timestamp)
potreeViewer.render()
syncCameras(potreeViewer, cesiumViewer)
cesiumViewer.render()
}

requestAnimationFrame(loop)
}

init()
Loading

0 comments on commit 9ed3eec

Please sign in to comment.