如何有效地检查鼠标是否位于 HTML5 canvas 中的许多 (120) 个不同区域?

How to check if a mouse is in many (120) different regions in HTML5 canvas efficiently?

我有一个包含 120 个不同点的极坐标图(见图)。我想这样做,如果用户单击或悬停在其中一个点上,则会显示该点的坐标。我有一个名为 pointCoordinates 的数组,它存储每个点的每个 canvas 坐标,如下所示:

[[x1, y1], [x2, y2] ... [x120, y120]]

这是我捕获鼠标坐标的方式(稍后我可能会更改为单击):

document.onmousemove = function(e) {
    var x = e.clientX;
    var y = e.clientY;
}

本来打算用公式判断鼠标是否在某个区域(用距离公式)或者全部简化成一个圆圈。无论哪种方式,这都需要我有 120 个不同的 if 语句来检查这一点。我觉得这是低效的,而且可能很慢。还有其他方法吗?

编辑:

为了提供更多信息,这些点不可拖动。我计划在单击的点附近显示类似工具提示的内容,其中将显示该点的极坐标。

编辑 2:

使用下面发布的代码并在地图上的 "clickable" 点绘制一个矩形后,我得到了这张图片。我不希望点击检测是完美的,但这在 pi/3 之后还有很长的路要走。任何想法如何解决这一问题?我使用此代码生成黑点:

for(var x = 0; x < WIDTH*2/3; x++){
        for(var y = 0; y < HEIGHT; y++){
          var mp = realToPolar(x, y);//converts canvas x and y into polar
          if(checkRadialDistance(mp[0], mp[1])){ //returns true if in bounds
            ctx.fillRect(x, y, 1, 1);
          }
        }
      }

使用常数仍然会生成相同的图案,只是粗细不同。 checkRadialDistance 只是内部调用 checkrt 的重命名的 checkr 函数。

JSBIN 请记住,屏幕的宽度必须大于高度才能正常工作。

mt-rt生成的图像。后来我做了一个小的编辑,所以当 theta = 0 时整个圆都被覆盖了。

编辑:我的(接受的)答案很糟糕。这更正了它:

这假设 r 为 1 到 5。将鼠标笛卡尔 mx,my 转换为极坐标 mr,mt。首先检查 mr 是否接近 5 个半径中的 1 个。功能检查器就是这样做的。如果接近,则检查 mt 是否接近 24 theta 中的 1。函数 checkt 就是这样做的。一个复杂的问题是 atan2 函数在点所在的 pi 弧度处不连续,因此在没有点的 -pi/24 弧度处不连续。

A "close" 值为 pi/24,因为 r=1 处两个相邻点之间的弧距将为 pi/12。

var del = 1*Math.PI/24*.7; // for example

function xy2rt(xy) { // to polar cordinates
  var rt = [];
  rt.push(Math.sqrt(xy[0]*xy[0]+xy[1]*xy[1])); // r
  var zatan = Math.atan2(xy[1], xy[0]);
  // make the discontinuity at -pi/24
  if (zatan < -Math.PI/24) zatan += 2*Math.PI; 
  rt.push(zatan); // theta
  return rt;
}
function checkr() { // check radial distance
  for (var pr=1; pr<=5; pr+=1) { // 5 radii
    if (Math.abs(mr-pr) < del) { checkt(pr); break; }
  }
}  
function checkt(pr) { // check theta
  var pt;
  for (var ipt=0; ipt<24; ipt+=1) { // 24 thetas
    pt = ipt / 24 * 2 * Math.PI; 
    if (Math.abs(mt-pt) < del/pr) { 
      // is close -- do whatever
      break;
    }
  }
}

我的问题是在检查弧距时,我使用的是 mr 和 pr,而应该只使用 pr。 OP 通过处理 canvas 上的每个像素发现了我的错误,并发现存在问题。我还处理了每个像素,这张图片显示了现在正确的例程。黑色是例程确定像素接近 120 个点之一的地方。

编辑:处理速度更快
有很多 Math.* 函数正在执行。虽然我什么都没有计时,但我想这一定要快得多。
1) 120个点的x,y坐标存储在数组中。
2) 不获取极坐标 mr、mt、pr 和 pt,而是使用向量处理。

这里是arcd的推导,使用向量的弧距。

sint = sin(theta) = (M cross P)/mr/pr (cross product Mouse X Point)  
cost = cos(theta) = (M dot P)/mr/pr (dot product Mouse . Point)  
sint will be used to get arc distance, but sint goes to zero at theta=+-pi as well as theta=0, so:
mdotp will be used to determine if theta is near zero and not +-pi
arcd = pr*theta
arcd = pr*sin(theta) (good approximation for small theta)  
arcd = pr*abs(M cross P)/mr/mp (from above)
if ardd < del, check if mdotp > 0.

这是 load-xy-arrays 和新的 checkr 和 checkt 例程。

  apx=[], apy=[]; // the saved x,y of the 120 points 
function loadapxapy() { // load arrays of px, py
  var itheta, theta
  for (var pr=1; pr<=5; pr+=1) { // 2-dimension arrays
    apx[pr] = []; apy[pr] = []; // 5 arrays, 1 for each pr
    for (itheta=0; itheta<24; itheta+=1) { // 24 x's and y's
      theta = Math.PI*itheta/12; 
      apx[pr][itheta] = pr*Math.cos(theta); 
      apy[pr][itheta] = pr*Math.sin(theta);
    }
  }
}
function checkr() { // check radial distance
  var mr = Math.sqrt(mx*mx+my*my); // mouse r
  for (var pr=1; pr<=5; pr+=1) { // check 1 to 5
   if (Math.abs(mr-pr) < del) { // mouser - pointr
      checkt(mr, pr); // if close, check thetas
    }
  }
}  
function checkt(mr, pr) { // check thetas
  var px, py, sint, mdotp, arcd;
  for (var itheta=0; itheta<24; itheta+=1) { // check 24
    px = apx[pr][itheta]; // get saved x
    py = apy[pr][itheta]; // and y
    // This arcd is derived from vector processing 
    // At least this doesn't use the accursed "atan"!
    sint = Math.abs(mx*py-my*px)/mr/pr; // sine
    arcd = pr*sint; // arc distance
    if (arcd<del) { // arc distance check
      mdotp = (mx*px+my*py); // final check
      if (mdotp > 0) { // to see if theta is near zero and not +-pi
        setpixelxy([mx, my]); // or whatever..
      }
    }
  }
}