文本碰撞检测

时间:2013-09-21 17:54:01

标签: javascript canvas fonts collision-detection

我正在构建一个Web应用程序,使用Canvas在HTML 5 fillText上以不同字体绘制一组字母。用户将单击该画布上的某个位置,我需要检查他们点击了哪个字母(或者如果他们点击了一个字母)。

我想我需要:

  1. 获取每个字母的矢量路径(我不知道如何做到这一点)。
  2. 使用一些简单的碰撞检测算法检查点击点是否在字母路径内。
  3. 有一些简单的功能可以做到这一点,我错过了吗?或者像这样的事情的图书馆?如果没有任何库,我如何获取特定字体的字母路径来自行检查?

    我需要使用字母的实际形状,而不仅仅是它的边界框,因为我不希望用户能够在O的中间点击并注册为点击。< / p>

    这方面的任何提示都将受到赞赏。

2 个答案:

答案 0 :(得分:3)

逻辑

如果不为其提供自定义逻辑,则无法在画布上处理单独的字母。绘制到画布上的所有内容都合并为一堆像素。

不幸的是,您无法将文本添加为​​纯路径,因此您必须检查像素值。否则,您只需将文本添加到新路径,并对每个字母使用isPointInPath方法。

一种方法

我们无法在此处提供完整的解决方案,但这里有一个基础,您可以在此基础上提供基本逻辑来单击画布上的单个字母:

  • 每个字母都存储为对象incl。它的位置,大小,字体和字符,还有一个矩形命中区域(见下文)
  • 使用这些对象定义数组,然后将它们传递给渲染函数
  • 当你注册一个点击迭代数组并测试矩形命中区域,如果在里面检查像素(*)

*)要区分重叠字母,您需要按优先级检查。您还可以将此char渲染到单独的画布上,以便只获得此char的像素。我没有在演示中展示这个,但你会明白这一点。

演示

var ltrs = []; /// stores the letter objects

/// Create some random objects

for(;i < 20; i++) {

    /// build the object
    var o = {char: alpha[((alpha.length - 1) * Math.random())|0],
             x:    ((w - 20) * Math.random())|0,
             y:    ((h - 20) * Math.random())|0,
             size: (50 * Math.random() + 16)|0,
             font: fonts[((fonts.length - 1) * Math.random())|0]};

             /// store other things such as color etc.

    /// store it in array
    ltrs.push(o);
}

然后我们有一些函数来渲染这些字符(参见演示)。

当我们处理点击时,我们遍历对象数组并首先检查边界以检查我们所处的字母(在这里选择一个像素不会使我们能够识别字母):

demo.onclick = function(e) {

    /// adjust mouse position to be relative to canvas
    var rect = demo.getBoundingClientRect(),
        x = e.clientX - rect.left,
        y = e.clientY - rect.top,
        i = 0, o;

    /// iterate
    for(;o = ltrs[i]; i++) {

        /// is in rectangle? "Older" letters has higher priority here...
        if (x > o.x && x < (o.x + o.rect[2]) &&
            y > o.y && y < (o.y + o.rect[3])) {

            /// it is, check if we actually clicked a letter
            /// This is what you would adopt to be on a separate canvas...    
            if (checkPixel(x, y) === true) {
                setLetterObject(o, '#f00')
                return;
            }
        }
    }
}

像素检查是直接的,它在x / y位置拾取单个像素并检查其 alpha 值(如果使用纯色背景,则检查颜色):

function checkPixel(x, y) {
    var data = ctx.getImageData(x, y, 1, 1).data;
    return (data[3] !== 0);
}

<强> CLICK HERE FOR ONLINE DEMO

更新了检查像素功能

此更新的支票能够检查字母,即使它们在同一地区重叠。

我们创建一个单独的画布来绘制字母。这会隔离字母,当我们选择一个像素时,我们只能从该特定字母中获取一个像素。我们的屏幕外画布仅在检查期间设置字母而不是背景的像素,这也与我们的背景颜色无关。开销很小。

function checkPixel(o, x, y) {

    /// create off-screen canvas        
    var oc = document.createElement('canvas'),
        octx = oc.getContext('2d'),
        data,
        oldX = o.x,
        oldY = o.y;

    /// default canvas is 300x150, adjust if letter size is larger *)
    //oc.width = oc.height = 200;

    /// this can be refactored to something better but for demo...
    o.x = 0;
    o.y = 0;

    setLetterObject(octx, o, '#000');

    o.x = oldX;
    o.y = oldY;

    data = octx.getImageData(x - oldX, y - oldY, 1, 1).data;
    return (data[3] !== 0);
}

*)当我们创建画布时,默认大小为300x150。为了避免重新分配新的位图,我们只是保留它,因为已经为它分配了内存,我们只需要从中选择一个像素。如果字母的像素大小大于默认大小,我们当然需要重新分配以使字母适合。

在这个演示中,我们临时覆盖x和y位置。对于生产,您应该启用setLetterObject方法以某种方式覆盖它,因为这将更优雅。但是我会在演示中保留原样,因为最重要的是理解原理。

答案 1 :(得分:0)

我认为最好的选择是实际使用像素,这是你可以做的最准确的事情(记住用户在点击时看到像素,仅此而已)。

由于你不能直接使用颜色(因为可以有许多具有相同颜色的文本对象(也可能是其他具有相同颜色的图元),你可以使用单独的“选择”画布。

基本上,当您在重绘函数的主画布上绘制对象时,您也会在另一个具有完全相同大小的隐藏画布中绘制它们,但是您可以使用每个实体的唯一颜色绘制它们。 因此,您可以在画布上拥有多达1600万个实体(24位),并通过在颜色代码和实体本身之间保持一个映射,立即知道哪一个被点击了。顺便说一下,这种地图通常用于CAD应用程序,以加快采摘速度。

唯一令人讨厌的部分是在画布上绘制时没有可移植的方法来禁用抗锯齿,因此从拾取画布返回的颜色可能不是您使用的颜色之一,或者更糟糕的是,通过单击实体的边界,可能会认为选择了不同的不相关实体。

这应该是一个非常罕见的事件,除非你的显示真的很拥挤,而且选择基本上是随机的。