Статьи

Обнаружение столкновений на уровне пикселей для преобразованной графики

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


Это последний кусок, который мы попробуем прогамить. Нажмите на шар и крючок, чтобы начать интерактивную демонстрацию.

Обратите внимание, что, несмотря на то, что изображение кокосовой пальмы вращается и трансформируется иным образом, у нас все еще есть пиксельное обнаружение столкновений.


Выполнение обнаружения столкновений на уровне пикселей требует, чтобы графика была приведена к объекту BitmapData . Мы рассмотрели это в предыдущем уроке, используя аналогию с рентгеновским снимком. В этом уроке я объясню этот процесс «рентгеновского снимка».

Рентгеновский процесс.

Предположим, у нас есть кусок графики (диаграмма 1), и мы хотим привести его к объекту BitmapData ; сначала нам нужно определить размеры объекта BitmapData (диаграмма 2). В этом случае это довольно просто, потому что свойства width и height графики обеспечивают это. Затем мы вызываем метод draw() ; Теоретически пиксели, которые по крайней мере наполовину заняты графикой, будут заполнены (диаграмма 3). Этот массив пикселей будет сравниваться с еще одним массивом пикселей из другого графического объекта, чтобы проверить наличие коллизий между ними (диаграмма 4).


Разные пространства.

В Flash IDE используются разные координатные пространства (диаграммы 1 и 2 выше). Я уверен, что каждый читатель испытал бы это — посмотрите мой учебник по аффинным пространствам для более детального просмотра.

В Flash IDE мы рисуем изображение и превращаем его в символ типа MovieClip . Когда мы дважды MovieClip , мы попадаем в другое координатное пространство (диаграмма 3). Отсюда мы можем нажать на метку этапа, чтобы выйти из локального координатного пространства этого графического объекта и достичь координатного пространства этапа. Затем мы можем начать преобразовывать экземпляр MovieClip на сцене (диаграмма 4). Фактически мы можем создать несколько экземпляров MovieClip , каждый из которых имеет разные преобразования.

Разные пространства.

В библиотеке оригинальная графика остается неизменной, несмотря на все изменения, сделанные на ее копиях на сцене. Это рационально, потому что всякий раз, когда мы делаем новую копию MovieClip на сцене, они всегда совпадают с оригинальной копией в библиотеке. Теперь возникает вопрос: «Как Flash фиксирует все преобразования, которые мы сделали с копиями на сцене?» Каждый из них использует свойство MovieClip.transform.matrix для захвата всех ваших преобразований (перемещение, поворот, наклон и т. Д.).

Теперь давайте вернемся туда, где мы остановились. Крайне важно, чтобы мы это понимали, потому что метод draw() в BitmapData ссылается не на графический экземпляр на сцене при выполнении «рентгеновского снимка», а на неизмененный графический источник в библиотеке.

Первый пиксель объекта BitmapData выравнивается с точкой регистрации (красная точка) MovieClip в локальном координатном пространстве (см. Диаграмму 3 в шаге 1), а затем захватывает изображение в пиксельной форме с указанными нами размерами.

Когда дело доходит до проверок hitTest , ActionScript выравнивает этот первый пиксель (верхний левый пиксель) объекта BitmapData с точкой регистрации графического экземпляра на сцене. При этом все пиксели в объекте BitmapData будут отображены в координатное пространство на сцене и получат свои индивидуальные координаты. В дальнейшем проверки могут быть выполнены путем сравнения этих координат между двумя растровыми изображениями, чтобы увидеть, перекрываются ли какие-либо пиксели.

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


Итак, если интересующий рисунок вращается на сцене, как мы выполняем draw() ?

Рентгеновская проблема.

Из приведенной выше диаграммы мы можем ясно увидеть эти проблемы.

схема проблема Описание
1 Размер объекта BitmapData Поскольку ориентация объекта изменилась, требуемое измерение приведения BitmapData больше не может быть удобно взято из свойств width и height графического экземпляра.
2 Ориентация графического источника Экземпляр графического изображения на сцене поворачивается, а экземпляр в библиотеке — нет. Нам нужно сделать снимок преобразованного графического источника из библиотеки.
3 Координата верхнего левого пикселя (начального пикселя) объекта BitmapData Мы не можем выровнять первый пиксель объекта BitmapData с точкой регистрации графического экземпляра. Это будет неверно.

Чтобы решить эти проблемы, мы сначала определим прямоугольник, плотно ограничивающий повернутый графический экземпляр на сцене. ActionScript обеспечивает это с помощью функции getBounds() . Это решит нашу первую проблему. Посмотрите на изображение ниже. Обратите внимание, что есть разница между точкой регистрации графического экземпляра и точкой прямоугольника.

Прямоугольник плотно ограничивающий графический экземпляр.

Я включил презентацию Flash ниже, чтобы показать прямоугольный ограничивающий прямоугольник (красный прямоугольник) и ограничивающий прямоугольник локального пространства (черный прямоугольник)


Далее мы приведем снимок преобразованного графического источника в этот прямоугольник на сцене. Посмотрите на изображение ниже.

Настройте графику в соответствии с.

Мы начнем с выравнивания точки регистрации графического источника с точкой регистрации прямоугольной области (диаграмма 1). Затем мы поворачиваем (диаграмма 2) и смещаем его (диаграмма 3) перед тем, как сделать «рентгеновский» снимок изображения на объект BitmapData (диаграмма 4).

Мы можем сделать это вручную или сделать копию свойства графического экземпляра transform.matrix . При использовании второго подхода мы должны быть осторожны, чтобы не использовать свойство перевода transform.matrix — в противном случае точки регистрации не будут выровнены, как вы видите на диаграмме 1. В обоих случаях нам нужно будет вычислить расстояние x и y чтобы компенсировать.


После этого длинного объяснения, я надеюсь, что легче понять код. Я выделил важные строки и добавил комментарии:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private var coconut:CTree, hk:Hook;
private var bdat1:BitmapData, bdat2:BitmapData;
private var t1:TextField;
private var angle:Number = 45
private var coconutBox:Rectangle;
 
public function Matrix_Bitmap4()
{
    coconut = new CTree();
    coconut.rotation = angle;
    coconut.x = stage.stageWidth * 0.3;
 
    coconutBox = coconut.getBounds(this);
    var coconut_newX:Number = coconut.x — coconutBox.x //get offset x in step 3
    var coconut_newY:Number = coconut.y — coconutBox.y //get offset y in step 3
 
    var m:Matrix = new Matrix();
    m.rotate(angle / 180 * Math.PI);
    //var m:Matrix = coconut.transform.matrix //recommended if many transformation happened.
    //m.tx = 0;
    m.translate(coconut_newX, coconut_newY);
 
    bdat1 = new BitmapData(coconutBox.width, coconutBox.width, true, 0x00000000);
    bdat1.draw(coconut, m);

Кроме того, не забудьте поменять расположение первого пикселя (вверху слева) в объекте BitmapData на расположение прямоугольника

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
private function check(e:Event):void
{
    var closeEnough:Boolean = coconut.hitTestObject(hk)
    if(closeEnough){
        //var point1:Point = new Point(coconut.x, coconut.y);
        //now that we have a different box with different location for starting pixel,
        //we should refer to coconutBox as the starting point
        var point1:Point = new Point(coconutBox.x, coconutBox.y);
 
        var point2:Point = new Point(hk.x, hk.y);
        if (bdat1.hitTest(point1, 1, bdat2, point2, 1)) {
            t1.text = «At least one pixel has collided»
        }
        else {
            t1.text = «No collision»
        }
    }
}

И вот пример работы.


Если интересующая форма, в данном случае кокосовая пальма, постоянно трансформируется (вращение, масштабирование, сжатие, перекос и т. Д.), BitmapData объект BitmapData должен обновляться в каждом кадре, и это потребует некоторой обработки. Также обратите внимание, что я выбрал альтернативный подход, упомянутый в шаге 4. Вот скрипт для обновления рентгеновской копии графики для каждого кадра:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
private function updateBmp():void {
    coconutBox = coconut.getBounds(this);
    var coconut_newX:Number = coconut.x — coconutBox.x //get offset x in step 3
    var coconut_newY:Number = coconut.y — coconutBox.y //get offset y in step 3
 
    //var m:Matrix = new Matrix();
    //m.rotate(angle / 180 * Math.PI);
    var m:Matrix = coconut.transform.matrix //recommended if many transformation happened,
    m.tx = 0;
    m.translate(coconut_newX, coconut_newY);
 
    bdat1 = new BitmapData(coconutBox.width, coconutBox.width, true, 0x00000000);
    b = new Bitmap(bdat1);
    bx = stage.stageWidth * 0.3;
    bdat1.draw(coconut, m);
}

Следующая функция выполняется каждый кадр:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
private function check(e:Event):void
{
    coconut.rotation += angle;
    coconut.scaleX +=0.01
 
    var closeEnough:Boolean = coconut.hitTestObject(hk)
    if(closeEnough){
        updateBmp();
        var point1:Point = new Point(coconutBox.x, coconutBox.y);
 
        var point2:Point = new Point(hk.x, hk.y);
        if (bdat1.hitTest(point1, 1, bdat2, point2, 1)) {
            t1.text = «At least one pixel has collided»
        }
        else {
            t1.text = «No collision»
        }
        bdat1.dispose();
    }
}

И это выход. Начните перетаскивать крючок, чтобы увидеть анимацию.


Я понимаю, что этот учебник может быть не слишком быстрым для чтения, но очень важно получить четкое представление о том, что происходит. Надеюсь, что это было полезно, и если вы заинтересованы в манипулировании этой матрицей 2×2 больше, посетите мою статью на эту тему . Благодарю.