diff --git a/src/web/templates/index.html b/src/web/templates/index.html index 722e5bd..0bc8299 100644 --- a/src/web/templates/index.html +++ b/src/web/templates/index.html @@ -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; + } + @@ -324,28 +387,36 @@ - +
-
-
-
-
Camera 1
-
Camera 0
-
-
Calibration
- +
+
+
+
+
+
+
Not calibrated
- - +
+
+
CAM 0
+ CAM 0 + CAM 0 live +
+
+
CAM 1
+ CAM 1 + CAM 1 live
- -
@@ -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); } }