如何使用 canvas 和 javascript 检测用户在触摸设备上画了一个圆形?

5

我正在使用 Javascript 制作一个七巧板拼图游戏。我需要检测用户是否用手指画了一个圆形(或类似圆形的形状)。我已经成功收集了数百(如果不是数千)个 x 和 y 的点:

 var touchX = event.targetTouches[0].pageX - canvas.offsetLeft;
 var touchY = event.targetTouches[0].pageY - canvas.offsetTop;

然后将每个x和y坐标推入一个数组中:

    touchMoveX.push(touchX);   
    touchMoveY.push(touchY);

然后我循环遍历每个数组并创建两个点:

    for(var i = 0; i < touchMoveX.length; i++)
      {
        for(var l=0; l < touchMoveY.length; l++)
        {
            var xPosition = touchMoveX[i];
            var yPosition = touchMoveY[l];

            var v1x = touchMoveX[i];
            var v2x = touchMoveX[i + 1];
            var v1y = touchMoveY[l];
            var v2y = touchMoveY[l + 1];

然后,我使用以下公式来计算这两个点之间的角度(以度为单位):

 var v1 = {x: v1x, y: v1y}, v2 = {x: v2x, y: v2y},
 angleRad = Math.acos( (v1.x * v2.x + v1.y * v2.y) / 
 (Math.sqrt(v1.x*v1.x + v1.y*v1.y) * Math.sqrt(v2.x*v2.x + v2.y*v2.y) ) ),
 angleDeg = angleRad * 180 / Math.PI;

我会把所有角度加起来,看看它们是否接近于360度。

但是上述代码并不很有效。是否有更好的方法呢?非常感谢。


你有没有考虑过使用图像矩代替拟合圆形?这个想法是一个圆有很多主要矩,但不太有次要矩。可以参考“Hu矩”或http://en.wikipedia.org/wiki/Image_moment。 - tom10
1个回答

5

计算所有点的平均值(给出一个便宜的近似中心),然后检查是否有超过一定百分比的点在某个阈值内。您可以调整这些值以调整精度,直到感觉合适。

编辑:没有考虑到圆可能有多个尺寸,但是您可以添加另一个步骤来计算所有距离的平均值。已针对此进行了调整示例。

var totalAmount = touchMoveX.length;

// sum up all coordinates and divide them by total length 
// the average is a cheap approximation of the center.
var averageX = touchMoveX.reduce( function ( previous, current) {
    return previous + current;
} ) / totalAmount ;
var averageY = touchMoveY.reduce( function ( previous, current) {
    return previous + current;
} ) / totalAmount ;

// compute distance to approximated center from each point
var distances = touchMoveX.map ( function ( x, index ) {
    var y = touchMoveY[index];        
    return Math.sqrt( Math.pow(x - averageX, 2) + Math.pow(y - averageY, 2) );
} );
// average of those distance is 
var averageDistance = distances.reduce ( function ( previous, current ) {
    return previous + current;
} ) / distances.length;

var min = averageDistance * 0.8;
var max = averageDistance * 1.2;
// filter out the ones not inside the min and max boundaries 
var inRange = distances.filter ( function ( d ) {
   return d > min && d < max;
} ).length;

var minPercentInRange = 80;
var percentInRange = inRange.length / totalAmount * 100;
// by the % of points within those boundaries we can guess if it's circle
if( percentInRange > minPercentInRange ) { 
   //it's probably a circle
}

1
谢谢。让我在我的代码中尝试一下,然后我会点击接受答案的复选标记。可能需要几分钟。再次感谢。顺便说一句,代码很棒。 - Ramona Soderlind
@RamonaSoderlind 谢谢 :) 我不能保证代码能够正常工作,如果你发现我漏掉的错误,请随时提出反馈意见。我只是想说明这个概念。 - Winchestro
我认为你比我更高级,因为我真的不理解你的代码。我想我很难从圆周上的多个点实际获取角度。我想我会尝试提出另一个(更好的)问题。再次感谢。 - Ramona Soderlind
@RamonaSoderlind,你是不是对概念或代码的某个特定部分有疑问?因为我的方法在数学上比你的方法简单得多。我很困惑。也许是使用了数组方法而不是for循环?(你可以在这里查找http://devdocs.io/javascript-array/) - Winchestro
在稍作修改后,这个解决方案对我非常完美。在 var percentInRange = inRange.length / totalAmount * 100; 中,从 inRange 中删除 length,因为它已经在几行之前恢复了它。 - G.Mendes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接