如何在 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}
}
算法
- 执行命中测试。
- 如果发现命中,请考虑实际碰撞可能发生在先前位置和当前位置之间的任何位置(在
delta
上)。
- 后退
delta = delta / 2
并执行命中测试。
- 如果仍然命中,重复步骤3。
- 如果找不到命中,则向前
delta = delta / 2
- 如果还是不中,重复第5步
- 如果命中,如果第 8 步允许,请重复第 3 步。
- 如果步长 (
delta
) 太小或过程超过最大迭代限制,中断并 return 可能的最佳碰撞点(提供最佳碰撞点的项目的点)
进一步改进
此解决方案基于以下问题:检测命中还不够,因为检测到命中时可能为时已晚,实际碰撞可能发生在两者之间的任何地方由于命中检测间隔,先前和当前点(在 delta
上)。
通过在检测到第一个命中 后根据适当的计算 开始非常准确的 "guess" 尝试,可以改进这种试错法。
我们可以用2
一次性计算出1
(实际碰撞点)。它似乎是 3
到 4
最近的点:
如果计算正确,只需2个循环就可以检测到一个碰撞点:第一个是检测命中(如#2
所示),第二个是验证。如果不正确,将采用反复试验的方法。
您可以从移动点的位置找到路径上的 nearest point,而不是试图找到直接交叉点。
如果点与最近点之间的距离低于阈值(等于 Path
的 strokeWidth
和点的半径),那么您可以认为它是有效的命中.
不过有一个问题:路径的 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
属性来完成
这是我的意图的简化形式: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}
}
算法
- 执行命中测试。
- 如果发现命中,请考虑实际碰撞可能发生在先前位置和当前位置之间的任何位置(在
delta
上)。 - 后退
delta = delta / 2
并执行命中测试。 - 如果仍然命中,重复步骤3。
- 如果找不到命中,则向前
delta = delta / 2
- 如果还是不中,重复第5步
- 如果命中,如果第 8 步允许,请重复第 3 步。
- 如果步长 (
delta
) 太小或过程超过最大迭代限制,中断并 return 可能的最佳碰撞点(提供最佳碰撞点的项目的点)
进一步改进
此解决方案基于以下问题:检测命中还不够,因为检测到命中时可能为时已晚,实际碰撞可能发生在两者之间的任何地方由于命中检测间隔,先前和当前点(在 delta
上)。
通过在检测到第一个命中 后根据适当的计算 开始非常准确的 "guess" 尝试,可以改进这种试错法。
我们可以用2
一次性计算出1
(实际碰撞点)。它似乎是 3
到 4
最近的点:
如果计算正确,只需2个循环就可以检测到一个碰撞点:第一个是检测命中(如#2
所示),第二个是验证。如果不正确,将采用反复试验的方法。
您可以从移动点的位置找到路径上的 nearest point,而不是试图找到直接交叉点。
如果点与最近点之间的距离低于阈值(等于 Path
的 strokeWidth
和点的半径),那么您可以认为它是有效的命中.
不过有一个问题:路径的 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
属性来完成