Столкновение прямоугольника и прямоугольника в VHDL

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

Я воспользовался советом из ответа larsbutler и использовал эту стратегию для столкновения:

  • объект.позицияX += объект.скоростьX
  • проверять/реагировать на коллизии
  • объект.позицияY += объект.скоростьY
  • проверять/реагировать на коллизии

Этот псевдокод объясняет, как я проверяю/отвечаю на коллизии:

// right edge of ball in between objects left and right edge
// OR
// left edge of ball in between objects left and right edge
if((ball.right >= object.left && ball.right <= ball.right) || (ball.left >= object.left && ball.left <= object.right))
{
    xCollision = true;
}

// top edge of ball in between objects top and bottom edge
// OR
// bottom edge of ball in between objects top and bottom edge
if((ball.top >= object.top && ball.top <= object.bottom) || (ball.bottom <= object.bottom && ball.bottom >= object.top))
{
    yCollision = true;
}

// respond to collision
if xCollision and yCollision then
{
    // This code block is respective to each x or y update in order to resolve collision
}

Имейте в виду, что верхний левый угол экрана (0, 0). Объекты располагаются от их центра. Вот схема:

Настройка диаграммы Pong

Это базовая диаграмма того, каким должен быть ответ: (источник)

Диаграмма реакции на столкновение

Проблема:

В данный момент я просто пытаюсь работать над x-столкновением. Проблема заключается в том, что код xPosition вытаскивает мяч из ракетки, чтобы не застрять. Кажется, что if xVelocity < 0 then неправильно оценивает. Скажем, мяч движется слева направо (xVelocity > 0), а затем мы ударяем по ракетке с правой стороны. xVelocity изменит знак на отрицательный (xVelocity ‹ 0). Проблема, если утверждение должно оцениваться как истинное и уменьшать xPosition, чтобы вывести его из весла. Однако этого не происходит, а вместо этого прыгает через весло и просто повторяется вперед и назад. Причина, по которой мы добавляем или вычитаем 40, заключается в тестировании, и на самом деле это будет количество, которое находится внутри весла.

Многие из моих реализаций, кажется, попадают в эту ловушку, xVelocity не оценивает правильно. Код работает, если вы переключаете плюс и минус в if else, но, на мой взгляд, это не имеет никакого логического смысла. Почему это должно быть противоположно тому, что у меня ниже? (имейте в виду, что перед этим xVelocity умножается на -1.

-- respond to collision
if xCollision = '1' and yCollision = '1' then

    -- Change direction
    xVelocity <= xVelocity * (-1);
    -- Add glancing y velocity of paddle
    yVelocity <= yVelocity + (collisionObjects(i)(5)/4);

    -- If bouncing in the left direction
    if xVelocity < 0 then
        -- move outwards as much as we are inside the paddle
        -- Should be negating from xPosition as we are bouncing left and want to resolve that way
        xPosition <= xPosition - 40;
    else
        xPosition <= xPosition + 40;
    end if;
end if;

Полный код: (VHDL)

-- Ball collision is using discrete collision! 
-- not sweep collision which helps with small fast objects passing through each other


library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use ieee.std_logic_unsigned.all;

--note this line.The package is compiled to this directory by default.
--so don't forget to include this directory. 
library work;
--this line also is must.This includes the particular package into your program.
use work.my_package.all;

entity Ball is
    generic(
        numCollisionObjects: integer := 2;
        ballRadius : integer := 10;
        rgbColor : std_logic_vector(7 downto 0) := "111" & "111" & "11"
    );
    port(
        reset: in std_logic;
        clk: in std_logic;
        hCounter: in std_logic_vector(9 downto 0);
        vCounter: in std_logic_vector(9 downto 0);
        colObject: out type_collisionObject;
        collisionObjects: in type_collisionObjectArray(0 to numCollisionObjects-1);

        pixelOn: out std_logic;
        rgbPixel: out std_logic_vector(7 downto 0) := rgbColor
    );
end Ball;

architecture Behavioral of Ball is
    signal xPosition : integer := 0;
    signal yPosition : integer := 0;

    signal xVelocity : integer := 0;
    signal yVelocity : integer := 0;

    signal pixelBuffer : std_logic;
    signal RGBBuffer : std_logic_vector(7 downto 0) := rgbColor;

    signal colObjectBuffer: type_collisionObject;
begin
    pixelOn <= pixelBuffer;
    rgbPixel <= RGBBuffer;

    colObjectBuffer <= (xPosition, yPosition, ballRadius * 2, ballRadius * 2, xVelocity, yVelocity);
    colObject <= colObjectBuffer;

    animation: process(clk)
        variable update_clk_count: natural := 0;
        variable update_clk_prescaler: natural := 10000000; -- 833333; -- Slow because of debuging... 50 Mhz / clk_prescaler = desired speed
        --variable i: natural := 1;
        variable xCollision: std_logic := '0';
        variable yCollision: std_logic := '0';

        variable colObject_lastState: type_collisionObject;
    begin

        if rising_edge(clk) then
            -- While reset is high then we reset the positions
            if reset = '1' then

                xPosition <= SCREEN_RESX/2;
                yPosition <= SCREEN_RESY/2;

                xVelocity <= 3;
                yVelocity <= 0;

            else
                if update_clk_count >= update_clk_prescaler then

                    colObject_lastState := colObjectBuffer;

                    -- if we are hitting the left wall
                    if (xPosition - ballRadius + xVelocity) <= 0 then
                        RGBBuffer <= rgbColor;

                        if xVelocity < 0 then
                            xVelocity <= xVelocity * (-1);
                        end if;
                    end if;

                    -- if we are hitting the right wall
                    if (xPosition + ballRadius + xVelocity) >= 640 then
                        RGBBuffer <= rgbColor;

                        if xVelocity > 0 then
                            xVelocity <= xVelocity * (-1);
                        end if;
                    end if;

                    -- if we are hitting the top wall
                    if (yPosition - ballRadius + yVelocity) <= 0 then
                        RGBBuffer <= rgbColor;

                        if yVelocity < 0 then
                            yVelocity <= yVelocity * (-1);
                        end if;
                    end if;

                    -- if we are hitting the bottom wall
                    if (yPosition + ballRadius + yVelocity) >= 480 then
                        RGBBuffer <= rgbColor;

                        if yVelocity > 0 then
                            yVelocity <= yVelocity * (-1);
                        end if;
                    end if;

                    -- Update x position
                    xPosition <= xPosition + xVelocity;

                    -- Check for collision after x updates
                    if not(xVelocity = 0) then
                        for i in collisionObjects'range loop
                            xCollision := '0';
                            yCollision := '0';

                            -- right edge of ball in between objects left and right edge
                            -- OR
                            -- left edge of ball in between objects left and right edge
                            if (xPosition + ballRadius >= collisionObjects(i)(0) - (collisionObjects(i)(2)/2) and xPosition + ballRadius <= collisionObjects(i)(0) + (collisionObjects(i)(2)/2)) 
                                    OR (xPosition - ballRadius >= collisionObjects(i)(0) - (collisionObjects(i)(2)/2) and xPosition - ballRadius <= collisionObjects(i)(0) + (collisionObjects(i)(2)/2)) then
                                xCollision := '1';
                            end if;

                            -- top edge of ball in between objects top and bottom edge
                            -- OR
                            -- bottom edge of ball in between objects top and bottom edge
                            if (yPosition - ballRadius >= collisionObjects(i)(1) - (collisionObjects(i)(3)/2) and yPosition - ballRadius <= collisionObjects(i)(1) + (collisionObjects(i)(3)/2)) 
                                    OR (yPosition + ballRadius >= collisionObjects(i)(1) - (collisionObjects(i)(3)/2) and yPosition + ballRadius <= collisionObjects(i)(1) + (collisionObjects(i)(3)/2)) then
                                yCollision := '1';
                            end if;

                            -- respond to collision
                            if xCollision = '1' and yCollision = '1' then

                                -- Change direction
                                xVelocity <= xVelocity * (-1);
                                -- Add glancing y velocity of paddle
                                yVelocity <= yVelocity + (collisionObjects(i)(5)/4);

                                -- If bouncing in the left direction
                                if xVelocity < 0 then
                                    -- move outwards as much as we are inside the paddle
                                    -- Should be negating from xPosition as we are bouncing left and want to resolve that way
                                    xPosition <= xPosition - 40;
                                else
                                    xPosition <= xPosition + 40;
                                end if;
                            end if;

                        end loop;
                    end if;


                    -- Update y position
                    yPosition <= yPosition + yVelocity;

                    -- Check for collision after y updates
                    if not(yVelocity = 0) then
                        for i in collisionObjects'range loop
                            xCollision := '0';
                            yCollision := '0';

                            -- right edge of ball in between objects left and right edge
                            -- OR
                            -- left edge of ball in between objects left and right edge
                            if (xPosition + ballRadius >= collisionObjects(i)(0) - (collisionObjects(i)(2)/2) and xPosition + ballRadius <= collisionObjects(i)(0) + (collisionObjects(i)(2)/2)) 
                                    OR (xPosition - ballRadius >= collisionObjects(i)(0) - (collisionObjects(i)(2)/2) and xPosition - ballRadius <= collisionObjects(i)(0) + (collisionObjects(i)(2)/2)) then
                                xCollision := '1';
                            end if;

                            -- top edge of ball in between objects top and bottom edge
                            -- OR
                            -- bottom edge of ball in between objects top and bottom edge
                            if (yPosition - ballRadius >= collisionObjects(i)(1) - (collisionObjects(i)(3)/2) and yPosition - ballRadius <= collisionObjects(i)(1) + (collisionObjects(i)(3)/2)) 
                                    OR (yPosition + ballRadius >= collisionObjects(i)(1) - (collisionObjects(i)(3)/2) and yPosition + ballRadius <= collisionObjects(i)(1) + (collisionObjects(i)(3)/2)) then
                                yCollision := '1';
                            end if;

                            -- respond to collision
                            if xCollision = '1' and yCollision = '1' then

                                yVelocity <= yVelocity * (-1);

                                -- If ball is moving in same direction the paddle is
                                if (yVelocity < 0 and collisionObjects(i)(5) < 0) 
                                        OR (yVelocity > 0 and collisionObjects(i)(5) > 0) then
                                    yVelocity <= yVelocity + (collisionObjects(i)(5)/2);
                                end if;
                            end if;

                        end loop;
                    end if;

                    update_clk_count := 0;
                end if;
            end if;

            update_clk_count := update_clk_count + 1;
        end if;
    end process;



    drawing: process(hCounter, vCounter)
    begin
        -- If within pixel bounds of bar
        if hCounter >= (xPosition - ballRadius) and hCounter <= (xPosition + ballRadius) and vCounter >= (yPosition - ballRadius) and vCounter <= (yPosition + ballRadius) then
            pixelBuffer <= '1';
        else
            pixelBuffer <= '0';
        end if;
    end process;

end Behavioral;

И соответствующая информация из my_package.vhd:

constant SCREEN_RESX: integer := 640;
constant SCREEN_RESY: integer := 480;

-- 0: position X
-- 1: position Y
-- 2: size X 
-- 3: size Y
-- 4: velocityX
-- 5: velocityY
type type_collisionObject is array (0 to 5) of integer; 
type type_collisionObjectArray is array(natural range <>) of type_collisionObject; 

Обновлять

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

Я бы хотел, чтобы это было больше подчеркнуто в руководствах и учебниках, потому что это стоило мне кучу времени.


person MLM    schedule 06.07.2013    source источник


Ответы (2)


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

Предполагая, что вы доверяете псевдокоду (и я вижу некоторые проблемы с ним :-)

Почему бы не перевести

if((ball.right >= object.left && ball.right <= ball.right) || (ball.left >= object.left && ball.left <= object.right))
{
    xCollision = true;
}

в VHDL (исправляя очевидное) как

if (ball.right >= object.left and ball.right <= object.right) 
   or (ball.left >= object.left and ball.left <= object.right) then
   xCollision := true;
end if;

или просто

xCollision := (ball.right >= object.left and ball.right <= object.right) 
   or (ball.left >= object.left and ball.left <= object.right);

и правильно создать типы данных для ball, object (записи) и xCollision (логическое значение)?

Перейдите к следующему шагу и оберните это как функцию или процедуру:

   function XCollision (ball, object : rect) return boolean is
   begin 
      return (ball.right >= object.left and ball.right <= ball.right) 
       or (ball.left >= object.left and ball.left <= object.right);
   end XCollision;

вызываемый как

   x_collision := false;
   for i in objects'range loop
      x_collision := x_collision or XCollision (ball, objects(i));
   end loop;

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

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

type Sides is (left, right, top, bottom); 
type obj_type is array (sides) of integer;
variable ball, object : obj_type;

в этом случае XCollision теперь может быть

   function XCollision (ball, object : rect) return boolean is
   begin 
      return (ball(right) >= object(left) and ...);
   end XCollision;

Ничего принципиально несинтезируемого в вышеизложенном нет.

Хотя я видел сборку объекта Synplicity 2010 года для определенных функций — она значительно отстает от XST в некоторых отношениях — как ни странно, она была довольна эквивалентной процедурой с параметром OUT для возвращаемого значения.

Те, кто говорит, что программирование для FPGA (проектирование аппаратного обеспечения) отличается от кодирования программного обеспечения, абсолютно правы — в определенных пределах — но примерно НИКОГДА, когда рекомендуют наносить вред вашей способности создавать чистый, понятный код, который говорит, что делает, и делает то, что говорит.

В чем ДЕЙСТВИТЕЛЬНО отличается аппаратный дизайн, так это в таких вещах, как:

  • возможно ли аппаратное обеспечение? цикл FOR с известными границами ВОЗМОЖЕН (при достаточно большой FPGA) - цикл WHILE с неизвестными границами генерирует неизвестное количество оборудования ... невозможно! Указатели (типы доступа VHDL) и «malloc» явно отсутствуют...
  • Аппаратное обеспечение маленькое и достаточно быстрое? При наличии достаточного количества объектов описанный выше цикл for переполнит FPGA или сгенерирует такую ​​длинную логическую цепочку, что она не будет соответствовать вашей целевой скорости. Затем вам нужно преобразовать его в конвейерный дизайн (большой и быстрый) или конечный автомат (маленький и быстрый, но занимает много циклов) для достижения ваших целей.
  • используя соответствующие типы данных. Если мне нужно 17-битное число, я просто создаю его и сохраняю много вентилей по сравнению с наилучшим доступным размером (32-битным), доступным программисту на C. (Есть языки программирования, которые позволяют это, очевидно, но их пользователи все же не большинство)
  • и, без сомнения, другие, но эти ИМО являются наиболее важными.
  • Повторите комментарии; аспекты параллельной обработки и использование сигналов для надежной связи между процессами являются самой большой разницей: в то время как приведенные выше пункты действительны в рамках одного процесса; сигналы и несколько процессов - ОГРОМНОЕ отличие от большинства других парадигм программирования.
person user_1818839    schedule 07.07.2013
comment
Ваши предложения значительно улучшили ясность кода. Есть ли какое-то время установки битов при смене сигналов или при использовании функций? Кажется, в моем ответе на блок кода столкновения происходят какие-то странные вещи. - person MLM; 09.07.2013
comment
Я узнал, что вы не можете гарантировать, что значение сигнала изменится в течение тактового цикла. Я думаю, что это ошибка, с которой я имел дело с этой реализацией и моими прошлыми версиями. Я очень ценю советы по улучшению моего кода и опубликую свой статус завтра в исходном сообщении. - person MLM; 09.07.2013
comment
А, тогда вы путаете сигналы и переменные, которые ведут себя по-разному, и не зря. Ближе к отметке сказать (с синхронизированными процессами), что вы можете гарантировать, что сигнал НЕ изменится в течение тактового цикла... Вам нужен ЭТО Q&A: stackoverflow.com/questions/13954193/ - надеюсь, это прояснит ситуацию. - person user_1818839; 09.07.2013

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

Попробуйте это, надеюсь, это сработает.

Редактировать: я делаю ту же игру с fpga, и она работает нормально.

person Ahmet    schedule 08.07.2013
comment
Вы правы, разницы нет, и мой код отражает это. Имейте в виду, что он дважды проверяет наличие коллизий. Один раз после X и один раз после обновления Y. Вам нужно будет более конкретно указать, какая часть не упрощена. Также я реализую то, что Брайан Драммонд сказал в своем ответе о том, как упростить, добавив типы (записи) и функции. - person MLM; 08.07.2013
comment
Почему вы используете этот код? 'если xVelocity ‹ 0, то' я не понял сути. Если мяч попадает в левый экран, то его скорость должна быть отрицательной, нет необходимости проверять. Просто умножьте скорость на знак минус. Пожалуйста, просто попробуйте и дайте нам знать. - person Ahmet; 08.07.2013
comment
Если мы движемся слева направо (xVelocity > 0) и нажимаем правую ракетку, xVelocity умножается на -1, поэтому xVelocity становится отрицательным. Затем мы переходим к оператору if, который должен быть истинным, и перемещать мяч дальше от ракетки в направлении отскока. На самом деле вам нужно изменить это утверждение на «if xVelocity › 0 then», чтобы оно работало правильно, что противоречит моей логике... Причина, по которой у нас есть оператор if, заключается в том, что ракетки находятся на каждой стороне экрана ( слева и справа) в вертикальном положении, и каждая лопатка обращена противоположной стороной друг к другу. - person MLM; 08.07.2013