如何在 strokeBounds 之间执行碰撞检测

How to perform Collision detection between strokeBounds

这是我的意图的简化形式:Sketch

我只希望当橙色圆圈足够靠近(当灰色圆圈接触)线时警告框变为红色:

但是,目前它可能会发出误报,即。在足够接近之前:

我们如何正确检测碰撞?

您可以使用 Path.getIntersections() or Path.intersects().

我想出了 "trial and error" 方法:Full example

// Returns item.position at the moment of collision
// and the collided object.
function collisionTest(item, curr){
    var prev = item.position;
    var point = curr.clone();
    var hit = null;
    var tolerance = 45;
    var _error = 0.01;
    var firstPass = true;
    var _stopSearching = false;
    var _step = ((curr - prev) / 2).clone();
    var _acceptable_iter_count = 16;
    var _max_iter_count = 100;

    var i; 
    for (i = 0; i < _max_iter_count; i++){
        var _hit = project.hitTest(point, {
            fill: true, 
            stroke: true, 
            segments: true, 
            tolerance: tolerance,
            match: function(hit){
                if (hit.item && hit.item.data && hit.item.data.type === "pointer"){
                    return false
                }
                return true
            }
        })

        if(_hit){
            hit = _hit;
            // hit has happened between prev and curr points 
            // step half way backward
            point -= _step 
            if (_step.length < _error){
                // step is too small, stop trials as soon 
                // as no hit can be found
                _stopSearching = true;
            }
        } else {
            if(firstPass || _stopSearching){
                break;
            } else  {
                // not hit found, but we should 
                // step forward to search for a more 
                // accurate collision point 
                point += _step  
            }
        }
        firstPass = false;
        if(_step.length >= _error * 2 ) {
            _step /= 2
        } else {
            // minimum step length must be error/2
            // in order to save loop count 
            _step = _step.normalize(_error * 0.8)
        }
    }
    if (i > _acceptable_iter_count){
        console.log("found at " + i + ". iteration, step: ", _step.length)
    }
    return {point, hit}
}

算法

  1. 执行命中测试。
  2. 如果发现命中,请考虑实际碰撞可能发生在先前位置和当前位置之间的任何位置(在 delta 上)。
  3. 后退 delta = delta / 2 并执行命中测试。
  4. 如果仍然命中,重复步骤3。
  5. 如果找不到命中,则向前delta = delta / 2
  6. 如果还是不中,重复第5步
  7. 如果命中,如果第 8 步允许,请重复第 3 步。
  8. 如果步长 (delta) 太小或过程超过最大迭代限制,中断并 return 可能的最佳碰撞点(提供最佳碰撞点的项目的点)

进一步改进

此解决方案基于以下问题:检测命中还不够,因为检测到命中时可能为时已晚,实际碰撞可能发生在两者之间的任何地方由于命中检测间隔,先前和当前点(在 delta 上)。

通过在检测到第一个命中 后根据适当的计算 开始非常准确的 "guess" 尝试,可以改进这种试错法。

我们可以用2一次性计算出1(实际碰撞点)。它似乎是 34 最近的点:

如果计算正确,只需2个循环就可以检测到一个碰撞点:第一个是检测命中(如#2所示),第二个是验证。如果不正确,将采用反复试验的方法。

您可以从移动点的位置找到路径上的 nearest point,而不是试图找到直接交叉点。

如果点与最近点之间的距离低于阈值(等于 PathstrokeWidth 和点的半径),那么您可以认为它是有效的命中.

不过有一个问题:路径的 strokeJoin & strokeCap 属性应该是 设置为 round,因为这会导致左右对称的笔划宽度 角落和结局,从而避免假阴性。

这是一个例子:

var path = new Path({
    segments: [
        new Point(100, 200),
        new Point(200, 100),
        new Point(300, 300)
    ],
    strokeWidth: 100,
    // This is important.
    strokeJoin: 'round',
    // This is important.
    strokeCap: 'round',
    strokeColor: 'red',
    selected: true,
    position: view.center
})

var dot = new Path.Circle({
    center: view.center,
    radius: 10,
    fillColor: 'red'
})

function onMouseMove(event) {
    dot.position = event.point

    var nearestPoint = path.getNearestPoint(dot.position)
    var distance = dot.position.subtract(nearestPoint)

    if (distance.length < (dot.bounds.width / 2) + (path.strokeWidth / 2)) {
        path.strokeColor = 'blue'
    } else {
        path.strokeColor = 'red'
    }
}

Sketch.


话虽这么说,最好的方法是等待(或自己实现),Path dilation/offsetting 并找到扩展路径上的交点,因为这将允许您使用任何 strokeJoin/strokeCap设置.

理想情况下,dilation/offsetting 将使用与 Path.

相同的 strokeJoin/strokeCap 属性来完成

有一个JS library that reliably does polygon offsetting可能对你有用。