Calibration tab: split layout — 3D left, debug images right
- Split view: 3D scene on left, camera debug images + controls on right - Debug images show detected court quad + computed camera XYZ - Camera positions displayed as colored cones with look-direction lines on 3D scene - Cameras cleared and redrawn on re-calibration (no duplicates) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -293,6 +293,69 @@
|
||||
.info-item .label { color: #666; }
|
||||
.info-item .value { color: #4ecca3; font-weight: 600; margin-left: 4px; }
|
||||
|
||||
/* Calibration split layout */
|
||||
.cal-split {
|
||||
display: flex;
|
||||
height: calc(100vh - 48px);
|
||||
}
|
||||
.cal-left {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.cal-left .viewport-3d {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-top: none;
|
||||
}
|
||||
.cal-right {
|
||||
width: 360px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-left: 1px solid #2a2a4a;
|
||||
background: #0d0d20;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.cal-controls {
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid #2a2a4a;
|
||||
}
|
||||
.cal-debug-images {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding: 8px;
|
||||
}
|
||||
.cal-debug-card {
|
||||
position: relative;
|
||||
}
|
||||
.cal-debug-card img {
|
||||
width: 100%;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #333;
|
||||
display: block;
|
||||
}
|
||||
.cal-debug-card .cal-live {
|
||||
opacity: 0.5;
|
||||
}
|
||||
.cal-debug-card img[src=""],
|
||||
.cal-debug-card img:not([src]) {
|
||||
display: none;
|
||||
}
|
||||
.cal-debug-label {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
left: 4px;
|
||||
background: rgba(0,0,0,0.7);
|
||||
color: #4ecca3;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -324,28 +387,36 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab 2: Calibration — 3D main, cameras small bottom center -->
|
||||
<!-- Tab 2: Calibration — split: 3D left, debug right -->
|
||||
<div class="tab-content" id="tab-calibration">
|
||||
<div class="tab-full">
|
||||
<div class="viewport-3d" id="calibration-3d"></div>
|
||||
<div class="bottom-bar">
|
||||
<div class="bottom-card"><img id="cal-cam1" alt="Camera 1"></div>
|
||||
<div class="bottom-card"><img id="cal-cam0" alt="Camera 0"></div>
|
||||
<div class="cam-card" style="width:auto;min-width:160px">
|
||||
<div class="cc-title">Calibration</div>
|
||||
<button class="btn-calibrate" id="btnCalibrate" onclick="doCalibrate()">Calibrate</button>
|
||||
<div class="cal-split">
|
||||
<div class="cal-left">
|
||||
<div class="viewport-3d" id="calibration-3d"></div>
|
||||
</div>
|
||||
<div class="cal-right">
|
||||
<div class="cal-controls">
|
||||
<button class="btn-calibrate" id="btnCalibrate" onclick="doCalibrate()" style="padding:8px 24px;font-size:14px">Calibrate</button>
|
||||
<div class="calibrate-status" id="calStatus">
|
||||
<span id="calStatusText">Not calibrated</span>
|
||||
</div>
|
||||
<div id="calError" style="color:#ff4444;font-size:8px;word-break:break-all;display:none"></div>
|
||||
<div id="calPositions" style="display:none">
|
||||
<div class="cc-divider"></div>
|
||||
<div class="cc-item" id="calPos0" style="color:#4ecca3;font-size:8px"></div>
|
||||
<div class="cc-item" id="calPos1" style="color:#ff88cc;font-size:8px"></div>
|
||||
<div id="calError" style="color:#ff4444;font-size:11px;word-break:break-all;display:none;margin-top:4px"></div>
|
||||
<div id="calPositions" style="display:none;margin-top:6px;font-size:11px">
|
||||
<div id="calPos0" style="color:#4ecca3"></div>
|
||||
<div id="calPos1" style="color:#ff88cc"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cal-debug-images">
|
||||
<div class="cal-debug-card">
|
||||
<div class="cal-debug-label">CAM 0</div>
|
||||
<img id="calDebugImg0" alt="CAM 0">
|
||||
<img id="cal-cam0" class="cal-live" alt="CAM 0 live">
|
||||
</div>
|
||||
<div class="cal-debug-card">
|
||||
<div class="cal-debug-label">CAM 1</div>
|
||||
<img id="calDebugImg1" alt="CAM 1">
|
||||
<img id="cal-cam1" class="cal-live" alt="CAM 1 live">
|
||||
</div>
|
||||
</div>
|
||||
<div class="bottom-card" id="calDebug0" style="display:none"><img id="calDebugImg0" alt="CAM 0 debug"></div>
|
||||
<div class="bottom-card" id="calDebug1" style="display:none"><img id="calDebugImg1" alt="CAM 1 debug"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -427,12 +498,8 @@ function doCalibrate() {
|
||||
for (var sid in data.result) {
|
||||
var r = data.result[sid];
|
||||
if (r.debug_image) {
|
||||
var dbgEl = document.getElementById('calDebug' + sid);
|
||||
var imgEl = document.getElementById('calDebugImg' + sid);
|
||||
if (dbgEl && imgEl) {
|
||||
imgEl.src = 'data:image/jpeg;base64,' + r.debug_image;
|
||||
dbgEl.style.display = 'block';
|
||||
}
|
||||
if (imgEl) imgEl.src = 'data:image/jpeg;base64,' + r.debug_image;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -510,30 +577,40 @@ function updateCalibrationStatus() {
|
||||
updateCalibrationStatus();
|
||||
setInterval(updateCalibrationStatus, 3000);
|
||||
|
||||
var cameraMeshes = [];
|
||||
|
||||
function addCamerasToScene(camData) {
|
||||
if (!courtSceneInitialized) return;
|
||||
|
||||
// Remove old camera meshes
|
||||
cameraMeshes.forEach(function(m) { courtScene.remove(m); });
|
||||
cameraMeshes = [];
|
||||
|
||||
for (var sid in camData) {
|
||||
var cam = camData[sid];
|
||||
var pos = cam.position;
|
||||
var color = sid === '0' ? 0x44aaff : 0xff44aa;
|
||||
|
||||
// Camera pyramid
|
||||
// Camera body
|
||||
var geo = new THREE.ConeGeometry(0.3, 0.5, 4);
|
||||
var mat = new THREE.MeshBasicMaterial({
|
||||
color: sid === '0' ? 0x44aaff : 0xff44aa,
|
||||
wireframe: true
|
||||
});
|
||||
var mat = new THREE.MeshBasicMaterial({ color: color, wireframe: true });
|
||||
var mesh = new THREE.Mesh(geo, mat);
|
||||
mesh.position.set(pos[0], pos[1], pos[2]);
|
||||
|
||||
// Point cone toward look direction
|
||||
var dir = cam.look_direction;
|
||||
mesh.lookAt(pos[0] + dir[0], pos[1] + dir[1], pos[2] + dir[2]);
|
||||
|
||||
courtScene.add(mesh);
|
||||
cameraMeshes.push(mesh);
|
||||
|
||||
// Label
|
||||
// (Three.js r128 doesn't have CSS2DRenderer built-in, so skip label for now)
|
||||
// Look direction line
|
||||
var lineGeo = new THREE.BufferGeometry().setFromPoints([
|
||||
new THREE.Vector3(pos[0], pos[1], pos[2]),
|
||||
new THREE.Vector3(pos[0] + dir[0] * 3, pos[1] + dir[1] * 3, pos[2] + dir[2] * 3)
|
||||
]);
|
||||
var line = new THREE.Line(lineGeo, new THREE.LineBasicMaterial({ color: color }));
|
||||
courtScene.add(line);
|
||||
cameraMeshes.push(line);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user