【问题标题】:Text Collision Detection文本碰撞检测
【发布时间】:2013-09-26 23:04:27
【问题描述】:

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

我想我需要:

  1. 获取每个字母的矢量路径(我不知道该怎么做)。
  2. 使用一些简单的碰撞检测算法检查点击点是否在字母路径内。

是否有一些我缺少的简单功能可以做到这一点?或者也许是这样的图书馆?如果没有任何库,我如何获取特定字体的字母的路径以自己进行检查?

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

我们将不胜感激。

【问题讨论】:

  • 你能在使用画布的位置获得像素颜色吗? (我不使用画布。)如果是这样,那将允许人们在大多数情况下使用颜色来区分字母(不包括 相同 颜色的重叠) - 它可以进一步与一个简单的“框”配对bound" 检查是否需要。
  • @user2246674 抱歉,我将绘制的大部分文本都是相同的颜色(黑底白字),我确实需要它来处理重叠字符。仅供参考:单击将“选择”字母(将其颜色更改为“选定”样式)。
  • @GameAlchemist 我同意。我看到像素是与之交互的,但是这两种解决方案的问题是它们要么要求我将我的对象放在单独的画布上,要么处理无法选择字母边界框后面的对象。我可以从字体系列获得的向量中绘制每个字符并测试它们吗?

标签: javascript canvas fonts collision-detection


【解决方案1】:

逻辑

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

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

一种方法

我们无法在 SO 上提供完整的解决方案,但希望您可以在此基础上构建一个基础,以提供单击画布上单个字母的基本逻辑:

  • 每个字母都存储为对象,包括。它的位置、大小、字体和字符,还带有一个矩形点击区域(见下文)
  • 用这些对象定义一个数组,然后将它们传递给渲染函数
  • 当您注册单击时,遍历数组并针对矩形命中区域进行测试,如果在内部检查像素 (*)

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

演示

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 方法以某种方式覆盖它,因为这样会更优雅。但我将在演示中保持原样,因为最重要的是理解原理。

【讨论】:

  • 这很有帮助,但是当我有两个字母部分重叠时,我无法单击后面的一个。第一个的边界框挡住了后面的文字。例如,我需要能够点击一个巨大的O 中的一个小m,即使O 位于顶部。我会考虑checkPixel。您提到将文本添加为​​路径,我认为这就是我想要的。你能举个例子吗?
  • @SuperScript 这就是我通过实现优先级并将要检查的每个字母绘制到单独的画布上的意思,因为这将使两者分离(即使 m 在 O 里面由于像素位于区域内,因此不会触发 O 误报)。不能仅仅因为画布不支持文本而将文本添加为​​路径。它们要么被填充,要么被描边,但不能像使用 f.ex 那样添加文本。矩形()。
  • @SuperScript 请查看更新的小提琴 (jsfiddle.net/AbdiasSoftware/xeQp2) 它现在使用我提到的单独画布渲染的方法,您可以看到您可以单击重叠的字母。
  • 只是想说,当您第一次回答这个问题时,我并不是很喜欢单独画布的想法,但现在它变得非常好。这个项目有点大,你的想法真的很有帮助。
【解决方案2】:

我想说,最好的选择是实际使用像素,顺便说一句,这是您可以做的最准确的事情(请记住,用户在点击时看到的是像素,仅此而已)。

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

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

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

这应该是一个非常罕见的事件,除非你的展示真的很拥挤而且无论如何挑选基本上都是随机的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-09-05
    • 2019-04-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多