feat: viewer split 3D+2D — AUV depth chart + USBL XY fix
This commit is contained in:
@@ -2,10 +2,11 @@ import * as THREE from 'three';
|
||||
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
||||
|
||||
// ── Renderer ──────────────────────────────────────────────────────────────────
|
||||
const viewerEl = document.getElementById('viewer');
|
||||
const renderer = new THREE.WebGLRenderer({ antialias: true });
|
||||
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
|
||||
renderer.setSize(innerWidth, innerHeight);
|
||||
document.body.appendChild(renderer.domElement);
|
||||
renderer.setSize(viewerEl.clientWidth, viewerEl.clientHeight);
|
||||
viewerEl.appendChild(renderer.domElement);
|
||||
|
||||
// ── Scene ─────────────────────────────────────────────────────────────────────
|
||||
const scene = new THREE.Scene();
|
||||
@@ -15,7 +16,7 @@ scene.add(new THREE.GridHelper(1000, 100, 0x111133, 0x0a0a22));
|
||||
scene.add(new THREE.AxesHelper(5));
|
||||
|
||||
// ── Camera + Controls ─────────────────────────────────────────────────────────
|
||||
const camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 0.1, 5000);
|
||||
const camera = new THREE.PerspectiveCamera(60, viewerEl.clientWidth / viewerEl.clientHeight, 0.1, 5000);
|
||||
camera.position.set(0, 150, 250);
|
||||
const controls = new OrbitControls(camera, renderer.domElement);
|
||||
controls.enableDamping = true;
|
||||
@@ -101,14 +102,9 @@ fetch('/api/trajectory')
|
||||
addLayer('usv_gps', makePoints(x, y, z, i => RTK_COLORS[Math.min(rtk[i], 2)]));
|
||||
}
|
||||
|
||||
if (d.auv_depth) {
|
||||
const { x, y, z } = d.auv_depth;
|
||||
addLayer('auv_depth', makeLine(x, y, z, 0x44ffaa));
|
||||
}
|
||||
|
||||
if (d.usbl) {
|
||||
const { x, y, z } = d.usbl;
|
||||
addLayer('usbl', makePoints(x, y, z, () => [1, 0.42, 0.21]));
|
||||
if (d.auv_usbl) {
|
||||
const { x, y, z } = d.auv_usbl;
|
||||
addLayer('auv_usbl', makePoints(x, y, z, () => [0.27, 1.0, 0.67]));
|
||||
}
|
||||
|
||||
const trajData = d.fused || d.lingbot_local;
|
||||
@@ -149,6 +145,60 @@ fetch('/api/trajectory')
|
||||
camera.position.set(cx, 50, -cy + 80);
|
||||
controls.update();
|
||||
}
|
||||
|
||||
// 2D depth/altitude chart
|
||||
if (d.auv_profile) {
|
||||
const { t_s, depth_m, altitude_m } = d.auv_profile;
|
||||
const hasAlt = altitude_m && altitude_m.some(v => !isNaN(v) && v !== null);
|
||||
const datasets = [
|
||||
{
|
||||
label: 'Depth (m)',
|
||||
data: t_s.map((t, i) => ({ x: t, y: -depth_m[i] })),
|
||||
borderColor: '#3a9fff',
|
||||
backgroundColor: 'rgba(58,159,255,0.08)',
|
||||
borderWidth: 1.5,
|
||||
pointRadius: 0,
|
||||
fill: true,
|
||||
},
|
||||
];
|
||||
if (hasAlt) {
|
||||
datasets.push({
|
||||
label: 'Altitude above seafloor (m)',
|
||||
data: t_s.map((t, i) => ({ x: t, y: altitude_m[i] })),
|
||||
borderColor: '#44ffaa',
|
||||
backgroundColor: 'transparent',
|
||||
borderWidth: 1.5,
|
||||
pointRadius: 0,
|
||||
});
|
||||
}
|
||||
new Chart(document.getElementById('depth-chart'), {
|
||||
type: 'line',
|
||||
data: { datasets },
|
||||
options: {
|
||||
animation: false,
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
parsing: false,
|
||||
plugins: {
|
||||
legend: { labels: { color: '#889', font: { size: 10, family: 'Courier New' } } },
|
||||
tooltip: { enabled: false },
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
type: 'linear',
|
||||
title: { display: true, text: 'Time (s)', color: '#445' },
|
||||
ticks: { color: '#445', font: { size: 9 } },
|
||||
grid: { color: '#111' },
|
||||
},
|
||||
y: {
|
||||
title: { display: true, text: 'm', color: '#445' },
|
||||
ticks: { color: '#445', font: { size: 9 } },
|
||||
grid: { color: '#111' },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
document.getElementById('status').textContent = `Error: ${err}`;
|
||||
@@ -164,9 +214,9 @@ document.querySelectorAll('[data-layer]').forEach(cb => {
|
||||
|
||||
// ── Resize ────────────────────────────────────────────────────────────────────
|
||||
window.addEventListener('resize', () => {
|
||||
camera.aspect = innerWidth / innerHeight;
|
||||
camera.aspect = viewerEl.clientWidth / viewerEl.clientHeight;
|
||||
camera.updateProjectionMatrix();
|
||||
renderer.setSize(innerWidth, innerHeight);
|
||||
renderer.setSize(viewerEl.clientWidth, viewerEl.clientHeight);
|
||||
});
|
||||
|
||||
// ── Render loop ───────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -5,51 +5,71 @@
|
||||
<title>COSMA NAV — Trajectory Viewer</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { background: #06060f; color: #ccc; font-family: 'Courier New', monospace; overflow: hidden; }
|
||||
canvas { display: block; }
|
||||
body { background: #06060f; color: #ccc; font-family: 'Courier New', monospace;
|
||||
display: flex; flex-direction: column; height: 100vh; overflow: hidden; }
|
||||
|
||||
#viewer { position: relative; flex: 0 0 62vh; }
|
||||
#viewer canvas { display: block; width: 100% !important; height: 100% !important; }
|
||||
|
||||
#legend {
|
||||
position: fixed; top: 16px; left: 16px;
|
||||
background: rgba(4,4,16,0.85); padding: 14px 18px;
|
||||
position: absolute; top: 14px; left: 14px;
|
||||
background: rgba(4,4,16,0.88); padding: 12px 16px;
|
||||
border-radius: 8px; border: 1px solid #223;
|
||||
font-size: 12px; min-width: 180px; backdrop-filter: blur(4px);
|
||||
}
|
||||
#legend h3 { color: #8af; margin-bottom: 10px; font-size: 13px; letter-spacing: 1px; }
|
||||
#legend label { display: flex; align-items: center; gap: 8px; cursor: pointer; margin: 5px 0; user-select: none; }
|
||||
.swatch { width: 10px; height: 10px; border-radius: 2px; flex-shrink: 0; }
|
||||
#info {
|
||||
position: fixed; bottom: 16px; left: 16px;
|
||||
background: rgba(4,4,16,0.85); padding: 8px 14px;
|
||||
border-radius: 6px; font-size: 11px; color: #89a;
|
||||
backdrop-filter: blur(4px); border: 1px solid #223;
|
||||
font-size: 11px; min-width: 170px; backdrop-filter: blur(4px); z-index: 10;
|
||||
}
|
||||
#legend h3 { color: #8af; margin-bottom: 8px; font-size: 12px; letter-spacing: 1px; }
|
||||
#legend label { display: flex; align-items: center; gap: 7px; cursor: pointer; margin: 4px 0; user-select: none; }
|
||||
.swatch { width: 9px; height: 9px; border-radius: 2px; flex-shrink: 0; }
|
||||
|
||||
#status {
|
||||
position: fixed; top: 16px; right: 16px;
|
||||
background: rgba(4,4,16,0.85); padding: 8px 14px;
|
||||
border-radius: 6px; font-size: 11px; color: #67d;
|
||||
backdrop-filter: blur(4px); border: 1px solid #223;
|
||||
position: absolute; top: 14px; right: 14px;
|
||||
background: rgba(4,4,16,0.88); padding: 6px 12px;
|
||||
border-radius: 6px; font-size: 10px; color: #67d;
|
||||
backdrop-filter: blur(4px); border: 1px solid #223; z-index: 10;
|
||||
}
|
||||
#info {
|
||||
position: absolute; bottom: 8px; left: 14px;
|
||||
background: rgba(4,4,16,0.75); padding: 5px 10px;
|
||||
border-radius: 5px; font-size: 10px; color: #89a; z-index: 10;
|
||||
}
|
||||
|
||||
#chart-panel {
|
||||
flex: 1; background: #08080f; border-top: 1px solid #1a1a2e;
|
||||
padding: 10px 16px 8px; display: flex; flex-direction: column;
|
||||
}
|
||||
#chart-title {
|
||||
font-size: 10px; color: #556; letter-spacing: 1px; text-transform: uppercase;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
#depth-chart { flex: 1; width: 100%; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="legend">
|
||||
<h3>COSMA NAV</h3>
|
||||
<label><input type="checkbox" checked data-layer="usv_gps">
|
||||
<span class="swatch" style="background:#3a9fff"></span>USV GPS</label>
|
||||
<label><input type="checkbox" checked data-layer="auv_depth">
|
||||
<span class="swatch" style="background:#44ffaa"></span>AUV depth</label>
|
||||
<label><input type="checkbox" checked data-layer="usbl">
|
||||
<span class="swatch" style="background:#ff6b35"></span>USBL fixes</label>
|
||||
<label><input type="checkbox" checked data-layer="fused">
|
||||
<span class="swatch" style="background:#ffffff"></span>Fused world</label>
|
||||
<label><input type="checkbox" checked data-layer="lingbot_local">
|
||||
<span class="swatch" style="background:#ffd700"></span>lingbot local</label>
|
||||
<label><input type="checkbox" checked data-layer="frustums">
|
||||
<span class="swatch" style="background:#ffd70066"></span>Frustums</label>
|
||||
<label><input type="checkbox" checked data-layer="ply">
|
||||
<span class="swatch" style="background:#556"></span>Point cloud</label>
|
||||
|
||||
<div id="viewer">
|
||||
<div id="legend">
|
||||
<h3>COSMA NAV</h3>
|
||||
<label><input type="checkbox" checked data-layer="usv_gps">
|
||||
<span class="swatch" style="background:#3a9fff"></span>USV GPS</label>
|
||||
<label><input type="checkbox" checked data-layer="auv_usbl">
|
||||
<span class="swatch" style="background:#44ffaa"></span>AUV (USBL)</label>
|
||||
<label><input type="checkbox" checked data-layer="fused">
|
||||
<span class="swatch" style="background:#ffffff"></span>Fused world</label>
|
||||
<label><input type="checkbox" checked data-layer="lingbot_local">
|
||||
<span class="swatch" style="background:#ffd700"></span>lingbot local</label>
|
||||
<label><input type="checkbox" checked data-layer="frustums">
|
||||
<span class="swatch" style="background:#ffd70066"></span>Frustums</label>
|
||||
<label><input type="checkbox" checked data-layer="ply">
|
||||
<span class="swatch" style="background:#556"></span>Point cloud</label>
|
||||
</div>
|
||||
<div id="status">Loading…</div>
|
||||
<div id="info">Orbit: drag · Zoom: scroll · Pan: right-drag</div>
|
||||
</div>
|
||||
|
||||
<div id="chart-panel">
|
||||
<div id="chart-title">AUV — Depth & Altitude Profile</div>
|
||||
<canvas id="depth-chart"></canvas>
|
||||
</div>
|
||||
<div id="status">Loading…</div>
|
||||
<div id="info">Orbit: drag · Zoom: scroll · Pan: right-drag</div>
|
||||
|
||||
<script type="importmap">
|
||||
{"imports":{
|
||||
@@ -57,6 +77,7 @@
|
||||
"three/addons/":"https://cdn.jsdelivr.net/npm/three@0.160/examples/jsm/"
|
||||
}}
|
||||
</script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4/dist/chart.umd.min.js"></script>
|
||||
<script type="module" src="/static/js/scene.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user