feat: editable camera params panel on Court tab with live 3D update
Position Y, Height, Stereo gap, Rotation angle, HFOV — all adjustable in real time, 3D scene updates on change. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -213,6 +213,34 @@
|
|||||||
height: 60px;
|
height: 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Camera params panel */
|
||||||
|
.cam-params {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
padding: 4px 8px;
|
||||||
|
}
|
||||||
|
.param-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
.param-row label {
|
||||||
|
font-size: 10px;
|
||||||
|
color: #888;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.param-row input {
|
||||||
|
width: 60px;
|
||||||
|
padding: 3px 5px;
|
||||||
|
background: #1a1a2e;
|
||||||
|
border: 1px solid #333;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: #4ecca3;
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
/* Event banner */
|
/* Event banner */
|
||||||
.event-banner {
|
.event-banner {
|
||||||
display: none;
|
display: none;
|
||||||
@@ -286,8 +314,30 @@
|
|||||||
<div class="bottom-bar">
|
<div class="bottom-bar">
|
||||||
<div class="cam-thumb"><img id="court-cam1" alt="Camera 1"></div>
|
<div class="cam-thumb"><img id="court-cam1" alt="Camera 1"></div>
|
||||||
<div class="cam-thumb"><img id="court-cam0" alt="Camera 0"></div>
|
<div class="cam-thumb"><img id="court-cam0" alt="Camera 0"></div>
|
||||||
|
<div class="cam-params">
|
||||||
|
<div class="param-row">
|
||||||
|
<label>Pos Y (м от корта)</label>
|
||||||
|
<input type="number" id="paramPosY" value="1" step="0.1" min="0.1" max="5" onchange="updateCamParams()">
|
||||||
|
</div>
|
||||||
|
<div class="param-row">
|
||||||
|
<label>Pos Z (высота м)</label>
|
||||||
|
<input type="number" id="paramPosZ" value="1" step="0.1" min="0.1" max="5" onchange="updateCamParams()">
|
||||||
|
</div>
|
||||||
|
<div class="param-row">
|
||||||
|
<label>Stereo (см)</label>
|
||||||
|
<input type="number" id="paramStereo" value="6" step="1" min="1" max="30" onchange="updateCamParams()">
|
||||||
|
</div>
|
||||||
|
<div class="param-row">
|
||||||
|
<label>Разворот (°)</label>
|
||||||
|
<input type="number" id="paramAngle" value="15" step="1" min="0" max="45" onchange="updateCamParams()">
|
||||||
|
</div>
|
||||||
|
<div class="param-row">
|
||||||
|
<label>HFOV (°)</label>
|
||||||
|
<input type="number" id="paramHfov" value="128" step="1" min="30" max="180" onchange="updateCamParams()">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="sidebar-panel">
|
<div class="sidebar-panel">
|
||||||
<button class="btn-calibrate" id="btnCalibrate" onclick="doCalibrate()">Calibrate Court</button>
|
<button class="btn-calibrate" id="btnCalibrate" onclick="doCalibrate()">Calibrate</button>
|
||||||
<div class="calibrate-status" id="calStatus">
|
<div class="calibrate-status" id="calStatus">
|
||||||
<span id="calStatusText">Not calibrated</span>
|
<span id="calStatusText">Not calibrated</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -507,7 +557,7 @@ function initCourtScene() {
|
|||||||
courtScene.add(new THREE.AmbientLight(0xffffff, 0.8));
|
courtScene.add(new THREE.AmbientLight(0xffffff, 0.8));
|
||||||
|
|
||||||
// Physical camera marker: 1m from net, center, 1m height
|
// Physical camera marker: 1m from net, center, 1m height
|
||||||
addStereocameras(courtScene);
|
addStereocameras(courtScene, getCamParams());
|
||||||
|
|
||||||
// Load existing calibration cameras
|
// Load existing calibration cameras
|
||||||
fetch('/api/calibration/data')
|
fetch('/api/calibration/data')
|
||||||
@@ -608,7 +658,7 @@ function initTrajectoryScene() {
|
|||||||
trajScene.add(new THREE.AmbientLight(0xffffff, 0.8));
|
trajScene.add(new THREE.AmbientLight(0xffffff, 0.8));
|
||||||
|
|
||||||
// Physical camera marker: 1m from net, center, 1m height
|
// Physical camera marker: 1m from net, center, 1m height
|
||||||
addStereocameras(trajScene);
|
addStereocameras(trajScene, getCamParams());
|
||||||
|
|
||||||
function animateTraj() {
|
function animateTraj() {
|
||||||
requestAnimationFrame(animateTraj);
|
requestAnimationFrame(animateTraj);
|
||||||
@@ -627,12 +677,26 @@ function initTrajectoryScene() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ===================== Stereo camera rig with court coverage =====================
|
// ===================== Stereo camera rig with court coverage =====================
|
||||||
function addStereocameras(scene) {
|
// Track camera overlay objects for live update
|
||||||
// Position: net line (X=6.7), 1m outside court edge (Y=-1), 1m height (Z=1)
|
var camOverlayObjects = [];
|
||||||
var baseX = 6.7, baseY = -1, baseZ = 1;
|
|
||||||
var stereoGap = 0.06; // 6cm between cameras
|
function addStereocameras(scene, params) {
|
||||||
var camAngle = 15; // degrees each camera is rotated outward from straight +Y
|
params = params || {};
|
||||||
var hfov = 128; // horizontal FOV degrees (IMX219 160° diagonal, 4:3 sensor)
|
var baseX = 6.7;
|
||||||
|
var baseY = -(params.posY || 1);
|
||||||
|
var baseZ = params.posZ || 1;
|
||||||
|
var stereoGap = (params.stereo || 6) / 100;
|
||||||
|
var camAngle = params.angle || 15;
|
||||||
|
var hfov = params.hfov || 128;
|
||||||
|
|
||||||
|
// Remove old overlay objects
|
||||||
|
camOverlayObjects.forEach(function(obj) { scene.remove(obj); });
|
||||||
|
camOverlayObjects = [];
|
||||||
|
|
||||||
|
function addObj(obj) {
|
||||||
|
scene.add(obj);
|
||||||
|
camOverlayObjects.push(obj);
|
||||||
|
}
|
||||||
|
|
||||||
var cam0x = baseX - stereoGap / 2;
|
var cam0x = baseX - stereoGap / 2;
|
||||||
var cam1x = baseX + stereoGap / 2;
|
var cam1x = baseX + stereoGap / 2;
|
||||||
@@ -644,7 +708,7 @@ function addStereocameras(scene) {
|
|||||||
new THREE.MeshBasicMaterial({ color: color })
|
new THREE.MeshBasicMaterial({ color: color })
|
||||||
);
|
);
|
||||||
body.position.set(cx, baseY, baseZ);
|
body.position.set(cx, baseY, baseZ);
|
||||||
scene.add(body);
|
addObj(body);
|
||||||
}
|
}
|
||||||
drawCamBody(cam0x, 0x44aaff);
|
drawCamBody(cam0x, 0x44aaff);
|
||||||
drawCamBody(cam1x, 0xff44aa);
|
drawCamBody(cam1x, 0xff44aa);
|
||||||
@@ -654,7 +718,7 @@ function addStereocameras(scene) {
|
|||||||
new THREE.Vector3(baseX, baseY, 0),
|
new THREE.Vector3(baseX, baseY, 0),
|
||||||
new THREE.Vector3(baseX, baseY, baseZ)
|
new THREE.Vector3(baseX, baseY, baseZ)
|
||||||
]);
|
]);
|
||||||
scene.add(new THREE.Line(poleGeo, new THREE.LineBasicMaterial({ color: 0x666666 })));
|
addObj(new THREE.Line(poleGeo, new THREE.LineBasicMaterial({ color: 0x666666 })));
|
||||||
|
|
||||||
// Court boundaries for clipping
|
// Court boundaries for clipping
|
||||||
var courtMinX = 0, courtMaxX = 13.4, courtMinY = 0, courtMaxY = 6.1;
|
var courtMinX = 0, courtMaxX = 13.4, courtMinY = 0, courtMaxY = 6.1;
|
||||||
@@ -713,7 +777,7 @@ function addStereocameras(scene) {
|
|||||||
var triGeo = new THREE.BufferGeometry().setFromPoints([
|
var triGeo = new THREE.BufferGeometry().setFromPoints([
|
||||||
camOnCourt, farPoints[i], farPoints[i + 1]
|
camOnCourt, farPoints[i], farPoints[i + 1]
|
||||||
]);
|
]);
|
||||||
scene.add(new THREE.Mesh(triGeo, mat));
|
addObj(new THREE.Mesh(triGeo, mat));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -886,6 +950,23 @@ setInterval(function() {
|
|||||||
}
|
}
|
||||||
}).catch(function() {});
|
}).catch(function() {});
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
|
// ===================== Camera params live update =====================
|
||||||
|
function getCamParams() {
|
||||||
|
return {
|
||||||
|
posY: parseFloat(document.getElementById('paramPosY').value) || 1,
|
||||||
|
posZ: parseFloat(document.getElementById('paramPosZ').value) || 1,
|
||||||
|
stereo: parseFloat(document.getElementById('paramStereo').value) || 6,
|
||||||
|
angle: parseFloat(document.getElementById('paramAngle').value) || 15,
|
||||||
|
hfov: parseFloat(document.getElementById('paramHfov').value) || 128
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCamParams() {
|
||||||
|
var p = getCamParams();
|
||||||
|
if (courtSceneInitialized) addStereocameras(courtScene, p);
|
||||||
|
if (trajSceneInitialized) addStereocameras(trajScene, p);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user