230 lines
7.8 KiB
HTML
230 lines
7.8 KiB
HTML
|
|
<!DOCTYPE html>
|
||
|
|
<html lang="en">
|
||
|
|
|
||
|
|
<head>
|
||
|
|
<meta charset="UTF-8">
|
||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
|
|
<title>Point Cloud Viewer (Updated for Binary PCD)</title>
|
||
|
|
<style>
|
||
|
|
body {
|
||
|
|
margin: 0;
|
||
|
|
overflow: hidden;
|
||
|
|
font-family: Arial, sans-serif;
|
||
|
|
}
|
||
|
|
|
||
|
|
#container {
|
||
|
|
position: relative;
|
||
|
|
width: 100vw;
|
||
|
|
height: 100vh;
|
||
|
|
}
|
||
|
|
|
||
|
|
#info {
|
||
|
|
position: absolute;
|
||
|
|
top: 10px;
|
||
|
|
left: 10px;
|
||
|
|
background: rgba(0, 0, 0, 0.7);
|
||
|
|
color: white;
|
||
|
|
padding: 10px;
|
||
|
|
border-radius: 5px;
|
||
|
|
z-index: 10;
|
||
|
|
}
|
||
|
|
|
||
|
|
#controls {
|
||
|
|
position: absolute;
|
||
|
|
top: 10px;
|
||
|
|
right: 10px;
|
||
|
|
background: rgba(0, 0, 0, 0.7);
|
||
|
|
color: white;
|
||
|
|
padding: 10px;
|
||
|
|
border-radius: 5px;
|
||
|
|
z-index: 10;
|
||
|
|
}
|
||
|
|
|
||
|
|
#controls select,
|
||
|
|
#controls button {
|
||
|
|
display: block;
|
||
|
|
margin: 5px 0;
|
||
|
|
padding: 5px;
|
||
|
|
}
|
||
|
|
|
||
|
|
#loading {
|
||
|
|
position: absolute;
|
||
|
|
top: 50%;
|
||
|
|
left: 50%;
|
||
|
|
transform: translate(-50%, -50%);
|
||
|
|
color: white;
|
||
|
|
background: rgba(0, 0, 0, 0.7);
|
||
|
|
padding: 20px;
|
||
|
|
border-radius: 5px;
|
||
|
|
z-index: 20;
|
||
|
|
display: none;
|
||
|
|
}
|
||
|
|
</style>
|
||
|
|
</head>
|
||
|
|
|
||
|
|
<body>
|
||
|
|
<div id="container"></div>
|
||
|
|
<div id="info">
|
||
|
|
<h3>Point Cloud Viewer</h3>
|
||
|
|
<p>Use mouse to orbit, zoom, and pan.</p>
|
||
|
|
</div>
|
||
|
|
<div id="controls">
|
||
|
|
<label for="pcdSelect">Select PCD File:</label>
|
||
|
|
<select id="pcdSelect">
|
||
|
|
<option value="">-- Loading files... --</option>
|
||
|
|
</select>
|
||
|
|
<button id="screenshotBtn">Take Screenshot</button>
|
||
|
|
</div>
|
||
|
|
<div id="loading">Loading Point Cloud...</div>
|
||
|
|
|
||
|
|
<script src="{{ url_for('static', filename='js/three.min.js') }}"></script>
|
||
|
|
<script src="{{ url_for('static', filename='js/OrbitControls.min.js') }}"></script>
|
||
|
|
<script src="{{ url_for('static', filename='js/PCDLoader.min.js') }}"></script>
|
||
|
|
|
||
|
|
<script>
|
||
|
|
let scene, camera, renderer, controls, pointCloud = null;
|
||
|
|
const container = document.getElementById('container');
|
||
|
|
const pcdSelect = document.getElementById('pcdSelect');
|
||
|
|
const screenshotBtn = document.getElementById('screenshotBtn');
|
||
|
|
const loadingDiv = document.getElementById('loading');
|
||
|
|
|
||
|
|
init();
|
||
|
|
loadPCDFileList();
|
||
|
|
|
||
|
|
function init() {
|
||
|
|
scene = new THREE.Scene();
|
||
|
|
scene.background = new THREE.Color(0xdddddd);
|
||
|
|
|
||
|
|
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
|
||
|
|
camera.position.z = 20;
|
||
|
|
|
||
|
|
renderer = new THREE.WebGLRenderer({ antialias: true, preserveDrawingBuffer: true });
|
||
|
|
renderer.setSize(window.innerWidth, window.innerHeight);
|
||
|
|
renderer.setPixelRatio(window.devicePixelRatio);
|
||
|
|
container.appendChild(renderer.domElement);
|
||
|
|
|
||
|
|
controls = new THREE.OrbitControls(camera, renderer.domElement);
|
||
|
|
controls.enableDamping = false;
|
||
|
|
controls.dampingFactor = 0.05;
|
||
|
|
controls.screenSpacePanning = true;
|
||
|
|
controls.minDistance = 1;
|
||
|
|
controls.maxDistance = 1000;
|
||
|
|
|
||
|
|
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
|
||
|
|
scene.add(ambientLight);
|
||
|
|
|
||
|
|
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.6);
|
||
|
|
directionalLight.position.set(10, 20, 15);
|
||
|
|
scene.add(directionalLight);
|
||
|
|
|
||
|
|
window.addEventListener('resize', onWindowResize, false);
|
||
|
|
animate();
|
||
|
|
}
|
||
|
|
|
||
|
|
function onWindowResize() {
|
||
|
|
camera.aspect = window.innerWidth / window.innerHeight;
|
||
|
|
camera.updateProjectionMatrix();
|
||
|
|
renderer.setSize(window.innerWidth, window.innerHeight);
|
||
|
|
}
|
||
|
|
|
||
|
|
function animate() {
|
||
|
|
requestAnimationFrame(animate);
|
||
|
|
controls.update();
|
||
|
|
renderer.render(scene, camera);
|
||
|
|
}
|
||
|
|
|
||
|
|
function loadPCDFileList() {
|
||
|
|
fetch('/api/pcd_files')
|
||
|
|
.then(response => response.json())
|
||
|
|
.then(files => {
|
||
|
|
pcdSelect.innerHTML = '';
|
||
|
|
const defaultOption = document.createElement('option');
|
||
|
|
defaultOption.text = '-- Select a PCD file --';
|
||
|
|
defaultOption.value = '';
|
||
|
|
pcdSelect.appendChild(defaultOption);
|
||
|
|
|
||
|
|
files.forEach(file => {
|
||
|
|
const option = document.createElement('option');
|
||
|
|
option.text = file;
|
||
|
|
option.value = file;
|
||
|
|
pcdSelect.appendChild(option);
|
||
|
|
});
|
||
|
|
})
|
||
|
|
.catch(error => {
|
||
|
|
console.error('Error loading PCD file list:', error);
|
||
|
|
pcdSelect.innerHTML = '<option value="">-- Error loading files --</option>';
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
pcdSelect.addEventListener('change', function () {
|
||
|
|
const selectedFile = this.value;
|
||
|
|
if (selectedFile) {
|
||
|
|
loadAndDisplayPCD(selectedFile);
|
||
|
|
} else {
|
||
|
|
if (pointCloud) {
|
||
|
|
scene.remove(pointCloud);
|
||
|
|
pointCloud.geometry.dispose();
|
||
|
|
pointCloud.material.dispose();
|
||
|
|
pointCloud = null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
function loadAndDisplayPCD(filename) {
|
||
|
|
if (pointCloud) {
|
||
|
|
scene.remove(pointCloud);
|
||
|
|
pointCloud.geometry.dispose();
|
||
|
|
pointCloud.material.dispose();
|
||
|
|
}
|
||
|
|
|
||
|
|
loadingDiv.style.display = 'block';
|
||
|
|
|
||
|
|
const loader = new THREE.PCDLoader();
|
||
|
|
loader.load(
|
||
|
|
`/api/load_pcd_stream/${encodeURIComponent(filename)}`,
|
||
|
|
function (pointsData) {
|
||
|
|
loadingDiv.style.display = 'none';
|
||
|
|
|
||
|
|
if (pointsData.material) {
|
||
|
|
pointsData.material.size = 0.05;
|
||
|
|
} else {
|
||
|
|
pointsData.material = new THREE.PointsMaterial({ size: 0.05, color: 0x00aaff });
|
||
|
|
}
|
||
|
|
|
||
|
|
pointCloud = pointsData;
|
||
|
|
scene.add(pointCloud);
|
||
|
|
|
||
|
|
if (pointCloud.geometry.boundingBox) {
|
||
|
|
pointCloud.geometry.computeBoundingBox();
|
||
|
|
const size = pointCloud.geometry.boundingBox.getSize(new THREE.Vector3());
|
||
|
|
const maxDim = Math.max(size.x, size.y, size.z);
|
||
|
|
camera.position.set(0, 0, maxDim * 1.5);
|
||
|
|
controls.target.copy(pointCloud.geometry.boundingBox.getCenter(new THREE.Vector3()));
|
||
|
|
} else {
|
||
|
|
camera.position.set(0, 0, 20);
|
||
|
|
controls.target.set(0, 0, 0);
|
||
|
|
}
|
||
|
|
controls.update();
|
||
|
|
},
|
||
|
|
undefined,
|
||
|
|
function (error) {
|
||
|
|
loadingDiv.style.display = 'none';
|
||
|
|
console.error('Error loading PCD file:', error);
|
||
|
|
alert(`Failed to load PCD file: ${error.message}`);
|
||
|
|
}
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
screenshotBtn.addEventListener('click', function () {
|
||
|
|
const canvas = renderer.domElement;
|
||
|
|
const link = document.createElement('a');
|
||
|
|
link.download = `pointcloud_screenshot_${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.png`;
|
||
|
|
link.href = canvas.toDataURL('image/png');
|
||
|
|
document.body.appendChild(link);
|
||
|
|
link.click();
|
||
|
|
document.body.removeChild(link);
|
||
|
|
});
|
||
|
|
</script>
|
||
|
|
</body>
|
||
|
|
|
||
|
|
</html>
|