Прямоугольники проверки попадания

Я работаю над проектом, в котором у меня есть несколько прямоугольников, и я хочу, чтобы для каждого из них был эффект наведения. Теперь я знаю, что могу просто захватить сообщение WM_MOUSEMOVE и перебрать каждый прямоугольник. Но что, если у меня много прямоугольников (если 50 - много).
Я могу ошибаться, но не стал бы повторять такое количество и проверять каждый раз каждый раз, когда движение мыши немного замедляет работу приложения?

Затем я начал задаваться вопросом, как это делает операционная система (например, Windows). Прямо сейчас на моем экране около 100+ вещей, и все они имеют какую-то анимацию, когда я наводю на них курсор. И я не думаю, что окна перебирают их все каждый раз, когда мышь перемещает пиксель.

В основном:
1. Как я могу определить, над каким прямоугольником находится моя мышь, если у меня есть около 50 прямоугольников, с учетом производительности.
2. Как окна это делают? (Мне больше любопытно, но если не сложно, может, я смогу реализовать что-то подобное в своей собственной программе?)

Да, и все они прямоугольники, они не будут вращаться или что-то в этом роде.


person Josh    schedule 02.03.2012    source источник
comment
Используйте PtInRect. Если есть проблема с эффективностью, профилируйте и измените ее.   -  person Mike Kwan    schedule 02.03.2012


Ответы (5)


Я бы не стал сильно беспокоиться о производительности, пока не станет ясно, что фрагмент кода создает настоящее узкое место. Предположим, что у вас есть такое узкое место, и измерим производительность следующего кода (он на C #, но я почти уверен, что C ++ не будет медленнее):

public class Rectangle
{
    public int X { get; set; }
    public int Y { get; set; }
    public int W { get; set; }
    public int H { get; set; }

    public bool HitTest(int x, int y)
    {
        return x >= X && x < X + W && y >= Y && y < Y + H ? true : false;
    }
}

Нас интересует производительность метода HitTest(), поэтому давайте ее измерим!

void PerformanceTest()
{
    const int Iterations = 1000000;
    Random rnd = new Random();
    var rectangles = Enumerable.Range(1, 50).Select(
            r => new Rectangle {
                X = rnd.Next(1000),
                Y = rnd.Next(1000),
                W = rnd.Next(1000),
                H = rnd.Next(1000)}).ToList();

    Stopwatch sw = new Stopwatch();
    sw.Start();
    for (int i = 0; i < Iterations; i++)
    {
        rectangles.ForEach(r => r.HitTest(500, 500));
    }
    sw.Stop();

    Console.WriteLine("Elapsed time: {0}ms. ({1}us per one iteration)",
        sw.ElapsedMilliseconds,
        (float)sw.ElapsedMilliseconds * 1000 / Iterations);
}

На моем ПК напечатан приведенный выше код:

Затраченное время: 701 мс. (0,701 мкс за одну итерацию)

Как видите, проверка 50 прямоугольников занимает менее одной микросекунды. Вы действительно думаете, что это слишком долго по сравнению со временем, необходимым для создания причудливых эффектов наведения и всего остального, что делает ваша программа? Конечно, только вы можете ответить на этот вопрос.

Но мораль моей истории такова: не пытайтесь выполнить предварительную оптимизацию и не тратьте время на попытки решить проблему, которая может не существовать вовсе.

person Igor Korkhov    schedule 02.03.2012

Не думайте о производительности. Если да, то измерьте!

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

Если ваши «прямоугольники» представляют собой элементы управления Windows (а они должны), вы можете установить прослушиватель мыши для каждого элемента управления, чтобы правый обработчик автоматически вызывался окнами.

person vulkanino    schedule 02.03.2012

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

Я предполагаю, что Windows делает то же самое. Очевидно, что не нужно тестировать дочерние окна, если указатель мыши не находится в родительском, и даже в самых плохо спроектированных диалогах редко отображается более сотни элементов управления одновременно. Элементы управления, которые имеют множество областей проверки попадания (например, ListView, сетки), оптимизируют собственное тестирование попадания.

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

person arx    schedule 03.03.2012

Другие вопросы здесь не ответили на вашу часть 2, поэтому я попробую:

2. How does windows do this? (I'm more curious then anything, but if it's not to complex, maybe I could implement something similar in my own program?)

Следует понимать, что даже если у вас открыты десятки окон, в каждом из которых много панелей инструментов, в каждой много элементов и т. Д., Каждый раз, когда вы перемещаете мышь, окнам не нужно проверять все < / em>.

Windows в основном имеет двухуровневую структуру: есть HWND, с помощью которых сама Windows управляет разделением пространства на рабочем столе; и обычно в каждом HWND есть элемент управления, который управляет своим собственным пространством внутри этого HWND: список, управляющий своими собственными элементами списка, элемент управления вкладками, управляющий своими собственными вкладками, элемент управления HTML, управляющий собственным макетом HTML-страницы, и так далее. (Или, в вашем случае, код, управляющий примерно 50 прямоугольниками.)

Когда мышь перемещается, Windows сначала определяет правильный HWND для отправки этого WM_MOUSEMOVE. И делает это путем обхода HWND. HWND хранятся в виде дерева, представляющего сдерживание, и порядок между братьями и сестрами, представляющий Z-порядок, поэтому Windows может выполнить простой спуск в глубину в это дерево, чтобы найти самый нижний HWND в любой заданной точке. Если вы запустите приложение Spy ++, вы сможете сами увидеть, как выглядит это дерево HWND. Обратите внимание, что Windows не выполняет полного исчерпывающего обхода: при обходе окон приложений верхнего уровня, например, чтобы узнать, в каком приложении находится точка, как только Windows находит первый HWND верхнего уровня, содержащий точку, он углубится в это, полностью игнорируя все другие приложения, которые находятся ниже / после него, и все элементы управления в них. Это ключ, который означает, что окнам нужно пройти только относительно небольшое количество HWND, даже если на экране сразу много видимых.

Как только Windows определяет правильный HWND, она отправляет ему соответствующее сообщение (WM_NCHITTEST, WM_MOUSEMOVE и т. Д.), И затем этот элемент управления должен делать то же самое для своего собственного содержимого. Для списка, содержащего элементы фиксированного размера, определение элемента в конкретной точке может быть таким же простым, как операция деления; или для элемента управления HTML, элемент управления может иметь свой собственный эквивалент «дереву макета», который он может использовать для быстрого перехода и быстрого определения элемента в этой точке. В вашем случае цикл по списку прямоугольников может быть прекрасным.

Это несколько упрощенная версия: она немного сложнее, чем указано выше - например. windows не только для проверки «точка в прямоугольнике», есть и другие проверки, позволяющие создавать прозрачные окна нечетной формы (а также невидимые и отключенные окна); но применима основная идея спуска по дереву.

Другой важный момент, о котором следует помнить, заключается в том, что все это происходит довольно быстро: перемещение мыши происходит в «человеческое время», и современный ЦП может выполнять множество операций за то время, которое требуется мыши, чтобы переместить пару пикселей на экран. И, наконец, обратите внимание, что когда вы перемещаете мышь из точки A в точку B на экране, мышь не всегда проходит через каждый пиксель между ними, особенно если вы перемещаете мышь быстро.

person BrendanMcK    schedule 04.03.2012

Я считаю, что у вас есть одна совершенно ненужная «ветвь» в ответе (которая в вашем тесте приводит к дополнительному миллиону операций). В вашем HitTest вы заканчиваете:

return ... ? true : false;

«? True: false» является избыточным, поскольку выражение уже является «истинным» или «ложным». Когда я пытаюсь создать сверхэффективный код, я всегда думаю о выполняемых «операциях» ...

PS: также такие вещи, как ++ var по сравнению с var ++, могут оказать достойное влияние на производительность в зависимости от того, как он используется в коде (поскольку оптимизаторы ловят некоторые из них и исправляют их за вас ...

PPS: пока я нахожусь ... также никогда не помещайте выражение или вызов метода в свои циклы, если цикл не изменяет результаты выражения, например: for (int i = 0; i ‹getWidth (); ++ i) .. .Если бы это зациклилось миллион раз, ваш метод был бы вызван миллион раз :)

person Joe    schedule 31.03.2012