From 014cc03677a35ee181a6637c2d77d175c3922d89 Mon Sep 17 00:00:00 2001 From: Ruslan Bakiev <572431+veikab@users.noreply.github.com> Date: Sat, 7 Mar 2026 11:44:06 +0700 Subject: [PATCH] =?UTF-8?q?fix:=20smooth=20polygon=20coverage,=20angle=201?= =?UTF-8?q?5=C2=B0,=20overlap=20blends=20to=20purple?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 --- src/web/templates/index.html | 97 ++++++++++++++++++++---------------- 1 file changed, 53 insertions(+), 44 deletions(-) diff --git a/src/web/templates/index.html b/src/web/templates/index.html index 102970e..e489635 100644 --- a/src/web/templates/index.html +++ b/src/web/templates/index.html @@ -631,7 +631,7 @@ function addStereocameras(scene) { // Position: net line (X=6.7), 1m outside court edge (Y=-1), 1m height (Z=1) var baseX = 6.7, baseY = -1, baseZ = 1; var stereoGap = 0.06; // 6cm between cameras - var camAngle = 28; // degrees each camera is rotated outward from straight +Y + var camAngle = 15; // degrees each camera is rotated outward from straight +Y var hfov = 160; // horizontal FOV degrees var cam0x = baseX - stereoGap / 2; @@ -661,54 +661,63 @@ function addStereocameras(scene) { var deg2rad = Math.PI / 180; var halfFov = hfov / 2; - // 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); + function buildCoveragePolygon(cx, angleDeg) { + var centerAngle = 90 + angleDeg; + var leftAngle = centerAngle + halfFov; + var rightAngle = centerAngle - halfFov; + 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 rayToCourtEdge(aDeg) { + var rad = aDeg * deg2rad; + var dx = Math.cos(rad); + var dy = Math.sin(rad); + if (dy <= 0) return null; - 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; - } - } + var tMax = 50; + if (dy > 1e-9) tMax = Math.min(tMax, (courtMaxY - oy) / dy); + if (dx > 1e-9) tMax = Math.min(tMax, (courtMaxX - ox) / dx); + else if (dx < -1e-9) tMax = Math.min(tMax, (courtMinX - ox) / dx); + + var tEnter = (courtMinY - oy) / dy; + if (tMax <= tEnter) return null; + var t = Math.max(tEnter, 0.01); + t = tMax; + var px = ox + dx * t; + var py = oy + dy * t; + px = Math.max(courtMinX, Math.min(courtMaxX, px)); + py = Math.max(courtMinY, Math.min(courtMaxY, py)); + return new THREE.Vector3(px, py, 0.015); + } + + var points = []; + var steps = 64; + for (var i = 0; i <= steps; i++) { + var a = rightAngle + (leftAngle - rightAngle) * (i / steps); + var pt = rayToCourtEdge(a); + if (pt) points.push(pt); + } + return points; + } + + function drawCoverageFan(points, color) { + if (points.length < 2) return; + var origin = new THREE.Vector3(points[0].x, courtMinY, 0.015); + for (var i = 0; i < points.length - 1; i++) { + var triGeo = new THREE.BufferGeometry().setFromPoints([ + origin, points[i], points[i + 1] + ]); + scene.add(new THREE.Mesh(triGeo, new THREE.MeshBasicMaterial({ + color: color, transparent: true, opacity: 0.35, side: THREE.DoubleSide, + depthWrite: false + }))); } } - 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); - } - } + var poly0 = buildCoveragePolygon(cam0x, -camAngle); + var poly1 = buildCoveragePolygon(cam1x, camAngle); + drawCoverageFan(poly0, 0x4488ff); // blue + drawCoverageFan(poly1, 0xff44aa); // pink + // overlap blends to purple naturally } // ===================== Draw court lines =====================