fix: court/trajectory layout, net orientation, camera position
- Court and Trajectory tabs: 3D viewport full screen, cameras small at bottom center as horizontal thumbnails - Fix net orientation (was perpendicular, now spans court width) - Default camera: center line, 1m from net, 1m height Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -94,34 +94,43 @@
|
|||||||
}
|
}
|
||||||
.viewport-3d canvas { width: 100% !important; height: 100% !important; }
|
.viewport-3d canvas { width: 100% !important; height: 100% !important; }
|
||||||
|
|
||||||
/* Bottom layout: 3D + sidebar panel */
|
/* Full-height tab layout: 3D on top, small bottom bar */
|
||||||
.court-bottom, .traj-bottom {
|
.tab-full {
|
||||||
display: flex;
|
display: flex;
|
||||||
height: calc(100vh - 100px - 30vw);
|
flex-direction: column;
|
||||||
overflow: hidden;
|
height: calc(100vh - 52px);
|
||||||
}
|
}
|
||||||
.court-main, .traj-main {
|
.tab-full .viewport-3d {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
position: relative;
|
height: auto;
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
.court-main .viewport-3d,
|
|
||||||
.traj-main .viewport-3d {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
border-top: none;
|
border-top: none;
|
||||||
}
|
}
|
||||||
|
.bottom-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 6px 8px;
|
||||||
|
background: #0d0d20;
|
||||||
|
border-top: 1px solid #222;
|
||||||
|
}
|
||||||
|
.cam-thumb {
|
||||||
|
width: 200px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.cam-thumb img {
|
||||||
|
width: 100%;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #222;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
/* Sidebar panel (calibration, etc.) */
|
/* Sidebar panel (calibration, etc.) */
|
||||||
.sidebar-panel {
|
.sidebar-panel {
|
||||||
width: 220px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
padding: 10px;
|
padding: 4px 12px;
|
||||||
background: #111128;
|
|
||||||
border-left: 1px solid #222;
|
|
||||||
}
|
}
|
||||||
.btn-calibrate {
|
.btn-calibrate {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -145,16 +154,12 @@
|
|||||||
.calibrate-status .ok { color: #4ecca3; }
|
.calibrate-status .ok { color: #4ecca3; }
|
||||||
.calibrate-status .fail { color: #ff4444; }
|
.calibrate-status .fail { color: #ff4444; }
|
||||||
|
|
||||||
/* VAR panel (sidebar version) */
|
/* VAR panel (bottom bar version) */
|
||||||
.var-panel-sidebar {
|
.var-panel-bottom {
|
||||||
width: 220px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 10px;
|
||||||
padding: 10px;
|
padding: 4px 12px;
|
||||||
background: #111128;
|
|
||||||
border-left: 1px solid #222;
|
|
||||||
}
|
}
|
||||||
.var-indicator {
|
.var-indicator {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -274,55 +279,43 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Tab 2: Court — cameras on top, 3D below -->
|
<!-- Tab 2: Court — 3D main, cameras small bottom center -->
|
||||||
<div class="tab-content" id="tab-court">
|
<div class="tab-content" id="tab-court">
|
||||||
<div class="cameras">
|
<div class="tab-full">
|
||||||
<div class="cam-box">
|
<div class="viewport-3d" id="court-3d"></div>
|
||||||
<img id="court-cam1" alt="Camera 1">
|
<div class="bottom-bar">
|
||||||
</div>
|
<div class="cam-thumb"><img id="court-cam1" alt="Camera 1"></div>
|
||||||
<div class="cam-box">
|
<div class="sidebar-panel">
|
||||||
<img id="court-cam0" alt="Camera 0">
|
<button class="btn-calibrate" id="btnCalibrate" onclick="doCalibrate()">Calibrate Court</button>
|
||||||
</div>
|
<div class="calibrate-status" id="calStatus">
|
||||||
</div>
|
<span id="calStatusText">Not calibrated</span>
|
||||||
<div class="court-bottom">
|
</div>
|
||||||
<div class="court-main">
|
|
||||||
<div class="viewport-3d" id="court-3d"></div>
|
|
||||||
</div>
|
|
||||||
<div class="sidebar-panel">
|
|
||||||
<button class="btn-calibrate" id="btnCalibrate" onclick="doCalibrate()">Calibrate Court</button>
|
|
||||||
<div class="calibrate-status" id="calStatus">
|
|
||||||
<span id="calStatusText">Not calibrated</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="cam-thumb"><img id="court-cam0" alt="Camera 0"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Tab 3: Trajectory — cameras on top, 3D below -->
|
<!-- Tab 3: Trajectory — 3D main, cameras small bottom center -->
|
||||||
<div class="tab-content" id="tab-trajectory">
|
<div class="tab-content" id="tab-trajectory">
|
||||||
<div class="cameras">
|
<div class="tab-full">
|
||||||
<div class="cam-box">
|
<div class="viewport-3d" id="trajectory-3d"></div>
|
||||||
<img id="traj-cam1" alt="Camera 1">
|
<div class="bottom-bar">
|
||||||
</div>
|
<div class="cam-thumb"><img id="traj-cam1" alt="Camera 1"></div>
|
||||||
<div class="cam-box">
|
<div class="var-panel-bottom">
|
||||||
<img id="traj-cam0" alt="Camera 0">
|
<div class="var-indicator">
|
||||||
</div>
|
<div class="var-dot" id="varDot"></div>
|
||||||
</div>
|
<div class="var-label" id="varLabel">VAR</div>
|
||||||
<div class="traj-bottom">
|
</div>
|
||||||
<div class="traj-main">
|
<div class="var-info" id="varInfo">
|
||||||
<div class="viewport-3d" id="trajectory-3d"></div>
|
<div class="var-line" id="varLine">No events</div>
|
||||||
</div>
|
<div class="var-timer" id="varTimer"></div>
|
||||||
<div class="var-panel-sidebar">
|
<div class="var-dist" id="varDist"></div>
|
||||||
<div class="var-indicator">
|
</div>
|
||||||
<div class="var-dot" id="varDot"></div>
|
<img class="var-snapshot" id="varSnapshot" style="display:none">
|
||||||
<div class="var-label" id="varLabel">VAR</div>
|
<div class="var-snapshot empty" id="varSnapshotEmpty">No snapshot</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="var-info" id="varInfo">
|
<div class="cam-thumb"><img id="traj-cam0" alt="Camera 0"></div>
|
||||||
<div class="var-line" id="varLine">No events</div>
|
|
||||||
<div class="var-timer" id="varTimer"></div>
|
|
||||||
<div class="var-dist" id="varDist"></div>
|
|
||||||
</div>
|
|
||||||
<img class="var-snapshot" id="varSnapshot" style="display:none">
|
|
||||||
<div class="var-snapshot empty" id="varSnapshotEmpty">No snapshot</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -476,7 +469,7 @@ function initCourtScene() {
|
|||||||
courtScene.background = new THREE.Color(0x0a0a1a);
|
courtScene.background = new THREE.Color(0x0a0a1a);
|
||||||
|
|
||||||
courtCamera = new THREE.PerspectiveCamera(50, w / h, 0.1, 100);
|
courtCamera = new THREE.PerspectiveCamera(50, w / h, 0.1, 100);
|
||||||
courtCamera.position.set(6.7, -8, 12);
|
courtCamera.position.set(7.7, 3.05, 1);
|
||||||
courtCamera.lookAt(6.7, 3.05, 0);
|
courtCamera.lookAt(6.7, 3.05, 0);
|
||||||
|
|
||||||
courtRenderer = new THREE.WebGLRenderer({ antialias: true });
|
courtRenderer = new THREE.WebGLRenderer({ antialias: true });
|
||||||
@@ -498,7 +491,7 @@ function initCourtScene() {
|
|||||||
|
|
||||||
// Net (wide plane)
|
// Net (wide plane)
|
||||||
var netMat = new THREE.MeshBasicMaterial({ color: 0xffffff, transparent: true, opacity: 0.4, side: THREE.DoubleSide });
|
var netMat = new THREE.MeshBasicMaterial({ color: 0xffffff, transparent: true, opacity: 0.4, side: THREE.DoubleSide });
|
||||||
var netWide = new THREE.PlaneGeometry(6.1, 0.914);
|
var netWide = new THREE.PlaneGeometry(0.914, 6.1);
|
||||||
var netWideMesh = new THREE.Mesh(netWide, netMat);
|
var netWideMesh = new THREE.Mesh(netWide, netMat);
|
||||||
netWideMesh.rotation.y = Math.PI / 2;
|
netWideMesh.rotation.y = Math.PI / 2;
|
||||||
netWideMesh.position.set(6.7, 3.05, 0.457);
|
netWideMesh.position.set(6.7, 3.05, 0.457);
|
||||||
@@ -549,15 +542,15 @@ function initTrajectoryScene() {
|
|||||||
trajScene.background = new THREE.Color(0x0a0a1a);
|
trajScene.background = new THREE.Color(0x0a0a1a);
|
||||||
|
|
||||||
trajCamera = new THREE.PerspectiveCamera(50, w / h, 0.1, 100);
|
trajCamera = new THREE.PerspectiveCamera(50, w / h, 0.1, 100);
|
||||||
trajCamera.position.set(6.7, -10, 8);
|
trajCamera.position.set(7.7, 3.05, 1);
|
||||||
trajCamera.lookAt(6.7, 3.05, 1);
|
trajCamera.lookAt(6.7, 3.05, 0);
|
||||||
|
|
||||||
trajRenderer = new THREE.WebGLRenderer({ antialias: true });
|
trajRenderer = new THREE.WebGLRenderer({ antialias: true });
|
||||||
trajRenderer.setSize(w, h);
|
trajRenderer.setSize(w, h);
|
||||||
container.appendChild(trajRenderer.domElement);
|
container.appendChild(trajRenderer.domElement);
|
||||||
|
|
||||||
var controls = new THREE.OrbitControls(trajCamera, trajRenderer.domElement);
|
var controls = new THREE.OrbitControls(trajCamera, trajRenderer.domElement);
|
||||||
controls.target.set(6.7, 3.05, 1);
|
controls.target.set(6.7, 3.05, 0);
|
||||||
controls.update();
|
controls.update();
|
||||||
|
|
||||||
// Court surface
|
// Court surface
|
||||||
@@ -571,7 +564,7 @@ function initTrajectoryScene() {
|
|||||||
|
|
||||||
// Net
|
// Net
|
||||||
var netMat = new THREE.MeshBasicMaterial({ color: 0xffffff, transparent: true, opacity: 0.3, side: THREE.DoubleSide });
|
var netMat = new THREE.MeshBasicMaterial({ color: 0xffffff, transparent: true, opacity: 0.3, side: THREE.DoubleSide });
|
||||||
var netMesh = new THREE.Mesh(new THREE.PlaneGeometry(6.1, 0.914), netMat);
|
var netMesh = new THREE.Mesh(new THREE.PlaneGeometry(0.914, 6.1), netMat);
|
||||||
netMesh.rotation.y = Math.PI / 2;
|
netMesh.rotation.y = Math.PI / 2;
|
||||||
netMesh.position.set(6.7, 3.05, 0.457);
|
netMesh.position.set(6.7, 3.05, 0.457);
|
||||||
trajScene.add(netMesh);
|
trajScene.add(netMesh);
|
||||||
|
|||||||
Reference in New Issue
Block a user