diff --git a/src/web/templates/index.html b/src/web/templates/index.html index 5b516dd..102970e 100644 --- a/src/web/templates/index.html +++ b/src/web/templates/index.html @@ -661,98 +661,54 @@ function addStereocameras(scene) { var deg2rad = Math.PI / 180; var halfFov = hfov / 2; - function drawCourtCoverage(cx, angleDeg, color) { - // Center direction: +Y (90°) rotated by angleDeg - var centerAngle = 90 + angleDeg; // +angle = toward +X, -angle = toward -X - var leftAngle = centerAngle + halfFov; - var rightAngle = centerAngle - halfFov; + // Rasterize coverage onto a grid, then draw colored quads + var gridRes = 0.2; // 20cm per cell + var cols = Math.ceil((courtMaxX - courtMinX) / gridRes); + var rows = Math.ceil((courtMaxY - courtMinY) / gridRes); + // 0 = no coverage, 1 = cam0 only, 2 = cam1 only, 3 = both + var grid = new Array(cols * rows).fill(0); - // Cast ray from camera ground pos, clip to court rect - var ox = cx, oy = baseY; + function markCoverage(cx, angleDeg, bit) { + var centerAngle = (90 + angleDeg) * deg2rad; + var halfFovRad = halfFov * deg2rad; + var leftAngle = centerAngle + halfFovRad; + var rightAngle = centerAngle - halfFovRad; - function rayCourtIntersect(angleDeg2) { - var rad = angleDeg2 * deg2rad; - var dx = Math.cos(rad); - var dy = Math.sin(rad); - if (Math.abs(dx) < 1e-9 && Math.abs(dy) < 1e-9) return null; - - var tMin = 0.01, tMax = 50; - // Clip to court Y bounds - if (dy > 1e-9) { - tMax = Math.min(tMax, (courtMaxY - oy) / dy); - } else if (dy < -1e-9) { - tMax = Math.min(tMax, (courtMinY - oy) / dy); + for (var r = 0; r < rows; r++) { + for (var c = 0; c < cols; c++) { + var cellX = courtMinX + (c + 0.5) * gridRes; + var cellY = courtMinY + (r + 0.5) * gridRes; + var dx = cellX - cx; + var dy = cellY - baseY; + var angle = Math.atan2(dy, dx); + if (angle >= rightAngle && angle <= leftAngle) { + grid[r * cols + c] |= bit; + } } - // Clip to court X bounds - if (dx > 1e-9) { - var tx = (courtMaxX - ox) / dx; - tMax = Math.min(tMax, tx); - } else if (dx < -1e-9) { - var tx = (courtMinX - ox) / dx; - tMax = Math.min(tMax, tx); - } - // Must enter court (Y >= 0) - if (dy > 1e-9) { - tMin = Math.max(tMin, (courtMinY - oy) / dy); - } else if (dy <= 0) { - return null; // ray goes away from court - } - - if (tMax <= tMin) return null; - return new THREE.Vector3(ox + dx * tMax, oy + dy * tMax, 0.02); - } - - // Build coverage polygon on court surface - var points = []; - var origin = new THREE.Vector3(ox, Math.max(oy, courtMinY), 0.02); - var steps = 48; - for (var i = 0; i <= steps; i++) { - var a = rightAngle + (leftAngle - rightAngle) * (i / steps); - var pt = rayCourtIntersect(a); - if (pt) { - // Clamp to court bounds - pt.x = Math.max(courtMinX, Math.min(courtMaxX, pt.x)); - pt.y = Math.max(courtMinY, Math.min(courtMaxY, pt.y)); - points.push(pt); - } - } - - if (points.length < 2) return; - - // Draw coverage edge line on court - var edgeGeo = new THREE.BufferGeometry().setFromPoints(points); - scene.add(new THREE.Line(edgeGeo, new THREE.LineBasicMaterial({ color: color, transparent: true, opacity: 0.7 }))); - - // Fill coverage area as triangle fan from camera ground position - var fanOrigin = new THREE.Vector3(ox, courtMinY, 0.02); - for (var i = 0; i < points.length - 1; i++) { - var triGeo = new THREE.BufferGeometry().setFromPoints([ - fanOrigin, points[i], points[i + 1] - ]); - scene.add(new THREE.Mesh(triGeo, new THREE.MeshBasicMaterial({ - color: color, transparent: true, opacity: 0.4, side: THREE.DoubleSide - }))); - } - - // Center line on court - var centerPt = rayCourtIntersect(centerAngle); - if (centerPt) { - centerPt.x = Math.max(courtMinX, Math.min(courtMaxX, centerPt.x)); - centerPt.y = Math.max(courtMinY, Math.min(courtMaxY, centerPt.y)); - var clGeo = new THREE.BufferGeometry().setFromPoints([ - new THREE.Vector3(ox, courtMinY, 0.02), centerPt - ]); - scene.add(new THREE.Line(clGeo, new THREE.LineDashedMaterial({ - color: color, dashSize: 0.2, gapSize: 0.1 - }))); - scene.children[scene.children.length - 1].computeLineDistances(); } } - // cam0: rotated -28° (looks slightly toward -X) - drawCourtCoverage(cam0x, -camAngle, 0x44aaff); - // cam1: rotated +28° (looks slightly toward +X) - drawCourtCoverage(cam1x, camAngle, 0xff44aa); + markCoverage(cam0x, -camAngle, 1); + markCoverage(cam1x, camAngle, 2); + + // Colors: cam0 only = blue, cam1 only = pink, overlap = purple + var colors = { 1: 0x4488ff, 2: 0xff44aa, 3: 0xaa44ff }; + var opacities = { 1: 0.35, 2: 0.35, 3: 0.5 }; + + for (var r = 0; r < rows; r++) { + for (var c = 0; c < cols; c++) { + var val = grid[r * cols + c]; + if (val === 0) continue; + var x1 = courtMinX + c * gridRes; + var y1 = courtMinY + r * gridRes; + var quad = new THREE.PlaneGeometry(gridRes, gridRes); + var mesh = new THREE.Mesh(quad, new THREE.MeshBasicMaterial({ + color: colors[val], transparent: true, opacity: opacities[val], side: THREE.DoubleSide + })); + mesh.position.set(x1 + gridRes / 2, y1 + gridRes / 2, 0.015); + scene.add(mesh); + } + } } // ===================== Draw court lines =====================