Подтвердить что ты не робот

Три метода обнаружения мыши для холста HTML5, нет адекватных

Я создал библиотеку холста для управления сценами фигур для некоторых рабочих проектов. Каждая фигура представляет собой объект с связанным с ним способом рисования. Во время обновления холста каждая фигура в стеке рисуется. Форма может иметь характерные связанные события мыши, которые все обернуты вокруг собственных событий мыши DOM.

Я нашел некоторые методы в дикой природе для обнаружения мыши на отдельных фигурах, каждый из которых работает, но с некоторыми довольно серьезными оговорками.

  • Прозрачный холст-призрак используется для индивидуальной индивидуальной формы. Затем я сохраняю копию холста призрака с помощью getImageData(). Как вы можете себе представить, это занимает много памяти, когда есть много точек с привязанными событиями мыши (100 кликабельных фигур на холсте 960x800 составляют ~ 300 МБ в памяти).

  • Чтобы обойти проблему памяти, я начал перебирать пиксельные данные и сохранять только адреса в пикселях с ненулевой альфа. Это хорошо работало для уменьшения памяти, но значительно увеличило загрузку процессора. Я повторяю только каждый 4-й индекс (RGBA), и любой пиксельный адрес с ненулевой альфа-памятью сохраняется как хэш-ключ для быстрого поиска во время перемещения мыши. Он по-прежнему перегружает мобильные браузеры и Firefox в Linux в течение 10 секунд.

  • Я читал о методе, где все фигуры будут нарисованы на одном холсте-призраке, используя цвет, чтобы различать, какая форма принадлежит каждому пикселю. Я был очень доволен этой идеей, потому что теоретически она должна иметь возможность различать миллионы форм.

    К сожалению, это нарушается сглаживанием, которое нельзя отключить при большинстве реализаций холста. Каждый нечеткий край создает десятки цветов, которые можно смело игнорировать, кроме того, что они могут смешать/с перекрывающимися краями формы. Последнее, что я хочу сделать, когда кто-то пересекает мышь по границе формы, - это загорать полуслучайные события mouseover для несвязанных фигур, связанных с цветами, которые возникли из смешивания из-за AA.

Я знаю, что это не новая проблема для разработчиков видеоигр и не должно быть быстрых алгоритмов для такого рода вещей. Если кто-то знает об алгоритме, который может разрешить (реалистично) сотни форм, не занимая процессор более чем на несколько секунд или резко увеличивая потребление ОЗУ, я был бы очень благодарен.

Есть две другие темы при обнаружении мыши, оба из которых обсуждают эту тему, но они идут не дальше, чем 3 метода, которые я описываю. Обнаружить наведение определенных точек в HTML-холсте? и холст HTML5 вокруг холста.

EDIT: 2011/10/21

Я тестировал другой метод, который более динамичен и не требует хранения чего-либо, но он искалечен проблемой производительности в Firefox. Метод в основном состоит в том, чтобы перебирать фигуры и: 1) очищать 1x1 пиксель под мышью, 2) рисовать фигуру, 3) получать 1x1 пиксель под мышью. Удивительно, но это очень хорошо работает в Chrome и IE, но, к сожалению, в Firefox.

Очевидно, Chrome и IE могут оптимизировать, если вам нужна только небольшая область пикселей, но Firefox, похоже, не оптимизируется на основе нужной области пикселей. Возможно, внутренне он получает весь холст, а затем возвращает область вашего пикселя.

Код и исходный вывод здесь: http://pastebin.com/aW3xr2eB.

Canvas getImageData() Performance (ms)

4b9b3361

Ответ 1

Если я правильно понял вопрос, вы хотите определить, когда мышь входит/оставляет фигуру на холсте, исправьте?

Если это так, то вы можете использовать простые геометрические вычисления, которые намного проще и быстрее, чем зацикливание над пиксельными данными. У вашего алгоритма рендеринга уже есть список всех видимых фигур, поэтому вы знаете положение, размер и тип каждой фигуры.

Предполагая, что у вас есть какой-то список фигур, похожий на то, что описывает @Benjammmin, вы можете перебирать видимые фигуры и делать проверки точек внутри полигона:

// Track which shape is currently under the mouse cursor, and raise
// mouse enter/leave events
function trackHoverShape(mousePos) {
    var shape;
    for (var i = 0, len = visibleShapes.length; i < len; i++) {
        shape = visibleShapes[i];
        switch (shape.type ) {
            case 'arc':
                if (pointInCircle(mousePos, shape) &&
                    _currentHoverShape !== shape) {
                        raiseEvent(_currentHoverShape, 'mouseleave');
                        _currentHoverShape = shape;
                        raiseEvent(_currentHoverShape, 'mouseenter');
                    return;
                }
                break;
            case 'rect':
                if (pointInRect(mousePos, shape) &&
                    _currentHoverShape !== shape) {
                       raiseEvent(_currentHoverShape, 'mouseleave');
                       _currentHoverShape = shape;
                       raiseEvent(_currentHoverShape, 'mouseenter');
                }
                break;
        }
    }
}

function raiseEvent(shape, eventName) {
    var handler = shape.events[eventName];
    if (handler)
        handler();
}

// Check if the distance between the point and the shape's
// center is greater than the circle radius. (Pythagorean theroem)
function pointInCircle(point, shape) {
    var distX = Math.abs(point.x - shape.center.x),
        distY = Math.abs(point.y - shape.center.y),
        dist = Math.sqrt(distX * distX + distY * distY);
    return dist < shape.radius;
}

Итак, просто вызовите trackHoverShape внутри вашего canvas mousemove, и он будет отслеживать фигуру, находящуюся под мышью.

Надеюсь, это поможет.

Ответ 2

Из комментария:

Лично я бы просто переключился на использование SVG. Это больше, чем это было сделано для. Однако, возможно, стоит посмотреть EaselJSисточник. Там метод Stage.getObjectUnderPoint() и их демо из этого, кажется, работают отлично.

Я закончил поиск источника, и библиотека использует ваш первый подход - отдельный скрытый холст для каждого объекта.

Одна идея, которая пришла на ум, заключалась в том, чтобы попытаться создать какой-то алгоритм, предназначенный для содержания, чтобы обнаруживать сглаженные пиксели и с какими формами они принадлежат. Я быстро отклонил эту идею.

У меня есть еще одна теория. Кажется, что нет способа использовать призрачные полотна, но, возможно, есть способ генерировать их только тогда, когда они необходимы.

Обратите внимание, что следующая идея - это все теоретические и непроверенные. Возможно, я мог упустить что-то, что означало бы, что этот метод не будет работать.

Наряду с рисованием объекта храните метод, в котором вы нарисовали этот объект. Затем, используя метод рисования объекта, вы можете рассчитать грубую ограничительную рамку для этого объекта. Когда вы нажимаете на холст, запустите цикл через все объекты, которые у вас есть на холсте, и извлеките те, которые ограничивающие поля перехватывают точку. Для каждого из этих выделенных объектов нарисуйте их отдельно на холсте призраков, используя ссылку метода для этого объекта. Определите, находится ли мышь над небелым пикселом, очистите холст и повторите.

В качестве примера рассмотрим два объекта. Я буду хранить методы рисования прямоугольника и круга читаемым образом.

  • circ = ['beginPath', ['arc', 75, 75, 10], 'closePath', 'fill']
  • rect = ['beginPath', ['rect', 150, 5, 30, 40], 'closePath', 'fill']

(Возможно, вы захотите минимизировать сохраненные данные или использовать другой синтаксис, например синтаксис SVG)

Когда я рисую эти круги в первый раз, я также буду учитывать значения размеров и использовать их для определения ограничивающего прямоугольника (Примечание: вам нужно будет компенсировать ширину штриха).

  • circ = {left: 65, top: 65, right: 85, bottom: 85}
  • rect = {left: 150, top: 5, right: 180, bottom: 45}

На холсте произошло событие клика. Точка мыши {x: 70, y: 80}

Циклируя через два объекта, мы обнаруживаем, что координаты мыши попадают в пределы окружности. Поэтому мы отмечаем объект круга как возможный кандидат на столкновение.

Анализируя метод рисования кругов, мы можем воссоздать его на холсте призрака, а затем проверить, попадают ли координаты мыши на небелый пиксель.

После определения, действительно ли это происходит или нет, мы можем очистить холст призрака, чтобы подготовиться к тому, чтобы на него были нарисованы все больше объектов.

Как вы можете видеть, это устраняет необходимость хранить 960 x 800 x 100 пикселей и всего 960 x 800 x2 максимум.

Эта идея лучше всего будет реализована как некоторый API для автоматической обработки данных (например, метод рисования, размеры...).