среда, 27 декабря 2023 г.

Использование SDL2 + memake + простой самописный 2D-коллайдер

 Решил посмотреть на самые простые способы отображения графических примитивов в Windows и нашел библиотеку memake. В использовании достаточно простая и удобная, но зависит от библиотеки SDL. В примере для Visual Studio (я использую VS 2019 и всем советую использовать эту или новее) сразу идут собранные бинари SDL, так что все работает "из коробки", но настроено там все только для дебажной x86 версии, так что я пытался настроить для остальных (Release x86, Debug x64, Release x64), но не преуспел (слишком много всяких параметров менять руками) и решил сделать все через cmake, попутно разбираясь, а что же там не работает. Получил вот такой CMakeLists.txt:

  1. #
  2. cmake_minimum_required (VERSION 3.8)
  3.  
  4. project ("MemakePrj")
  5.  
  6. # Add source to this project's executable.
  7. add_executable (MemakePrj "main.cpp" "Memake/Memake.cpp" "Memake/Vector2d.cpp")
  8.  
  9. # SDL2 headers
  10. target_include_directories(MemakePrj PRIVATE "SDL2-2.0.14/include")
  11.  
  12. # add SDL_MAIN_HANDLED definition to avoid
  13. # "LNK2019 unresolved external symbol SDL_main referenced in function main_getcmdline"
  14. add_definitions( -DSDL_MAIN_HANDLED )
  15.  
  16. # SDL library folder
  17. set(SDL2_lib_folder "${PROJECT_SOURCE_DIR}/SDL2-2.0.14/lib")
  18.  
  19. message(${CMAKE_BUILD_TYPE})
  20.  
  21. # check 32 or 64 bits
  22. if(CMAKE_SIZEOF_VOID_P EQUAL 8)
  23. # 64 bits
  24. set(SDL2_lib_folder "${SDL2_lib_folder}/x64")
  25. elseif(CMAKE_SIZEOF_VOID_P EQUAL 4)
  26. # 32 bits
  27. set(SDL2_lib_folder "${SDL2_lib_folder}/x86")
  28. endif()
  29.  
  30. # link SDL2 static lib
  31. target_link_libraries(MemakePrj ${SDL2_lib_folder}/SDL2.lib)
  32. target_link_libraries(MemakePrj ${SDL2_lib_folder}/SDL2main.lib)
  33.  
  34. # copy dynamic lib to folder with executable file
  35. file(COPY ${SDL2_lib_folder}/SDL2.dll DESTINATION ${PROJECT_BINARY_DIR})
Теперь по порядку. В строке 7 добавляю к main.cpp еще два cpp-файла из библиотеки memake (остальное там - заголовочные файлы) и вся библиотека будет таким образом включена в исполнимый файл. Можно было сделать отдельный CMakeLists.txt для папки memake, чтобы вся библиотека подтягивалась и собиралась отдельно, но мне было лень и теперь все у меня одним куском. В строке 10 добавляю заголовки для SDL, а в строке 14 добавляю специальный дефинишен SDL_MAIN_HANDLED, потому что без него будет ошибка компиляции "LNK2019 unresolved external symbol SDL_main ...". В строке 17 указываем папку с бинарями библиотеки SDL, а в строках 22-28 определяем, какую версию бинарных файлов SDL нам надо использовать x86 или x64. В строках 31 и 32 собственно подключаем эти библиотеки к проекту. В строке 35 очень интересный момент - копирование SDL2.dll в папку с исполняемым файлом. Оказывается, без этой dll оно все не будет работать и в солюшене для Visual Studio был просто добавлен путь в переменную PATH для проекта. Я не нашел как сделать что-то такое же для cmake, поэтому просто скопировал файл библиотеки в папку с exe-файлом (строка 35). На самом деле составлять CMakeLists.txt для проекта - это целое дело, сопоставимое с написанием кода, но это все же вспомогательная задача, от которой нужно только одно: чтобы все собиралось и работало. Так что я не упорствовал в поиске каких-то сильно красивых, изящных и правильных решений: работает, выглядит понятно - ну и хорошо.
То ли дело посмотреть как работает "коллижн менеджер" в оригинальном примере - а он работает очень просто и можно даже сказать примитивно, но справляется со своей задачей (демонстрация работы библиотеки): 

  1. void checkCollision(Ball& b)
  2. {
  3. float distX = x - b.x;
  4. float distY = y - b.y;
  5.  
  6. float distance = sqrt((distX * distX) + (distY * distY));
  7. if (distance < r + b.r)
  8. {
  9. dx *= -1;
  10. dy *= -1;
  11. }
  12. }
Если расстояниеот одного до другого шарика меньше, чем сумма радиусов обоих, то шарик отлетает в противоположную сторону (направления движения по обоим осям умножаются на -1). Простенько и со вкусом. В результате все шарики летают под углом, кратным 45 градусов: 


Я решил заморочиться и все же учесть правило "угол падения равен углу отражения", для чего даже написал "библиотеку" операций для двумерного пространства. В результате вот такой получился код:

  1. void checkCollision(Ball& b)
  2. {
  3. float dist = pos.distanceTo(b.pos);
  4. if (dist < r + b.r)
  5. {
  6. // direction to other ball
  7. Point2f toB = b.pos - pos;
  8. // calculate dot product
  9. float dotProd = f.dotProduct(toB);
  10. // if dot product is negative then force directed away from B ball
  11. // and we do nothing
  12. if (dotProd > 0)
  13. {
  14. // angle between normal and force (moving) vectors
  15. float angle = f.angleTo(toB);
  16. // the angle of incidence is equal to the angle of reflection
  17. f = Mat2x2f().rot(angle * 2) * f;
  18. f = f * (-1.f);
  19. }
  20. }
  21. }
Если вкратце, то мы тут тоже сначала измеряем расстояние от этого до другого шарика, а потом вычисляем вектор toB от центра этого до центра другого шарика, после чего в строке 9 вычисляем dot product вектора движения f на toB (что есть проекция f на toB) и в случае, если он больше нуля (то есть направлен на другой шарик), то происходит "отскок", а если меньше нуля, то значит наш шарик и так двигается прочь от другого шарика и делать ничего не надо. Отскок мы вычисляем следующим образом: вычисляем угол между вектором движения и вектором направления на центр другого шарика (строка 15), после чего доворачиваем (путем умножения на матрицу поворота) вокруг оси направления вектор движения на этот двойной угол и потом умножаем его на -1 и таким образом реализуем правило "угол падения равен углу отражения". Для большей наглядности вот картинка:


Вот тут вертикальная ось - это как раз вектор toB на центр другого шарика, а горизонтальная линия - это касательная к точке, где произошел контакт. Получилось вот так:


Уже гораздо более интересно! Но пришлось, конечно, вспомнить векторную и матричную математику.

Весь код здесь.

Комментариев нет:

Отправить комментарий