├── .gitignore ├── README_files ├── lines.png ├── points.png ├── polygons.png ├── rotation.png ├── skeletal.png └── projection.png ├── Sources ├── Gfx │ ├── CMakeLists.txt │ ├── Rasterizer.hpp │ ├── ImageBuffer.hpp │ └── Gfx.hpp ├── Math │ ├── CMakeLists.txt │ └── Math.hpp ├── Tools │ ├── CMakeLists.txt │ └── Timer.hpp ├── 01_SamplePoint │ ├── CMakeLists.txt │ └── Main.cpp ├── 02_SampleLines │ ├── CMakeLists.txt │ └── Main.cpp ├── 04_SamplePointProjection │ ├── CMakeLists.txt │ └── Main.cpp ├── 07_RandomVectorWithinCone │ ├── CMakeLists.txt │ └── Main.cpp ├── 03_SamplePointRotation │ ├── CMakeLists.txt │ └── Main.cpp ├── 06_SampleSkeletalBasics │ ├── CMakeLists.txt │ ├── Main.cpp │ └── Skeleton.hpp └── 05_SamplePolygonalDraw │ ├── CMakeLists.txt │ └── Main.cpp ├── README.md └── CMakeLists.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .* 2 | Bin 3 | out 4 | cmake-build-* -------------------------------------------------------------------------------- /README_files/lines.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darkoffalex/basegraphics/HEAD/README_files/lines.png -------------------------------------------------------------------------------- /README_files/points.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darkoffalex/basegraphics/HEAD/README_files/points.png -------------------------------------------------------------------------------- /README_files/polygons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darkoffalex/basegraphics/HEAD/README_files/polygons.png -------------------------------------------------------------------------------- /README_files/rotation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darkoffalex/basegraphics/HEAD/README_files/rotation.png -------------------------------------------------------------------------------- /README_files/skeletal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darkoffalex/basegraphics/HEAD/README_files/skeletal.png -------------------------------------------------------------------------------- /README_files/projection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/darkoffalex/basegraphics/HEAD/README_files/projection.png -------------------------------------------------------------------------------- /Sources/Gfx/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Версия CMake 2 | cmake_minimum_required(VERSION 3.15) 3 | 4 | # Название библиотеки 5 | set(TARGET_NAME "Gfx") 6 | 7 | # Добавляем header-only библиотеку 8 | add_library(${TARGET_NAME} INTERFACE) 9 | target_include_directories(${TARGET_NAME} INTERFACE $) -------------------------------------------------------------------------------- /Sources/Math/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Версия CMake 2 | cmake_minimum_required(VERSION 3.15) 3 | 4 | # Название библиотеки 5 | set(TARGET_NAME "Math") 6 | 7 | # Добавляем header-only библиотеку 8 | add_library(${TARGET_NAME} INTERFACE) 9 | target_include_directories(${TARGET_NAME} INTERFACE $) -------------------------------------------------------------------------------- /Sources/Tools/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Версия CMake 2 | cmake_minimum_required(VERSION 3.15) 3 | 4 | # Название библиотеки 5 | set(TARGET_NAME "Tools") 6 | 7 | # Добавляем header-only библиотеку 8 | add_library(${TARGET_NAME} INTERFACE) 9 | target_include_directories(${TARGET_NAME} INTERFACE $) -------------------------------------------------------------------------------- /Sources/01_SamplePoint/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Версия CMake 2 | cmake_minimum_required(VERSION 3.15) 3 | 4 | # Название приложения 5 | set(TARGET_NAME "01_SamplePoint") 6 | set(TARGET_BIN_NAME "01_SamplePoint") 7 | 8 | # Добавляем .exe (проект в Visual Studio) 9 | add_executable(${TARGET_NAME} 10 | "Main.cpp") 11 | 12 | # Меняем название запускаемого файла в зависимости от типа сборки 13 | set_property(TARGET ${TARGET_NAME} PROPERTY OUTPUT_NAME "${TARGET_BIN_NAME}$<$:_Debug>_${PLATFORM_BIT_SUFFIX}") 14 | 15 | # Статическая линковка рантайма и стандартных библиотек 16 | if(MSVC) 17 | set_property(TARGET ${TARGET_NAME} PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") 18 | else() 19 | set_property(TARGET ${TARGET_NAME} PROPERTY LINK_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-Bstatic,--whole-archive -lwinpthread -Wl,--no-whole-archive") 20 | endif() 21 | 22 | # Линковка с библиотекой для работы с математикой 23 | target_link_libraries(${TARGET_NAME} PUBLIC "Math") 24 | 25 | # Линковка с библиотекой для работы с графикой 26 | target_link_libraries(${TARGET_NAME} PUBLIC "Gfx") -------------------------------------------------------------------------------- /Sources/02_SampleLines/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Версия CMake 2 | cmake_minimum_required(VERSION 3.15) 3 | 4 | # Название приложения 5 | set(TARGET_NAME "02_SampleLines") 6 | set(TARGET_BIN_NAME "02_SampleLines") 7 | 8 | # Добавляем .exe (проект в Visual Studio) 9 | add_executable(${TARGET_NAME} 10 | "Main.cpp") 11 | 12 | # Меняем название запускаемого файла в зависимости от типа сборки 13 | set_property(TARGET ${TARGET_NAME} PROPERTY OUTPUT_NAME "${TARGET_BIN_NAME}$<$:_Debug>_${PLATFORM_BIT_SUFFIX}") 14 | 15 | # Статическая линковка рантайма и стандартных библиотек 16 | if(MSVC) 17 | set_property(TARGET ${TARGET_NAME} PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") 18 | else() 19 | set_property(TARGET ${TARGET_NAME} PROPERTY LINK_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-Bstatic,--whole-archive -lwinpthread -Wl,--no-whole-archive") 20 | endif() 21 | 22 | # Линковка с библиотекой для работы с математикой 23 | target_link_libraries(${TARGET_NAME} PUBLIC "Math") 24 | 25 | # Линковка с библиотекой для работы с графикой 26 | target_link_libraries(${TARGET_NAME} PUBLIC "Gfx") -------------------------------------------------------------------------------- /Sources/04_SamplePointProjection/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Версия CMake 2 | cmake_minimum_required(VERSION 3.15) 3 | 4 | # Название приложения 5 | set(TARGET_NAME "04_SampleProjection") 6 | set(TARGET_BIN_NAME "04_SampleProjection") 7 | 8 | # Добавляем .exe (проект в Visual Studio) 9 | add_executable(${TARGET_NAME} 10 | "Main.cpp") 11 | 12 | # Меняем название запускаемого файла в зависимости от типа сборки 13 | set_property(TARGET ${TARGET_NAME} PROPERTY OUTPUT_NAME "${TARGET_BIN_NAME}$<$:_Debug>_${PLATFORM_BIT_SUFFIX}") 14 | 15 | # Статическая линковка рантайма и стандартных библиотек 16 | if(MSVC) 17 | set_property(TARGET ${TARGET_NAME} PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") 18 | else() 19 | set_property(TARGET ${TARGET_NAME} PROPERTY LINK_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-Bstatic,--whole-archive -lwinpthread -Wl,--no-whole-archive") 20 | endif() 21 | 22 | # Линковка с библиотекой для работы с математикой 23 | target_link_libraries(${TARGET_NAME} PUBLIC "Math") 24 | 25 | # Линковка с библиотекой для работы с графикой 26 | target_link_libraries(${TARGET_NAME} PUBLIC "Gfx") -------------------------------------------------------------------------------- /Sources/07_RandomVectorWithinCone/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Версия CMake 2 | cmake_minimum_required(VERSION 3.15) 3 | 4 | # Название приложения 5 | set(TARGET_NAME "07_RandomVectorWithinCone") 6 | set(TARGET_BIN_NAME "07_RandomVectorWithinCone") 7 | 8 | # Добавляем .exe (проект в Visual Studio) 9 | add_executable(${TARGET_NAME} 10 | "Main.cpp") 11 | 12 | # Меняем название запускаемого файла в зависимости от типа сборки 13 | set_property(TARGET ${TARGET_NAME} PROPERTY OUTPUT_NAME "${TARGET_BIN_NAME}$<$:_Debug>_${PLATFORM_BIT_SUFFIX}") 14 | 15 | # Статическая линковка рантайма и стандартных библиотек 16 | if(MSVC) 17 | set_property(TARGET ${TARGET_NAME} PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") 18 | else() 19 | set_property(TARGET ${TARGET_NAME} PROPERTY LINK_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-Bstatic,--whole-archive -lwinpthread -Wl,--no-whole-archive") 20 | endif() 21 | 22 | # Линковка с библиотекой для работы с математикой 23 | target_link_libraries(${TARGET_NAME} PUBLIC "Math") 24 | 25 | # Линковка с библиотекой для работы с графикой 26 | target_link_libraries(${TARGET_NAME} PUBLIC "Gfx") -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Примеры базовых графических алгоритмов 2 | Перечень демо-проектов с реализацией базовых графических алгоритмов применямых в растеризации. От точек до полигонального меша. 3 | 4 | 1) Точки 5 | 6 | ![изображение](README_files/points.png) 7 | 8 | 2) Линии 9 | 10 | ![изображение](README_files/lines.png) 11 | 12 | 3) Вращение точек 13 | 14 | ![изображение](README_files/rotation.png) 15 | 16 | 4) Проекция точек 17 | 18 | ![изображение](README_files/projection.png) 19 | 20 | 5) Полигональный рендеринг 21 | 22 | ![изображение](README_files/polygons.png) 23 | 24 | 6) Базовая (упрощенная) релизация скелетной анимации 25 | 26 | ![изображение](README_files/skeletal.png) 27 | 28 | 7) Растеризатор с шейдерным конвейером (_не реализовано_) 29 | 30 | Код писался и тестировался при помощи следующего набора инструментов 31 | - CLion (IDE) 32 | - MinGW или MSVC (Компиляция и сборка) 33 | 34 | 35 | 36 | Вы можете открыть данный проект при помощи IDE с поддержкой CMake (CLion, Visual Studio 2019) и собрать его, 37 | либо сгенерировать файлы проекта для подходящей IDE (данный вариант не проверялся). 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Sources/03_SamplePointRotation/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Версия CMake 2 | cmake_minimum_required(VERSION 3.15) 3 | 4 | # Название приложения 5 | set(TARGET_NAME "03_SamplePointRotation") 6 | set(TARGET_BIN_NAME "03_SamplePointRotation") 7 | 8 | # Добавляем .exe (проект в Visual Studio) 9 | add_executable(${TARGET_NAME} 10 | "Main.cpp") 11 | 12 | # Меняем название запускаемого файла в зависимости от типа сборки 13 | set_property(TARGET ${TARGET_NAME} PROPERTY OUTPUT_NAME "${TARGET_BIN_NAME}$<$:_Debug>_${PLATFORM_BIT_SUFFIX}") 14 | 15 | # Статическая линковка рантайма и стандартных библиотек 16 | if(MSVC) 17 | set_property(TARGET ${TARGET_NAME} PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") 18 | else() 19 | set_property(TARGET ${TARGET_NAME} PROPERTY LINK_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-Bstatic,--whole-archive -lwinpthread -Wl,--no-whole-archive") 20 | endif() 21 | 22 | # Линковка с библиотекой для работы с математикой 23 | target_link_libraries(${TARGET_NAME} PUBLIC "Math") 24 | 25 | # Линковка с библиотекой для работы с графикой 26 | target_link_libraries(${TARGET_NAME} PUBLIC "Gfx") 27 | 28 | # Линковка с библиотекой вспомогательных инструментов 29 | target_link_libraries(${TARGET_NAME} PUBLIC "Tools") -------------------------------------------------------------------------------- /Sources/06_SampleSkeletalBasics/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Версия CMake 2 | cmake_minimum_required(VERSION 3.15) 3 | 4 | # Название приложения 5 | set(TARGET_NAME "06_SampleSkeletalBasics") 6 | set(TARGET_BIN_NAME "06_SampleSkeletalBasics") 7 | 8 | # Добавляем .exe (проект в Visual Studio) 9 | add_executable(${TARGET_NAME} 10 | "Main.cpp" "Skeleton.hpp") 11 | 12 | # Меняем название запускаемого файла в зависимости от типа сборки 13 | set_property(TARGET ${TARGET_NAME} PROPERTY OUTPUT_NAME "${TARGET_BIN_NAME}$<$:_Debug>_${PLATFORM_BIT_SUFFIX}") 14 | 15 | # Статическая линковка рантайма и стандартных библиотек 16 | if(MSVC) 17 | set_property(TARGET ${TARGET_NAME} PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") 18 | else() 19 | set_property(TARGET ${TARGET_NAME} PROPERTY LINK_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-Bstatic,--whole-archive -lwinpthread -Wl,--no-whole-archive") 20 | endif() 21 | 22 | # Линковка с библиотекой для работы с математикой 23 | target_link_libraries(${TARGET_NAME} PUBLIC "Math") 24 | 25 | # Линковка с библиотекой для работы с графикой 26 | target_link_libraries(${TARGET_NAME} PUBLIC "Gfx") 27 | 28 | # Линковка с библиотекой вспомогательных инструментов 29 | target_link_libraries(${TARGET_NAME} PUBLIC "Tools") -------------------------------------------------------------------------------- /Sources/05_SamplePolygonalDraw/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Версия CMake 2 | cmake_minimum_required(VERSION 3.15) 3 | 4 | # Название приложения 5 | set(TARGET_NAME "05_SamplePolygonalDraw") 6 | set(TARGET_BIN_NAME "05_SamplePolygonalDraw") 7 | 8 | # Добавляем .exe (проект в Visual Studio) 9 | add_executable(${TARGET_NAME} 10 | "Main.cpp") 11 | 12 | # Меняем название запускаемого файла в зависимости от типа сборки 13 | set_property(TARGET ${TARGET_NAME} PROPERTY OUTPUT_NAME "${TARGET_BIN_NAME}$<$:_Debug>_${PLATFORM_BIT_SUFFIX}") 14 | 15 | # Статическая линковка рантайма и стандартных библиотек 16 | if(MSVC) 17 | set_property(TARGET ${TARGET_NAME} PROPERTY COMPILE_FLAGS "-DNOMINMAX") 18 | set_property(TARGET ${TARGET_NAME} PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") 19 | else() 20 | set_property(TARGET ${TARGET_NAME} PROPERTY LINK_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-Bstatic,--whole-archive -lwinpthread -Wl,--no-whole-archive") 21 | endif() 22 | 23 | # Линковка с библиотекой для работы с математикой 24 | target_link_libraries(${TARGET_NAME} PUBLIC "Math") 25 | 26 | # Линковка с библиотекой для работы с графикой 27 | target_link_libraries(${TARGET_NAME} PUBLIC "Gfx") 28 | 29 | # Линковка с библиотекой вспомогательных инструментов 30 | target_link_libraries(${TARGET_NAME} PUBLIC "Tools") -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Версия CMake 2 | cmake_minimum_required(VERSION 3.15) 3 | 4 | # Название проекта (решение в Visual Studio) 5 | project(BaseGraphics) 6 | 7 | # Стандарт С/С++ 8 | set(CMAKE_CXX_STANDARD 14) 9 | 10 | # Устанавливаем каталоги для бинарников 11 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/Bin) 12 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/Bin) 13 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/Bin) 14 | 15 | # Определить разрядность платформы 16 | if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4") 17 | set(PLATFORM_BIT_SUFFIX "x86") 18 | else() 19 | set(PLATFORM_BIT_SUFFIX "x64") 20 | endif() 21 | 22 | # Стандартные библиотеки для GNU/MinGW 23 | if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 24 | set(CMAKE_CXX_STANDARD_LIBRARIES "-static-libgcc -static-libstdc++ -lwsock32 -lws2_32 ${CMAKE_CXX_STANDARD_LIBRARIES}") 25 | endif() 26 | 27 | # Библиотека для работы с математикой (header-only) 28 | add_subdirectory("Sources/Math") 29 | 30 | # Библиотека для работы с графикой (header-only) 31 | add_subdirectory("Sources/Gfx") 32 | 33 | # Библилтека вспомогательных инструментов 34 | add_subdirectory("Sources/Tools") 35 | 36 | # Примеры приложений 37 | add_subdirectory("Sources/01_SamplePoint") 38 | add_subdirectory("Sources/02_SampleLines") 39 | add_subdirectory("Sources/03_SamplePointRotation") 40 | add_subdirectory("Sources/04_SamplePointProjection") 41 | add_subdirectory("Sources/05_SamplePolygonalDraw") 42 | add_subdirectory("Sources/06_SampleSkeletalBasics") 43 | add_subdirectory("Sources/07_RandomVectorWithinCone") 44 | -------------------------------------------------------------------------------- /Sources/Gfx/Rasterizer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ImageBuffer.hpp" 4 | 5 | #include 6 | #include 7 | 8 | namespace gfx 9 | { 10 | 11 | template 12 | class Rasterizer 13 | { 14 | public: 15 | enum class FrontFace 16 | { 17 | eClockWise, 18 | eCounterClockWise 19 | }; 20 | 21 | struct Vec4 22 | { 23 | float x; 24 | float y; 25 | float z; 26 | float w; 27 | }; 28 | 29 | private: 30 | /// Указатель на буфер цвета 31 | ImageBuffer* pColorBuffer_; 32 | /// Указатель на буфер глубины 33 | ImageBuffer* pDepthBuffer_; 34 | /// Как описывается передняя грань 35 | FrontFace frontFace_; 36 | /// Отсечение задних граней 37 | bool backFaceCooling_; 38 | 39 | /// Функция - вершинный шейдер 40 | std::function vertexShaderFn_; 41 | 42 | /// Функция - фрагментный шейдер 43 | std::function fragmentShaderFn_; 44 | public: 45 | /** 46 | * Конструктор по умолчанию 47 | */ 48 | Rasterizer(): 49 | pColorBuffer_(nullptr), 50 | pDepthBuffer_(nullptr), 51 | frontFace_(FrontFace::eClockWise), 52 | backFaceCooling_(true) 53 | {} 54 | 55 | /** 56 | * Основной конструктор 57 | * @param pColorBuffer Указатель на буфер цвета 58 | * @param pDepthBuffer Указатель на буфер глубины 59 | * @param frontFace Как описывается передняя грань 60 | * @param backFaceCooling Отсечение задних граней 61 | */ 62 | Rasterizer(ImageBuffer* pColorBuffer, 63 | ImageBuffer* pDepthBuffer, 64 | FrontFace frontFace = FrontFace::eClockWise, 65 | bool backFaceCooling = true): 66 | pColorBuffer_(pColorBuffer), 67 | pDepthBuffer_(pDepthBuffer), 68 | frontFace_(frontFace), 69 | backFaceCooling_(backFaceCooling) 70 | {} 71 | 72 | void DrawTriangle(const VERTEX& v0, const VERTEX& v1, const VERTEX& v2) 73 | { 74 | } 75 | }; 76 | } 77 | 78 | -------------------------------------------------------------------------------- /Sources/Tools/Timer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | using namespace std::chrono; 6 | 7 | namespace tools 8 | { 9 | class Timer 10 | { 11 | private: 12 | time_point currentFrameTick_; 13 | time_point previousFrameTick_; 14 | time_point lastFpsCounterUpdatedTime_; 15 | unsigned framesCount_; 16 | unsigned fps_; 17 | float delta_; 18 | bool fpsCounterReady_; 19 | 20 | public: 21 | 22 | /** 23 | * При создании таймера currentTick_ устанавливается в текущее время 24 | * @details Создавать таймер следует до цикла 25 | */ 26 | Timer(): 27 | currentFrameTick_(std::chrono::high_resolution_clock::now()), 28 | lastFpsCounterUpdatedTime_(std::chrono::high_resolution_clock::now()), 29 | framesCount_(0), 30 | fps_(0), 31 | delta_(0.0f), 32 | fpsCounterReady_(false) {} 33 | 34 | /** 35 | * Получить разницу во времени между текущим и предыдущим кадром 36 | * @return Значение разницы в миллисекундах 37 | */ 38 | [[nodiscard]] float getDelta() const 39 | { 40 | return delta_; 41 | } 42 | 43 | /** 44 | * Обновить таймер 45 | * @details Время предыдущего кадра - текущее время ПРЕДЫДУЩЕГО кадра, время текущего кадра - НАСТОЯЩЕЕ время 46 | */ 47 | void updateTimer() 48 | { 49 | // Время предыдущего кадра это текущее время предыдущего кадра (до обновления таймера) 50 | previousFrameTick_ = currentFrameTick_; 51 | // Время текущего кадра это НАСТОЯЩЕЕ время 52 | currentFrameTick_ = std::chrono::high_resolution_clock::now(); 53 | // Считать счетчик FPS не готовым к показу 54 | fpsCounterReady_ = false; 55 | 56 | // Сколько времени прошло с прошлого кадра 57 | const int64_t delta = std::chrono::duration_cast(currentFrameTick_ - previousFrameTick_).count(); 58 | delta_ = static_cast(delta) / 1000.0f; 59 | 60 | // Подсчет FPS 61 | // Если прошла секунда с прошлого обновления счетчика FPS 62 | if (std::chrono::duration_cast(currentFrameTick_ - lastFpsCounterUpdatedTime_).count() > 1000) 63 | { 64 | // Счетчик FPS равен кол-ву кадров набранных за секунду с прошлого обновления счетчика 65 | fps_ = framesCount_; 66 | // Обнулить кол-во кадров 67 | framesCount_ = 0; 68 | // Последнее обновление счетчика произошло сейчас 69 | lastFpsCounterUpdatedTime_ = currentFrameTick_; 70 | // FPS готов показу (пока таймер не обновлен) 71 | fpsCounterReady_ = true; 72 | } 73 | 74 | // Увеличить счетчик кадров 75 | framesCount_++; 76 | } 77 | 78 | /** 79 | * Получить FPS 80 | * @details Для корректного значения таймер должен обновляться в каждом кадре 81 | * @return Кол-во кадров за секунду с прошлого обновления счетчика 82 | */ 83 | [[nodiscard]] unsigned getFps() const 84 | { 85 | return fps_; 86 | } 87 | 88 | /** 89 | * Готов ли счетчик FPS к показу 90 | * @details Если показ FPS занимает время, стоит показывать его только тогда когда счетчик обновлен 91 | * @return Да или нет 92 | */ 93 | [[nodiscard]] bool isFpsCounterReady() const 94 | { 95 | return fpsCounterReady_; 96 | } 97 | }; 98 | } 99 | 100 | -------------------------------------------------------------------------------- /Sources/Gfx/ImageBuffer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace gfx 5 | { 6 | /** 7 | * Буфер данных двумерного изображения 8 | * @tparam T Тип или класс описывающий цвет одного элемента (текселя) текстуры 9 | */ 10 | template 11 | class ImageBuffer 12 | { 13 | private: 14 | unsigned width_ = 0; 15 | unsigned height_ = 0; 16 | T* data_; 17 | 18 | public: 19 | /** 20 | * Конструктор по умолчанию (инициализация пустого буфера) 21 | */ 22 | ImageBuffer(): width_(0), height_(0), data_(nullptr){}; 23 | 24 | /** 25 | * Конструктор 26 | * @param width Ширина изображения 27 | * @param height Высота изображения 28 | * @param clear Значение для очистки 29 | */ 30 | ImageBuffer(const unsigned width, const unsigned height, const T& clear): 31 | width_(width), 32 | height_(height), 33 | data_((width * height) > 0 ? new T[width * height] : nullptr) 34 | { 35 | if(data_ != nullptr){ 36 | std::fill_n(this->data_, this->width_ * this->height_, clear); 37 | } 38 | } 39 | 40 | /** 41 | * Конструктор копирования 42 | * Вызывается при инициализации объекта другим объектом (присвоение во веремя создания - этот же случай) 43 | * @param other Копируемый объекь 44 | */ 45 | ImageBuffer(const ImageBuffer& other): ImageBuffer(other.width_, other.height_) 46 | { 47 | if (other.data_) 48 | { 49 | memcpy(this->data_, other.data_, other.width_ * other.height_ * sizeof(T)); 50 | } 51 | } 52 | 53 | /** 54 | * Конструктор перемещения 55 | * Когда объект инициализируется из R-value ссылки (напр. объект возвращен функцией) и копировать нет смысла 56 | * @param other Перемещаемый объект 57 | */ 58 | ImageBuffer(ImageBuffer&& other) noexcept : ImageBuffer() 59 | { 60 | // Омеенять ресурсы объектов 61 | std::swap(data_,other.data_); 62 | std::swap(width_,other.width_); 63 | std::swap(height_,other.height_); 64 | } 65 | 66 | /** 67 | * Оператор присвоения через копирование 68 | * Вызывается когда значение одного объекта присваивается другому 69 | * @param other Копируемый объекь 70 | * @return Ссылка на текущий объект 71 | */ 72 | ImageBuffer& operator=(const ImageBuffer& other) 73 | { 74 | // Если присвоение самому себе 75 | if(this == &other) 76 | return *this; 77 | 78 | // Очистить ресурс текущего объекта 79 | if(data_) delete[] data_; 80 | data_ = nullptr; 81 | width_ = 0; 82 | height_ = 0; 83 | 84 | // Установить размеры 85 | this->width_ = other.width_; 86 | this->height_ = other.height_; 87 | 88 | // Если предпологается что буфер не пуст - выделить память и скопировать в нее данные 89 | if(width_ * height_ > 0){ 90 | this->data_ = new T[other.width_ * other.height_]; 91 | memcpy(this->data_, other.data_, other.width_ * other.height_ * sizeof(T)); 92 | } 93 | 94 | // Вернуть текущий объект (ссылку) 95 | return *this; 96 | } 97 | 98 | /** 99 | * Оператор присвоения через перемещение по R-value ссылке 100 | * Вызывается когда объекту присваивается R-value значение (например возвращаемое функцией) 101 | * @param other Перемещаемый объект 102 | * @return Ссылка на текущий объект 103 | */ 104 | ImageBuffer& operator=(ImageBuffer&& other) noexcept 105 | { 106 | // Если присваивание самому себе - просто вернуть ссылку на этот объект 107 | if (&other == this) return *this; 108 | 109 | // Очистить ресурс текущего объекта 110 | if(data_) delete[] data_; 111 | data_ = nullptr; 112 | width_ = 0; 113 | height_ = 0; 114 | 115 | // Омеенять ресурсы объектов 116 | std::swap(data_,other.data_); 117 | std::swap(width_,other.width_); 118 | std::swap(height_,other.height_); 119 | 120 | // Вернуть текущий объект (ссылку) 121 | return *this; 122 | } 123 | 124 | /** 125 | * Оператор для работы с буфером как с двумерным массивом 126 | * @param y Номер ряда 127 | * @return Указатель на часть массива данных 128 | */ 129 | T* operator[](int y) 130 | { 131 | return this->data_ + this->width_ * y; 132 | } 133 | 134 | /** 135 | * Очистка ресурса 136 | */ 137 | ~ImageBuffer() 138 | { 139 | delete[] data_; 140 | } 141 | 142 | /** 143 | * Получить размер в байтах 144 | * @return 145 | */ 146 | [[nodiscard]] unsigned int getSize() const { 147 | return this->width_ * this->height_ * sizeof(T); 148 | } 149 | 150 | /** 151 | * Очистка буфера 152 | * @param clearValue 153 | */ 154 | void clear(const T& clearValue){ 155 | if((this->width_ * this->height_) > 0 && this->data_){ 156 | std::fill_n(this->data_, this->width_ * this->height_, clearValue); 157 | //memset(this->data_,0,this->width_ * this->height_ * sizeof(T)); 158 | } 159 | } 160 | 161 | /** 162 | * Получить данные 163 | * @return 164 | */ 165 | T* getData(){ 166 | return this->data_; 167 | } 168 | 169 | /** 170 | * Получить ширину 171 | * @return 172 | */ 173 | [[nodiscard]] unsigned int getWidth() const 174 | { 175 | return this->width_; 176 | } 177 | 178 | /** 179 | * Получить высоту 180 | * @return 181 | */ 182 | [[nodiscard]] unsigned int getHeight() const 183 | { 184 | return this->height_; 185 | } 186 | 187 | /** 188 | * Проверка попадания точки в границы буфера кадра 189 | * @param x Координаты точки по X 190 | * @param y Координаты точки по Y 191 | * @return Да или нет 192 | */ 193 | [[nodiscard]] bool isPointIn(int x, int y) const 194 | { 195 | if(this->getSize() == 0) return false; 196 | 197 | return 198 | static_cast(x) <= (this->getWidth()-1) && static_cast(x) >= 0 && 199 | static_cast(y) <= (this->getHeight()-1) && static_cast(y) >= 0; 200 | } 201 | }; 202 | } 203 | 204 | -------------------------------------------------------------------------------- /Sources/02_SampleLines/Main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | /** 8 | * Коды ошибок 9 | */ 10 | enum ErrorCode 11 | { 12 | eNoErrors, 13 | eClassRegistrationError, 14 | eWindowCreationError, 15 | }; 16 | 17 | /// Дескриптор исполняемого модуля программы 18 | HINSTANCE g_hInstance = nullptr; 19 | /// Дескриптор осноного окна отрисовки 20 | HWND g_hwnd = nullptr; 21 | /// Дескриптор контекста отрисовки 22 | HDC g_hdc = nullptr; 23 | /// Наименование класса 24 | const char* g_strClassName = "MainWindowClass"; 25 | /// Заголовок окна 26 | const char* g_strWindowCaption = "DemoApp"; 27 | /// Код последней ошибки 28 | ErrorCode g_lastError = ErrorCode::eNoErrors; 29 | 30 | /** 31 | * Обработчик оконных сообщений 32 | * @param hWnd Дескриптор окна 33 | * @param message Сообщение 34 | * @param wParam Параметр сообщения 35 | * @param lParam Параметр сообщения 36 | * @return Код выполнения 37 | */ 38 | LRESULT CALLBACK WindowProcedure(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); 39 | 40 | /** 41 | * Копирование информации о пикселях изображения в буфер "поверхности" окна 42 | * @param pixels Массив пикселей 43 | * @param width Ширина области 44 | * @param height Высота области 45 | * @param hWnd Дескриптор окна 46 | */ 47 | void PresentFrame(void *pixels, int width, int height, HWND hWnd); 48 | 49 | /** 50 | * Точка входа 51 | * @param argc Кол-во аргументов 52 | * @param argv Аргмуенты 53 | * @return Код исполнения 54 | */ 55 | int main(int argc, char* argv[]) 56 | { 57 | try { 58 | // Получение дескриптора исполняемого модуля программы 59 | g_hInstance = GetModuleHandle(nullptr); 60 | 61 | // Информация о классе 62 | WNDCLASSEX classInfo; 63 | classInfo.cbSize = sizeof(WNDCLASSEX); 64 | classInfo.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; 65 | classInfo.cbClsExtra = 0; 66 | classInfo.cbWndExtra = 0; 67 | classInfo.hInstance = g_hInstance; 68 | classInfo.hIcon = LoadIcon(g_hInstance, IDI_APPLICATION); 69 | classInfo.hIconSm = LoadIcon(g_hInstance, IDI_APPLICATION); 70 | classInfo.hCursor = LoadCursor(nullptr, IDC_ARROW); 71 | classInfo.hbrBackground = CreateSolidBrush(RGB(240, 240, 240)); 72 | classInfo.lpszMenuName = nullptr; 73 | classInfo.lpszClassName = g_strClassName; 74 | classInfo.lpfnWndProc = WindowProcedure; 75 | 76 | // Пытаемся зарегистрировать оконный класс 77 | if (!RegisterClassEx(&classInfo)) { 78 | g_lastError = ErrorCode::eClassRegistrationError; 79 | throw std::runtime_error("ERROR: Can't register window class."); 80 | } 81 | 82 | // Создание окна 83 | g_hwnd = CreateWindow( 84 | g_strClassName, 85 | g_strWindowCaption, 86 | WS_OVERLAPPEDWINDOW, 87 | 0, 0, 88 | 800, 600, 89 | nullptr, 90 | nullptr, 91 | g_hInstance, 92 | nullptr); 93 | 94 | // Если не удалось создать окно 95 | if (!g_hwnd) { 96 | g_lastError = ErrorCode::eWindowCreationError; 97 | throw std::runtime_error("ERROR: Can't create main application window."); 98 | } 99 | 100 | // Показать окно 101 | ShowWindow(g_hwnd, SW_SHOWNORMAL); 102 | 103 | // Получение контекста отрисовки 104 | g_hdc = GetDC(g_hwnd); 105 | 106 | // Размеры клиентской области окна 107 | RECT clientRect; 108 | GetClientRect(g_hwnd, &clientRect); 109 | 110 | // Создать буффер кадра 111 | auto frameBuffer = gfx::ImageBuffer(clientRect.right, clientRect.bottom, {0, 0, 0, 0}); 112 | std::cout << "INFO: Frame-buffer initialized (resolution : " << frameBuffer.getWidth() << "x" << frameBuffer.getHeight() << ", size : " << frameBuffer.getSize() << " bytes)" << std::endl; 113 | 114 | // Пропорции области вида 115 | float aspectRatio = static_cast(clientRect.right) / static_cast(clientRect.bottom); 116 | 117 | // Точки в NDC-координатах 118 | math::Vec2 bottomLeft = {-1.0f / aspectRatio,-1.0f}; 119 | math::Vec2 topRight = {1.0f / aspectRatio,1.0f}; 120 | 121 | // Перевод в пространство экрана 122 | auto bottomLeftScreen = math::NdcToScreen(bottomLeft, frameBuffer.getWidth(), frameBuffer.getHeight()); 123 | auto topRightScreen = math::NdcToScreen(topRight, frameBuffer.getWidth(), frameBuffer.getHeight()); 124 | 125 | // Нарисовать линию 126 | gfx::SetLine(&frameBuffer,bottomLeftScreen.x,bottomLeftScreen.y,topRightScreen.x,topRightScreen.y,{0,255,0,0}); 127 | 128 | // Нарисовать прямоугольник 129 | gfx::SetBox(&frameBuffer,bottomLeftScreen.x,bottomLeftScreen.y,topRightScreen.x,topRightScreen.y,{0,255,0,0}); 130 | 131 | /** MAIN LOOP **/ 132 | 133 | // Оконное сообщение 134 | MSG msg = {}; 135 | 136 | // Запуск цикла 137 | while (true) 138 | { 139 | // Обработка оконных сообщений 140 | if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) 141 | { 142 | DispatchMessage(&msg); 143 | 144 | if (msg.message == WM_QUIT) { 145 | break; 146 | } 147 | } 148 | 149 | // Показ кадра 150 | PresentFrame(frameBuffer.getData(), static_cast(frameBuffer.getWidth()), static_cast(frameBuffer.getHeight()), g_hwnd); 151 | } 152 | } 153 | catch(std::exception& ex) 154 | { 155 | std::cout << ex.what() << std::endl; 156 | } 157 | 158 | // Уничтожение окна 159 | DestroyWindow(g_hwnd); 160 | // Вырегистрировать класс окна 161 | UnregisterClass(g_strClassName, g_hInstance); 162 | 163 | // Код выполнения/ошибки 164 | return static_cast(g_lastError); 165 | } 166 | 167 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 168 | 169 | /** 170 | * Обработчик оконных сообщений 171 | * @param hWnd Дескриптор окна 172 | * @param message Сообщение 173 | * @param wParam Параметр сообщения 174 | * @param lParam Параметр сообщения 175 | * @return Код выполнения 176 | */ 177 | LRESULT CALLBACK WindowProcedure(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) 178 | { 179 | switch (message) 180 | { 181 | case WM_DESTROY: 182 | PostQuitMessage(0); 183 | break; 184 | 185 | case WM_RBUTTONDOWN: 186 | case WM_LBUTTONDOWN: 187 | case WM_MBUTTONDOWN: 188 | // При нажатии любой кнопки мыши 189 | break; 190 | 191 | case WM_MOUSEMOVE: 192 | // При движении мыши 193 | // Если зажата левая кнопка мыши 194 | if (wParam & MK_LBUTTON) { 195 | } 196 | break; 197 | 198 | default: 199 | return DefWindowProc(hWnd, message, wParam, lParam); 200 | } 201 | 202 | return 0; 203 | } 204 | 205 | /** 206 | * Копирование информации о пикселях изображения в буфер "поверхности" окна 207 | * @param pixels Массив пикселей 208 | * @param width Ширина области 209 | * @param height Высота области 210 | * @param hWnd Дескриптор окна 211 | */ 212 | void PresentFrame(void *pixels, int width, int height, HWND hWnd) 213 | { 214 | // Получить хендл на временный bit-map (4 байта на пиксель) 215 | HBITMAP hBitMap = CreateBitmap( 216 | width, 217 | height, 218 | 1, 219 | 8 * 4, 220 | pixels); 221 | 222 | // Получить device context окна 223 | HDC hdc = GetDC(hWnd); 224 | 225 | // Временный DC для переноса bit-map'а 226 | HDC srcHdc = CreateCompatibleDC(hdc); 227 | 228 | // Связать bit-map с временным DC 229 | SelectObject(srcHdc, hBitMap); 230 | 231 | // Копировать содержимое временного DC в DC окна 232 | BitBlt( 233 | hdc, // HDC назначения 234 | 0, // Начало вставки по оси X 235 | 0, // Начало вставки по оси Y 236 | static_cast(width), // Ширина 237 | static_cast(height), // Высота 238 | srcHdc, // Исходный HDC (из которого будут копироваться данные) 239 | 0, // Начало считывания по оси X 240 | 0, // Начало считывания по оси Y 241 | SRCCOPY // Копировать 242 | ); 243 | 244 | // Уничтожить bit-map 245 | DeleteObject(hBitMap); 246 | // Уничтожить временный DC 247 | DeleteDC(srcHdc); 248 | // Уничтожить DC 249 | ReleaseDC(hWnd,hdc); 250 | } 251 | -------------------------------------------------------------------------------- /Sources/01_SamplePoint/Main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | /** 8 | * Коды ошибок 9 | */ 10 | enum ErrorCode 11 | { 12 | eNoErrors, 13 | eClassRegistrationError, 14 | eWindowCreationError, 15 | }; 16 | 17 | /// Дескриптор исполняемого модуля программы 18 | HINSTANCE g_hInstance = nullptr; 19 | /// Дескриптор осноного окна отрисовки 20 | HWND g_hwnd = nullptr; 21 | /// Дескриптор контекста отрисовки 22 | HDC g_hdc = nullptr; 23 | /// Наименование класса 24 | const char* g_strClassName = "MainWindowClass"; 25 | /// Заголовок окна 26 | const char* g_strWindowCaption = "DemoApp"; 27 | /// Код последней ошибки 28 | ErrorCode g_lastError = ErrorCode::eNoErrors; 29 | 30 | /** 31 | * Обработчик оконных сообщений 32 | * @param hWnd Дескриптор окна 33 | * @param message Сообщение 34 | * @param wParam Параметр сообщения 35 | * @param lParam Параметр сообщения 36 | * @return Код выполнения 37 | */ 38 | LRESULT CALLBACK WindowProcedure(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); 39 | 40 | /** 41 | * Копирование информации о пикселях изображения в буфер "поверхности" окна 42 | * @param pixels Массив пикселей 43 | * @param width Ширина области 44 | * @param height Высота области 45 | * @param hWnd Дескриптор окна 46 | */ 47 | void PresentFrame(void *pixels, int width, int height, HWND hWnd); 48 | 49 | /** 50 | * Точка входа 51 | * @param argc Кол-во аргументов 52 | * @param argv Аргмуенты 53 | * @return Код исполнения 54 | */ 55 | int main(int argc, char* argv[]) 56 | { 57 | try { 58 | // Получение дескриптора исполняемого модуля программы 59 | g_hInstance = GetModuleHandle(nullptr); 60 | 61 | // Информация о классе 62 | WNDCLASSEX classInfo; 63 | classInfo.cbSize = sizeof(WNDCLASSEX); 64 | classInfo.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; 65 | classInfo.cbClsExtra = 0; 66 | classInfo.cbWndExtra = 0; 67 | classInfo.hInstance = g_hInstance; 68 | classInfo.hIcon = LoadIcon(g_hInstance, IDI_APPLICATION); 69 | classInfo.hIconSm = LoadIcon(g_hInstance, IDI_APPLICATION); 70 | classInfo.hCursor = LoadCursor(nullptr, IDC_ARROW); 71 | classInfo.hbrBackground = CreateSolidBrush(RGB(240, 240, 240)); 72 | classInfo.lpszMenuName = nullptr; 73 | classInfo.lpszClassName = g_strClassName; 74 | classInfo.lpfnWndProc = WindowProcedure; 75 | 76 | // Пытаемся зарегистрировать оконный класс 77 | if (!RegisterClassEx(&classInfo)) { 78 | g_lastError = ErrorCode::eClassRegistrationError; 79 | throw std::runtime_error("ERROR: Can't register window class."); 80 | } 81 | 82 | // Создание окна 83 | g_hwnd = CreateWindow( 84 | g_strClassName, 85 | g_strWindowCaption, 86 | WS_OVERLAPPEDWINDOW, 87 | 0, 0, 88 | 800, 600, 89 | nullptr, 90 | nullptr, 91 | g_hInstance, 92 | nullptr); 93 | 94 | // Если не удалось создать окно 95 | if (!g_hwnd) { 96 | g_lastError = ErrorCode::eWindowCreationError; 97 | throw std::runtime_error("ERROR: Can't create main application window."); 98 | } 99 | 100 | // Показать окно 101 | ShowWindow(g_hwnd, SW_SHOWNORMAL); 102 | 103 | // Получение контекста отрисовки 104 | g_hdc = GetDC(g_hwnd); 105 | 106 | // Размеры клиентской области окна 107 | RECT clientRect; 108 | GetClientRect(g_hwnd, &clientRect); 109 | 110 | // Создать буффер кадра 111 | auto frameBuffer = gfx::ImageBuffer(clientRect.right, clientRect.bottom, {0, 0, 0, 0}); 112 | std::cout << "INFO: Frame-buffer initialized (resolution : " << frameBuffer.getWidth() << "x" << frameBuffer.getHeight() << ", size : " << frameBuffer.getSize() << " bytes)" << std::endl; 113 | 114 | // Пропорции области вида 115 | //float aspectRatio = static_cast(clientRect.right) / static_cast(clientRect.bottom); 116 | 117 | // Точка в NDC-координатах 118 | math::Vec2 center = {0.0f,0.0f}; 119 | 120 | // Перевод в пространство экрана 121 | math::Vec2 centerScreen = math::NdcToScreen(center, frameBuffer.getWidth(), frameBuffer.getHeight()); 122 | 123 | // Пройтись по пикселям 124 | for(int y = 0; y < frameBuffer.getHeight(); y++) 125 | { 126 | for(int x = 0; x < frameBuffer.getWidth(); x++) 127 | { 128 | // Случайный цвет 129 | unsigned char r = rand() % 254 + 1; 130 | unsigned char g = rand() % 254 + 1; 131 | unsigned char b = rand() % 254 + 1; 132 | 133 | // Установить цвет 134 | gfx::SetPint(&frameBuffer,x,y,{b,g,r,0}); 135 | } 136 | } 137 | 138 | /** MAIN LOOP **/ 139 | 140 | // Оконное сообщение 141 | MSG msg = {}; 142 | 143 | // Запуск цикла 144 | while (true) 145 | { 146 | // Обработка оконных сообщений 147 | if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) 148 | { 149 | DispatchMessage(&msg); 150 | 151 | if (msg.message == WM_QUIT) { 152 | break; 153 | } 154 | } 155 | 156 | // Показ кадра 157 | PresentFrame(frameBuffer.getData(), static_cast(frameBuffer.getWidth()), static_cast(frameBuffer.getHeight()), g_hwnd); 158 | } 159 | } 160 | catch(std::exception& ex) 161 | { 162 | std::cout << ex.what() << std::endl; 163 | } 164 | 165 | // Уничтожение окна 166 | DestroyWindow(g_hwnd); 167 | // Вырегистрировать класс окна 168 | UnregisterClass(g_strClassName, g_hInstance); 169 | 170 | // Код выполнения/ошибки 171 | return static_cast(g_lastError); 172 | } 173 | 174 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 175 | 176 | /** 177 | * Обработчик оконных сообщений 178 | * @param hWnd Дескриптор окна 179 | * @param message Сообщение 180 | * @param wParam Параметр сообщения 181 | * @param lParam Параметр сообщения 182 | * @return Код выполнения 183 | */ 184 | LRESULT CALLBACK WindowProcedure(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) 185 | { 186 | switch (message) 187 | { 188 | case WM_DESTROY: 189 | PostQuitMessage(0); 190 | break; 191 | 192 | case WM_RBUTTONDOWN: 193 | case WM_LBUTTONDOWN: 194 | case WM_MBUTTONDOWN: 195 | // При нажатии любой кнопки мыши 196 | break; 197 | 198 | case WM_MOUSEMOVE: 199 | // При движении мыши 200 | // Если зажата левая кнопка мыши 201 | if (wParam & MK_LBUTTON) { 202 | } 203 | break; 204 | 205 | default: 206 | return DefWindowProc(hWnd, message, wParam, lParam); 207 | } 208 | 209 | return 0; 210 | } 211 | 212 | /** 213 | * Копирование информации о пикселях изображения в буфер "поверхности" окна 214 | * @param pixels Массив пикселей 215 | * @param width Ширина области 216 | * @param height Высота области 217 | * @param hWnd Дескриптор окна 218 | */ 219 | void PresentFrame(void *pixels, int width, int height, HWND hWnd) 220 | { 221 | // Получить хендл на временный bit-map (4 байта на пиксель) 222 | HBITMAP hBitMap = CreateBitmap( 223 | width, 224 | height, 225 | 1, 226 | 8 * 4, 227 | pixels); 228 | 229 | // Получить device context окна 230 | HDC hdc = GetDC(hWnd); 231 | 232 | // Временный DC для переноса bit-map'а 233 | HDC srcHdc = CreateCompatibleDC(hdc); 234 | 235 | // Связать bit-map с временным DC 236 | SelectObject(srcHdc, hBitMap); 237 | 238 | // Копировать содержимое временного DC в DC окна 239 | BitBlt( 240 | hdc, // HDC назначения 241 | 0, // Начало вставки по оси X 242 | 0, // Начало вставки по оси Y 243 | static_cast(width), // Ширина 244 | static_cast(height), // Высота 245 | srcHdc, // Исходный HDC (из которого будут копироваться данные) 246 | 0, // Начало считывания по оси X 247 | 0, // Начало считывания по оси Y 248 | SRCCOPY // Копировать 249 | ); 250 | 251 | // Уничтожить bit-map 252 | DeleteObject(hBitMap); 253 | // Уничтожить временный DC 254 | DeleteDC(srcHdc); 255 | // Уничтожить DC 256 | ReleaseDC(hWnd,hdc); 257 | } 258 | -------------------------------------------------------------------------------- /Sources/04_SamplePointProjection/Main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | /** 8 | * Коды ошибок 9 | */ 10 | enum ErrorCode 11 | { 12 | eNoErrors, 13 | eClassRegistrationError, 14 | eWindowCreationError, 15 | }; 16 | 17 | /// Дескриптор исполняемого модуля программы 18 | HINSTANCE g_hInstance = nullptr; 19 | /// Дескриптор осноного окна отрисовки 20 | HWND g_hwnd = nullptr; 21 | /// Дескриптор контекста отрисовки 22 | HDC g_hdc = nullptr; 23 | /// Наименование класса 24 | const char* g_strClassName = "MainWindowClass"; 25 | /// Заголовок окна 26 | const char* g_strWindowCaption = "DemoApp"; 27 | /// Код последней ошибки 28 | ErrorCode g_lastError = ErrorCode::eNoErrors; 29 | 30 | /** 31 | * Обработчик оконных сообщений 32 | * @param hWnd Дескриптор окна 33 | * @param message Сообщение 34 | * @param wParam Параметр сообщения 35 | * @param lParam Параметр сообщения 36 | * @return Код выполнения 37 | */ 38 | LRESULT CALLBACK WindowProcedure(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); 39 | 40 | /** 41 | * Копирование информации о пикселях изображения в буфер "поверхности" окна 42 | * @param pixels Массив пикселей 43 | * @param width Ширина области 44 | * @param height Высота области 45 | * @param hWnd Дескриптор окна 46 | */ 47 | void PresentFrame(void *pixels, int width, int height, HWND hWnd); 48 | 49 | /** 50 | * Точка входа 51 | * @param argc Кол-во аргументов 52 | * @param argv Аргмуенты 53 | * @return Код исполнения 54 | */ 55 | int main(int argc, char* argv[]) 56 | { 57 | try { 58 | // Получение дескриптора исполняемого модуля программы 59 | g_hInstance = GetModuleHandle(nullptr); 60 | 61 | // Информация о классе 62 | WNDCLASSEX classInfo; 63 | classInfo.cbSize = sizeof(WNDCLASSEX); 64 | classInfo.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; 65 | classInfo.cbClsExtra = 0; 66 | classInfo.cbWndExtra = 0; 67 | classInfo.hInstance = g_hInstance; 68 | classInfo.hIcon = LoadIcon(g_hInstance, IDI_APPLICATION); 69 | classInfo.hIconSm = LoadIcon(g_hInstance, IDI_APPLICATION); 70 | classInfo.hCursor = LoadCursor(nullptr, IDC_ARROW); 71 | classInfo.hbrBackground = CreateSolidBrush(RGB(240, 240, 240)); 72 | classInfo.lpszMenuName = nullptr; 73 | classInfo.lpszClassName = g_strClassName; 74 | classInfo.lpfnWndProc = WindowProcedure; 75 | 76 | // Пытаемся зарегистрировать оконный класс 77 | if (!RegisterClassEx(&classInfo)) { 78 | g_lastError = ErrorCode::eClassRegistrationError; 79 | throw std::runtime_error("ERROR: Can't register window class."); 80 | } 81 | 82 | // Создание окна 83 | g_hwnd = CreateWindow( 84 | g_strClassName, 85 | g_strWindowCaption, 86 | WS_OVERLAPPEDWINDOW, 87 | 0, 0, 88 | 800, 600, 89 | nullptr, 90 | nullptr, 91 | g_hInstance, 92 | nullptr); 93 | 94 | // Если не удалось создать окно 95 | if (!g_hwnd) { 96 | g_lastError = ErrorCode::eWindowCreationError; 97 | throw std::runtime_error("ERROR: Can't create main application window."); 98 | } 99 | 100 | // Показать окно 101 | ShowWindow(g_hwnd, SW_SHOWNORMAL); 102 | 103 | // Получение контекста отрисовки 104 | g_hdc = GetDC(g_hwnd); 105 | 106 | // Размеры клиентской области окна 107 | RECT clientRect; 108 | GetClientRect(g_hwnd, &clientRect); 109 | 110 | // Создать буффер кадра 111 | auto frameBuffer = gfx::ImageBuffer(clientRect.right, clientRect.bottom, {0, 0, 0, 0}); 112 | std::cout << "INFO: Frame-buffer initialized (resolution : " << frameBuffer.getWidth() << "x" << frameBuffer.getHeight() << ", size : " << frameBuffer.getSize() << " bytes)" << std::endl; 113 | 114 | // Пропорции области вида 115 | float aspectRatio = static_cast(clientRect.right) / static_cast(clientRect.bottom); 116 | 117 | // Положения вершин куба 118 | std::vector> vertices { 119 | {-1.0f,1.0f,1.0f}, 120 | {1.0f,1.0f,1.0f}, 121 | {1.0f,-1.0f,1.0f}, 122 | {-1.0f,-1.0f,1.0f}, 123 | 124 | {-1.0f,1.0f,-1.0f}, 125 | {1.0f,1.0f,-1.0f}, 126 | {1.0f,-1.0f,-1.0f}, 127 | {-1.0f,-1.0f,-1.0f} 128 | }; 129 | 130 | // Индексы (тройки вершин) 131 | std::vector indices { 132 | 0,1,2, 2,3,0, 133 | 1,5,6, 6,2,1, 134 | 5,4,7, 7,6,5, 135 | 4,0,3, 3,7,4, 136 | 4,5,1, 1,0,4, 137 | 3,2,6, 6,7,3 138 | }; 139 | 140 | // Пройти по всем индексам (шаг - 3 индекса) 141 | for(size_t i = 3; i <= indices.size(); i+=3) 142 | { 143 | // Пройтись по тройке индексов (по 2 чтобы игнорировать диагональные соединения) 144 | for(size_t j = 0; j < 2; j++) 145 | { 146 | // Получить положение двух точек 147 | auto p0 = vertices[indices[(i-3)+j]]; 148 | auto p1 = vertices[indices[(i-3)+((j + 1) % 3)]]; 149 | 150 | // Сдвигаем точки 151 | p0 = p0 + math::Vec3(0.0f,0.0f,-4.0f); 152 | p1 = p1 + math::Vec3(0.0f,0.0f,-4.0f); 153 | 154 | // Проекция положений двух точек 155 | // auto pp0 = math::ProjectOrthogonal(p0,-2.0f,2.0f,-2.0f,2.0f,0.1f,100.0f,aspectRatio); 156 | // auto pp1 = math::ProjectOrthogonal(p1,-2.0f,2.0f,-2.0f,2.0f,0.1f,100.0f,aspectRatio); 157 | 158 | auto pp0 = math::ProjectPerspective(p0,90.0f,0.1f,100.0f,aspectRatio); 159 | auto pp1 = math::ProjectPerspective(p1,90.0f,0.1f,100.0f,aspectRatio); 160 | 161 | // Положение в пространстве экрана 162 | auto sp0 = math::NdcToScreen({pp0.x, pp0.y}, frameBuffer.getWidth(), frameBuffer.getHeight()); 163 | auto sp1 = math::NdcToScreen({pp1.x, pp1.y}, frameBuffer.getWidth(), frameBuffer.getHeight()); 164 | 165 | // Написовать линию 166 | gfx::SetLine(&frameBuffer,sp0.x,sp0.y,sp1.x,sp1.y,{0,255,0,0},gfx::SAFE_CHECK_ALL_POINTS); 167 | } 168 | } 169 | 170 | /** MAIN LOOP **/ 171 | 172 | // Оконное сообщение 173 | MSG msg = {}; 174 | 175 | // Запуск цикла 176 | while (true) 177 | { 178 | // Обработка оконных сообщений 179 | if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) 180 | { 181 | DispatchMessage(&msg); 182 | 183 | if (msg.message == WM_QUIT) { 184 | break; 185 | } 186 | } 187 | 188 | // Показ кадра 189 | PresentFrame(frameBuffer.getData(), static_cast(frameBuffer.getWidth()), static_cast(frameBuffer.getHeight()), g_hwnd); 190 | } 191 | } 192 | catch(std::exception& ex) 193 | { 194 | std::cout << ex.what() << std::endl; 195 | } 196 | 197 | // Уничтожение окна 198 | DestroyWindow(g_hwnd); 199 | // Вырегистрировать класс окна 200 | UnregisterClass(g_strClassName, g_hInstance); 201 | 202 | // Код выполнения/ошибки 203 | return static_cast(g_lastError); 204 | } 205 | 206 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 207 | 208 | /** 209 | * Обработчик оконных сообщений 210 | * @param hWnd Дескриптор окна 211 | * @param message Сообщение 212 | * @param wParam Параметр сообщения 213 | * @param lParam Параметр сообщения 214 | * @return Код выполнения 215 | */ 216 | LRESULT CALLBACK WindowProcedure(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) 217 | { 218 | switch (message) 219 | { 220 | case WM_DESTROY: 221 | PostQuitMessage(0); 222 | break; 223 | 224 | case WM_RBUTTONDOWN: 225 | case WM_LBUTTONDOWN: 226 | case WM_MBUTTONDOWN: 227 | // При нажатии любой кнопки мыши 228 | break; 229 | 230 | case WM_MOUSEMOVE: 231 | // При движении мыши 232 | // Если зажата левая кнопка мыши 233 | if (wParam & MK_LBUTTON) { 234 | } 235 | break; 236 | 237 | default: 238 | return DefWindowProc(hWnd, message, wParam, lParam); 239 | } 240 | 241 | return 0; 242 | } 243 | 244 | /** 245 | * Копирование информации о пикселях изображения в буфер "поверхности" окна 246 | * @param pixels Массив пикселей 247 | * @param width Ширина области 248 | * @param height Высота области 249 | * @param hWnd Дескриптор окна 250 | */ 251 | void PresentFrame(void *pixels, int width, int height, HWND hWnd) 252 | { 253 | // Получить хендл на временный bit-map (4 байта на пиксель) 254 | HBITMAP hBitMap = CreateBitmap( 255 | width, 256 | height, 257 | 1, 258 | 8 * 4, 259 | pixels); 260 | 261 | // Получить device context окна 262 | HDC hdc = GetDC(hWnd); 263 | 264 | // Временный DC для переноса bit-map'а 265 | HDC srcHdc = CreateCompatibleDC(hdc); 266 | 267 | // Связать bit-map с временным DC 268 | SelectObject(srcHdc, hBitMap); 269 | 270 | // Копировать содержимое временного DC в DC окна 271 | BitBlt( 272 | hdc, // HDC назначения 273 | 0, // Начало вставки по оси X 274 | 0, // Начало вставки по оси Y 275 | static_cast(width), // Ширина 276 | static_cast(height), // Высота 277 | srcHdc, // Исходный HDC (из которого будут копироваться данные) 278 | 0, // Начало считывания по оси X 279 | 0, // Начало считывания по оси Y 280 | SRCCOPY // Копировать 281 | ); 282 | 283 | // Уничтожить bit-map 284 | DeleteObject(hBitMap); 285 | // Уничтожить временный DC 286 | DeleteDC(srcHdc); 287 | // Уничтожить DC 288 | ReleaseDC(hWnd,hdc); 289 | } 290 | -------------------------------------------------------------------------------- /Sources/03_SamplePointRotation/Main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | /** 10 | * Коды ошибок 11 | */ 12 | enum ErrorCode 13 | { 14 | eNoErrors, 15 | eClassRegistrationError, 16 | eWindowCreationError, 17 | }; 18 | 19 | /// Дескриптор исполняемого модуля программы 20 | HINSTANCE g_hInstance = nullptr; 21 | /// Дескриптор осноного окна отрисовки 22 | HWND g_hwnd = nullptr; 23 | /// Дескриптор контекста отрисовки 24 | HDC g_hdc = nullptr; 25 | /// Наименование класса 26 | const char* g_strClassName = "MainWindowClass"; 27 | /// Заголовок окна 28 | const char* g_strWindowCaption = "DemoApp"; 29 | /// Код последней ошибки 30 | ErrorCode g_lastError = ErrorCode::eNoErrors; 31 | /// Таймер 32 | tools::Timer* g_pTimer = nullptr; 33 | 34 | /** 35 | * Обработчик оконных сообщений 36 | * @param hWnd Дескриптор окна 37 | * @param message Сообщение 38 | * @param wParam Параметр сообщения 39 | * @param lParam Параметр сообщения 40 | * @return Код выполнения 41 | */ 42 | LRESULT CALLBACK WindowProcedure(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); 43 | 44 | /** 45 | * Копирование информации о пикселях изображения в буфер "поверхности" окна 46 | * @param pixels Массив пикселей 47 | * @param width Ширина области 48 | * @param height Высота области 49 | * @param hWnd Дескриптор окна 50 | */ 51 | void PresentFrame(void *pixels, int width, int height, HWND hWnd); 52 | 53 | /** 54 | * Точка входа 55 | * @param argc Кол-во аргументов 56 | * @param argv Аргмуенты 57 | * @return Код исполнения 58 | */ 59 | int main(int argc, char* argv[]) 60 | { 61 | try { 62 | // Получение дескриптора исполняемого модуля программы 63 | g_hInstance = GetModuleHandle(nullptr); 64 | 65 | // Информация о классе 66 | WNDCLASSEX classInfo; 67 | classInfo.cbSize = sizeof(WNDCLASSEX); 68 | classInfo.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; 69 | classInfo.cbClsExtra = 0; 70 | classInfo.cbWndExtra = 0; 71 | classInfo.hInstance = g_hInstance; 72 | classInfo.hIcon = LoadIcon(g_hInstance, IDI_APPLICATION); 73 | classInfo.hIconSm = LoadIcon(g_hInstance, IDI_APPLICATION); 74 | classInfo.hCursor = LoadCursor(nullptr, IDC_ARROW); 75 | classInfo.hbrBackground = CreateSolidBrush(RGB(240, 240, 240)); 76 | classInfo.lpszMenuName = nullptr; 77 | classInfo.lpszClassName = g_strClassName; 78 | classInfo.lpfnWndProc = WindowProcedure; 79 | 80 | // Пытаемся зарегистрировать оконный класс 81 | if (!RegisterClassEx(&classInfo)) { 82 | g_lastError = ErrorCode::eClassRegistrationError; 83 | throw std::runtime_error("ERROR: Can't register window class."); 84 | } 85 | 86 | // Создание окна 87 | g_hwnd = CreateWindow( 88 | g_strClassName, 89 | g_strWindowCaption, 90 | WS_OVERLAPPEDWINDOW, 91 | 0, 0, 92 | 800, 600, 93 | nullptr, 94 | nullptr, 95 | g_hInstance, 96 | nullptr); 97 | 98 | // Если не удалось создать окно 99 | if (!g_hwnd) { 100 | g_lastError = ErrorCode::eWindowCreationError; 101 | throw std::runtime_error("ERROR: Can't create main application window."); 102 | } 103 | 104 | // Показать окно 105 | ShowWindow(g_hwnd, SW_SHOWNORMAL); 106 | 107 | // Получение контекста отрисовки 108 | g_hdc = GetDC(g_hwnd); 109 | 110 | // Размеры клиентской области окна 111 | RECT clientRect; 112 | GetClientRect(g_hwnd, &clientRect); 113 | 114 | // Создать буффер кадра 115 | auto frameBuffer = gfx::ImageBuffer(clientRect.right, clientRect.bottom, {0, 0, 0, 0}); 116 | std::cout << "INFO: Frame-buffer initialized (resolution : " << frameBuffer.getWidth() << "x" << frameBuffer.getHeight() << ", size : " << frameBuffer.getSize() << " bytes)" << std::endl; 117 | 118 | // Пропорции области вида 119 | float aspectRatio = static_cast(clientRect.right) / static_cast(clientRect.bottom); 120 | 121 | // Центральная точка 122 | math::Vec2 center = {0.0f,0.0f}; 123 | 124 | // Точки квадрата 125 | std::vector> quadPoints = { 126 | {-0.5f,-0.5f}, 127 | {-0.5f,0.5f}, 128 | {0.5f,0.5f}, 129 | {0.5f,-0.5f} 130 | }; 131 | 132 | // Текущий угол поворота 133 | float rotationAngle = 0.0f; 134 | 135 | // Скорость вращения 136 | float angleSpeed = 0.03f; 137 | 138 | /** MAIN LOOP **/ 139 | 140 | // Таймер основного цикла (для выяснения временной дельты и FPS) 141 | g_pTimer = new tools::Timer(); 142 | 143 | // Оконное сообщение 144 | MSG msg = {}; 145 | 146 | // Запуск цикла 147 | while (true) 148 | { 149 | // Обновить таймер 150 | g_pTimer->updateTimer(); 151 | 152 | // Обработка оконных сообщений 153 | if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) 154 | { 155 | DispatchMessage(&msg); 156 | 157 | if (msg.message == WM_QUIT) { 158 | break; 159 | } 160 | } 161 | 162 | // Поскольку показ FPS на окне уменьшает FPS - делаем это только тогда когда счетчик готов (примерно 1 раз в секунду) 163 | if (g_pTimer->isFpsCounterReady()){ 164 | std::string fps = std::string(g_strWindowCaption).append(" (").append(std::to_string(g_pTimer->getFps())).append(" FPS)"); 165 | SetWindowTextA(g_hwnd, fps.c_str()); 166 | } 167 | 168 | // Точки после преобразований 169 | std::vector> pointsTransformed; 170 | 171 | // Обновление сцены 172 | { 173 | // Приращение угла поворота 174 | rotationAngle += (angleSpeed * g_pTimer->getDelta()); 175 | 176 | // Пройтись по точкам квадрата 177 | for(const auto& point : quadPoints) 178 | { 179 | // Повернуть точку на заданный угол 180 | auto p = math::Rotate2D(point,rotationAngle); 181 | // Корректировать положение точки с учетом пропорций жкрана (чтобы избежать растяжения) 182 | p.x /= aspectRatio; 183 | // Добавить в массив обработанных точек 184 | pointsTransformed.push_back(p); 185 | } 186 | } 187 | 188 | // Рисование примитивов 189 | { 190 | // Нарисовать окружность в центре 191 | auto centerScreen = math::NdcToScreen(center,frameBuffer.getWidth(),frameBuffer.getHeight()); 192 | gfx::SetCircle(&frameBuffer,centerScreen.x,centerScreen.y,60,{0,0,255}); 193 | 194 | // Пройтись по всем трансформированным точкам 195 | for(size_t i = 0; i < pointsTransformed.size(); i++) 196 | { 197 | // Индексы двух точек 198 | size_t i0 = i; 199 | size_t i1 = ((i+1) > (pointsTransformed.size()-1) ? 0 : i+1); 200 | 201 | // Перевести точки в координаты экрана 202 | auto ps0 = math::NdcToScreen(pointsTransformed[i0],frameBuffer.getWidth(),frameBuffer.getHeight()); 203 | auto ps1 = math::NdcToScreen(pointsTransformed[i1],frameBuffer.getWidth(),frameBuffer.getHeight()); 204 | 205 | // Нарисовать линию соединяющую точки 206 | gfx::SetLine(&frameBuffer,ps0.x,ps0.y,ps1.x,ps1.y,{0,255,0}); 207 | } 208 | } 209 | 210 | // Показ кадра 211 | PresentFrame(frameBuffer.getData(), static_cast(frameBuffer.getWidth()), static_cast(frameBuffer.getHeight()), g_hwnd); 212 | 213 | // Очистка кадра 214 | frameBuffer.clear({0,0,0,0}); 215 | } 216 | } 217 | catch(std::exception& ex) 218 | { 219 | std::cout << ex.what() << std::endl; 220 | } 221 | 222 | // Уничтожение окна 223 | DestroyWindow(g_hwnd); 224 | // Вырегистрировать класс окна 225 | UnregisterClass(g_strClassName, g_hInstance); 226 | 227 | // Код выполнения/ошибки 228 | return static_cast(g_lastError); 229 | } 230 | 231 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 232 | 233 | /** 234 | * Обработчик оконных сообщений 235 | * @param hWnd Дескриптор окна 236 | * @param message Сообщение 237 | * @param wParam Параметр сообщения 238 | * @param lParam Параметр сообщения 239 | * @return Код выполнения 240 | */ 241 | LRESULT CALLBACK WindowProcedure(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) 242 | { 243 | switch (message) 244 | { 245 | case WM_DESTROY: 246 | PostQuitMessage(0); 247 | break; 248 | 249 | case WM_RBUTTONDOWN: 250 | case WM_LBUTTONDOWN: 251 | case WM_MBUTTONDOWN: 252 | // При нажатии любой кнопки мыши 253 | break; 254 | 255 | case WM_MOUSEMOVE: 256 | // При движении мыши 257 | // Если зажата левая кнопка мыши 258 | if (wParam & MK_LBUTTON) { 259 | } 260 | break; 261 | 262 | default: 263 | return DefWindowProc(hWnd, message, wParam, lParam); 264 | } 265 | 266 | return 0; 267 | } 268 | 269 | /** 270 | * Копирование информации о пикселях изображения в буфер "поверхности" окна 271 | * @param pixels Массив пикселей 272 | * @param width Ширина области 273 | * @param height Высота области 274 | * @param hWnd Дескриптор окна 275 | */ 276 | void PresentFrame(void *pixels, int width, int height, HWND hWnd) 277 | { 278 | // Получить хендл на временный bit-map (4 байта на пиксель) 279 | HBITMAP hBitMap = CreateBitmap( 280 | width, 281 | height, 282 | 1, 283 | 8 * 4, 284 | pixels); 285 | 286 | // Получить device context окна 287 | HDC hdc = GetDC(hWnd); 288 | 289 | // Временный DC для переноса bit-map'а 290 | HDC srcHdc = CreateCompatibleDC(hdc); 291 | 292 | // Связать bit-map с временным DC 293 | SelectObject(srcHdc, hBitMap); 294 | 295 | // Копировать содержимое временного DC в DC окна 296 | BitBlt( 297 | hdc, // HDC назначения 298 | 0, // Начало вставки по оси X 299 | 0, // Начало вставки по оси Y 300 | static_cast(width), // Ширина 301 | static_cast(height), // Высота 302 | srcHdc, // Исходный HDC (из которого будут копироваться данные) 303 | 0, // Начало считывания по оси X 304 | 0, // Начало считывания по оси Y 305 | SRCCOPY // Копировать 306 | ); 307 | 308 | // Уничтожить bit-map 309 | DeleteObject(hBitMap); 310 | // Уничтожить временный DC 311 | DeleteDC(srcHdc); 312 | // Уничтожить DC 313 | ReleaseDC(hWnd,hdc); 314 | } 315 | -------------------------------------------------------------------------------- /Sources/07_RandomVectorWithinCone/Main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | /** 13 | * Коды ошибок 14 | */ 15 | enum ErrorCode 16 | { 17 | eNoErrors, 18 | eClassRegistrationError, 19 | eWindowCreationError, 20 | }; 21 | 22 | /// Дескриптор исполняемого модуля программы 23 | HINSTANCE g_hInstance = nullptr; 24 | /// Дескриптор осноного окна отрисовки 25 | HWND g_hwnd = nullptr; 26 | /// Дескриптор контекста отрисовки 27 | HDC g_hdc = nullptr; 28 | /// Наименование класса 29 | const char* g_strClassName = "MainWindowClass"; 30 | /// Заголовок окна 31 | const char* g_strWindowCaption = "DemoApp"; 32 | /// Код последней ошибки 33 | ErrorCode g_lastError = ErrorCode::eNoErrors; 34 | 35 | /** 36 | * Обработчик оконных сообщений 37 | * @param hWnd Дескриптор окна 38 | * @param message Сообщение 39 | * @param wParam Параметр сообщения 40 | * @param lParam Параметр сообщения 41 | * @return Код выполнения 42 | */ 43 | LRESULT CALLBACK WindowProcedure(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); 44 | 45 | /** 46 | * Копирование информации о пикселях изображения в буфер "поверхности" окна 47 | * @param pixels Массив пикселей 48 | * @param width Ширина области 49 | * @param height Высота области 50 | * @param hWnd Дескриптор окна 51 | */ 52 | void PresentFrame(void *pixels, int width, int height, HWND hWnd); 53 | 54 | /** 55 | * Нарисовать линейные примитивы 56 | * @param imageBuffer Указатель на кадровый буфер 57 | * @param projectedToNdcPoints Точки спроецированные в NDC пространство 58 | * @param pointsPerPrimitive Количество точек на один примитив 59 | */ 60 | void DrawLinePrimitives(gfx::ImageBuffer* imageBuffer, const std::vector>& projectedToNdcPoints, uint32_t pointsPerPrimitive); 61 | 62 | /** 63 | * Спроецировать точки в NDC 64 | * @param mProjection Матрица проекции 65 | * @param points Массив точек в пространстве мира 66 | * @return Массив спроецированных точек (без учета глубины) 67 | */ 68 | std::vector> ProjectPoints(const math::Mat4& mProjection, const std::vector>& points); 69 | 70 | /** 71 | * Точка входа 72 | * @param argc Кол-во аргументов 73 | * @param argv Аргмуенты 74 | * @return Код исполнения 75 | */ 76 | int main(int argc, char* argv[]) 77 | { 78 | try { 79 | // Получение дескриптора исполняемого модуля программы 80 | g_hInstance = GetModuleHandle(nullptr); 81 | 82 | // Информация о классе 83 | WNDCLASSEX classInfo; 84 | classInfo.cbSize = sizeof(WNDCLASSEX); 85 | classInfo.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; 86 | classInfo.cbClsExtra = 0; 87 | classInfo.cbWndExtra = 0; 88 | classInfo.hInstance = g_hInstance; 89 | classInfo.hIcon = LoadIcon(g_hInstance, IDI_APPLICATION); 90 | classInfo.hIconSm = LoadIcon(g_hInstance, IDI_APPLICATION); 91 | classInfo.hCursor = LoadCursor(nullptr, IDC_ARROW); 92 | classInfo.hbrBackground = CreateSolidBrush(RGB(240, 240, 240)); 93 | classInfo.lpszMenuName = nullptr; 94 | classInfo.lpszClassName = g_strClassName; 95 | classInfo.lpfnWndProc = WindowProcedure; 96 | 97 | // Пытаемся зарегистрировать оконный класс 98 | if (!RegisterClassEx(&classInfo)) { 99 | g_lastError = ErrorCode::eClassRegistrationError; 100 | throw std::runtime_error("ERROR: Can't register window class."); 101 | } 102 | 103 | // Создание окна 104 | g_hwnd = CreateWindow( 105 | g_strClassName, 106 | g_strWindowCaption, 107 | WS_OVERLAPPEDWINDOW, 108 | 0, 0, 109 | 800, 600, 110 | nullptr, 111 | nullptr, 112 | g_hInstance, 113 | nullptr); 114 | 115 | // Если не удалось создать окно 116 | if (!g_hwnd) { 117 | g_lastError = ErrorCode::eWindowCreationError; 118 | throw std::runtime_error("ERROR: Can't create main application window."); 119 | } 120 | 121 | // Показать окно 122 | ShowWindow(g_hwnd, SW_SHOWNORMAL); 123 | 124 | // Получение контекста отрисовки 125 | g_hdc = GetDC(g_hwnd); 126 | 127 | // Размеры клиентской области окна 128 | RECT clientRect; 129 | GetClientRect(g_hwnd, &clientRect); 130 | 131 | // Создать буффер кадра 132 | auto frameBuffer = gfx::ImageBuffer(clientRect.right, clientRect.bottom, {0, 0, 0, 0}); 133 | std::cout << "INFO: Frame-buffer initialized (resolution : " << frameBuffer.getWidth() << "x" << frameBuffer.getHeight() << ", size : " << frameBuffer.getSize() << " bytes)" << std::endl; 134 | 135 | // Пропорции области вида 136 | float aspectRatio = static_cast(clientRect.right) / static_cast(clientRect.bottom); 137 | 138 | // Матрица проекции 139 | math::Mat4 mProjection = math::GetProjectionMatPerspective(90.0f,aspectRatio,0.0f,100.0f); 140 | 141 | // Точки линий 142 | std::vector> linePoints { 143 | }; 144 | 145 | // Точки квадрата (квадрат впереди на -4 единицы) 146 | std::vector> quadPoints = { 147 | {-1.0f,1.0f,-2.0f}, 148 | {1.0f,1.0f,-2.0f}, 149 | {1.0f,-1.0f,-2.0f}, 150 | {-1.0f,-1.0f,-2.0f} 151 | }; 152 | 153 | // Положение источника света 154 | math::Vec3 lightPosition = {0.0f,0.0f,-2.0f}; 155 | // Положение точки из которой в сторону источника будут сгенерированы линии 156 | math::Vec3 pointPosition = {0.0f,0.0f,-0.1f}; 157 | // Радиус источника света 158 | float lightRadius = 1.0f; 159 | // Вектор направления к источнику 160 | auto toLight = math::Normalize(lightPosition - pointPosition); 161 | 162 | // Генератор случайных чисел 163 | std::chrono::milliseconds ms = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()); 164 | std::default_random_engine rndEngine(static_cast(ms.count())); 165 | 166 | /** MAIN LOOP **/ 167 | 168 | // Оконное сообщение 169 | MSG msg = {}; 170 | 171 | // Запуск цикла 172 | while (true) 173 | { 174 | // Обработка оконных сообщений 175 | if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) 176 | { 177 | DispatchMessage(&msg); 178 | 179 | if (msg.message == WM_QUIT) { 180 | break; 181 | } 182 | } 183 | 184 | // Очистить точки 185 | linePoints.clear(); 186 | 187 | // Рандомизация 188 | ms = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()); 189 | rndEngine.seed(static_cast(ms.count())); 190 | std::uniform_real_distribution vectorDist(-1.0f,1.0f); 191 | std::uniform_real_distribution radiusDist(0.0f,1.0f); 192 | 193 | for(uint32_t i = 0; i < 100; i++) 194 | { 195 | // Случайные значения компонентов вектора для генерации перпендикуряра 196 | float xR = vectorDist(rndEngine); 197 | float yR = vectorDist(rndEngine); 198 | float zR = vectorDist(rndEngine); 199 | 200 | // Случайное значение множителя радиуса 201 | float rR = radiusDist(rndEngine); 202 | 203 | // Получить случайную точку диске 204 | auto randomPerpendicular = math::Normalize(math::Cross(toLight, toLight + math::Vec3(xR, yR, zR))); 205 | auto edgePoint = lightPosition + (randomPerpendicular * lightRadius * rR); 206 | 207 | linePoints.push_back(lightPosition); 208 | linePoints.push_back(edgePoint); 209 | } 210 | 211 | 212 | // Точки после преобразований (2D) 213 | std::vector> quadPointsTransformed = ProjectPoints(mProjection, quadPoints); 214 | std::vector> linePointsTransformed = ProjectPoints(mProjection, linePoints); 215 | // Нарисовать спроецированные примитивы 216 | DrawLinePrimitives(&frameBuffer,quadPointsTransformed,4); 217 | DrawLinePrimitives(&frameBuffer,linePointsTransformed,2); 218 | 219 | // Показ кадра 220 | PresentFrame(frameBuffer.getData(), static_cast(frameBuffer.getWidth()), static_cast(frameBuffer.getHeight()), g_hwnd); 221 | 222 | // Очистка кадра 223 | frameBuffer.clear({0,0,0,0}); 224 | } 225 | } 226 | catch(std::exception& ex) 227 | { 228 | std::cout << ex.what() << std::endl; 229 | } 230 | 231 | // Уничтожение окна 232 | DestroyWindow(g_hwnd); 233 | // Вырегистрировать класс окна 234 | UnregisterClass(g_strClassName, g_hInstance); 235 | 236 | // Код выполнения/ошибки 237 | return static_cast(g_lastError); 238 | } 239 | 240 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 241 | 242 | /** 243 | * Обработчик оконных сообщений 244 | * @param hWnd Дескриптор окна 245 | * @param message Сообщение 246 | * @param wParam Параметр сообщения 247 | * @param lParam Параметр сообщения 248 | * @return Код выполнения 249 | */ 250 | LRESULT CALLBACK WindowProcedure(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) 251 | { 252 | switch (message) 253 | { 254 | case WM_DESTROY: 255 | PostQuitMessage(0); 256 | break; 257 | 258 | case WM_RBUTTONDOWN: 259 | case WM_LBUTTONDOWN: 260 | case WM_MBUTTONDOWN: 261 | // При нажатии любой кнопки мыши 262 | break; 263 | 264 | case WM_MOUSEMOVE: 265 | // При движении мыши 266 | // Если зажата левая кнопка мыши 267 | if (wParam & MK_LBUTTON) { 268 | } 269 | break; 270 | 271 | default: 272 | return DefWindowProc(hWnd, message, wParam, lParam); 273 | } 274 | 275 | return 0; 276 | } 277 | 278 | /** 279 | * Копирование информации о пикселях изображения в буфер "поверхности" окна 280 | * @param pixels Массив пикселей 281 | * @param width Ширина области 282 | * @param height Высота области 283 | * @param hWnd Дескриптор окна 284 | */ 285 | void PresentFrame(void *pixels, int width, int height, HWND hWnd) 286 | { 287 | // Получить хендл на временный bit-map (4 байта на пиксель) 288 | HBITMAP hBitMap = CreateBitmap( 289 | width, 290 | height, 291 | 1, 292 | 8 * 4, 293 | pixels); 294 | 295 | // Получить device context окна 296 | HDC hdc = GetDC(hWnd); 297 | 298 | // Временный DC для переноса bit-map'а 299 | HDC srcHdc = CreateCompatibleDC(hdc); 300 | 301 | // Связать bit-map с временным DC 302 | SelectObject(srcHdc, hBitMap); 303 | 304 | // Копировать содержимое временного DC в DC окна 305 | BitBlt( 306 | hdc, // HDC назначения 307 | 0, // Начало вставки по оси X 308 | 0, // Начало вставки по оси Y 309 | static_cast(width), // Ширина 310 | static_cast(height), // Высота 311 | srcHdc, // Исходный HDC (из которого будут копироваться данные) 312 | 0, // Начало считывания по оси X 313 | 0, // Начало считывания по оси Y 314 | SRCCOPY // Копировать 315 | ); 316 | 317 | // Уничтожить bit-map 318 | DeleteObject(hBitMap); 319 | // Уничтожить временный DC 320 | DeleteDC(srcHdc); 321 | // Уничтожить DC 322 | ReleaseDC(hWnd,hdc); 323 | } 324 | 325 | /** 326 | * Нарисовать линейные примитивы 327 | * @param imageBuffer Указатель на кадровый буфер 328 | * @param projectedToNdcPoints Точки спроецированные в NDC пространство 329 | * @param pointsPerPrimitive Количество точек на один примитив 330 | */ 331 | void DrawLinePrimitives(gfx::ImageBuffer* imageBuffer, const std::vector> &projectedToNdcPoints, uint32_t pointsPerPrimitive) 332 | { 333 | for(size_t i = 0; i < projectedToNdcPoints.size(); i+=pointsPerPrimitive) 334 | { 335 | for(size_t j = i; j < i + pointsPerPrimitive; j++) 336 | { 337 | // Индексы двух точек 338 | size_t i0 = j; 339 | size_t i1 = ((j+1) > (i+pointsPerPrimitive-1) ? i : j+1); 340 | 341 | // Перевести точки в координаты экрана 342 | auto ps0 = math::NdcToScreen(projectedToNdcPoints[i0],imageBuffer->getWidth(),imageBuffer->getHeight()); 343 | auto ps1 = math::NdcToScreen(projectedToNdcPoints[i1],imageBuffer->getWidth(),imageBuffer->getHeight()); 344 | 345 | // Нарисовать линию соединяющую точки 346 | gfx::SetLine(imageBuffer,ps0.x,ps0.y,ps1.x,ps1.y,{0,255,0}); 347 | } 348 | } 349 | } 350 | 351 | /** 352 | * Спроецировать точки в NDC 353 | * @param mProjection Матрица проекции 354 | * @param points Массив точек в пространстве мира 355 | * @return Массив спроецированных точек (без учета глубины) 356 | */ 357 | std::vector> ProjectPoints(const math::Mat4 &mProjection, const std::vector> &points) 358 | { 359 | std::vector> result{}; 360 | 361 | for(const auto& point : points) 362 | { 363 | // Проекция 364 | auto vt = mProjection * math::Vec4({point.x,point.y,point.z,1.0f}); 365 | // Перспективное деление 366 | vt = vt / vt.w; 367 | // Добавить в итоговый массив 368 | result.emplace_back(vt.x,vt.y); 369 | } 370 | 371 | return result; 372 | } 373 | -------------------------------------------------------------------------------- /Sources/06_SampleSkeletalBasics/Main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "Skeleton.hpp" 10 | 11 | 12 | /** 13 | * Коды ошибок 14 | */ 15 | enum ErrorCode 16 | { 17 | eNoErrors, 18 | eClassRegistrationError, 19 | eWindowCreationError, 20 | }; 21 | 22 | /// Дескриптор исполняемого модуля программы 23 | HINSTANCE g_hInstance = nullptr; 24 | /// Дескриптор осноного окна отрисовки 25 | HWND g_hwnd = nullptr; 26 | /// Дескриптор контекста отрисовки 27 | HDC g_hdc = nullptr; 28 | /// Наименование класса 29 | const char* g_strClassName = "MainWindowClass"; 30 | /// Заголовок окна 31 | const char* g_strWindowCaption = "DemoApp"; 32 | /// Код последней ошибки 33 | ErrorCode g_lastError = ErrorCode::eNoErrors; 34 | /// Таймер 35 | tools::Timer* g_pTimer = nullptr; 36 | 37 | /** 38 | * Обработчик оконных сообщений 39 | * @param hWnd Дескриптор окна 40 | * @param message Сообщение 41 | * @param wParam Параметр сообщения 42 | * @param lParam Параметр сообщения 43 | * @return Код выполнения 44 | */ 45 | LRESULT CALLBACK WindowProcedure(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); 46 | 47 | /** 48 | * Копирование информации о пикселях изображения в буфер "поверхности" окна 49 | * @param pixels Массив пикселей 50 | * @param width Ширина области 51 | * @param height Высота области 52 | * @param hWnd Дескриптор окна 53 | */ 54 | void PresentFrame(void *pixels, int width, int height, HWND hWnd); 55 | 56 | /** 57 | * Простое описание вершины 58 | * В отличии от реальных примеров, здесь отсутствуют веса и вершина может принадлежать только одной кости 59 | */ 60 | struct Vertex 61 | { 62 | /// Положение в пространстве 63 | math::Vec3 position; 64 | 65 | /// Индек кости, к которой привязана вершины 66 | size_t boneId = 0; 67 | }; 68 | 69 | /** 70 | * Точка входа 71 | * @param argc Кол-во аргументов 72 | * @param argv Аргмуенты 73 | * @return Код исполнения 74 | */ 75 | int main(int argc, char* argv[]) 76 | { 77 | try { 78 | // Получение дескриптора исполняемого модуля программы 79 | g_hInstance = GetModuleHandle(nullptr); 80 | 81 | // Информация о классе 82 | WNDCLASSEX classInfo; 83 | classInfo.cbSize = sizeof(WNDCLASSEX); 84 | classInfo.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; 85 | classInfo.cbClsExtra = 0; 86 | classInfo.cbWndExtra = 0; 87 | classInfo.hInstance = g_hInstance; 88 | classInfo.hIcon = LoadIcon(g_hInstance, IDI_APPLICATION); 89 | classInfo.hIconSm = LoadIcon(g_hInstance, IDI_APPLICATION); 90 | classInfo.hCursor = LoadCursor(nullptr, IDC_ARROW); 91 | classInfo.hbrBackground = CreateSolidBrush(RGB(240, 240, 240)); 92 | classInfo.lpszMenuName = nullptr; 93 | classInfo.lpszClassName = g_strClassName; 94 | classInfo.lpfnWndProc = WindowProcedure; 95 | 96 | // Пытаемся зарегистрировать оконный класс 97 | if (!RegisterClassEx(&classInfo)) { 98 | g_lastError = ErrorCode::eClassRegistrationError; 99 | throw std::runtime_error("ERROR: Can't register window class."); 100 | } 101 | 102 | // Создание окна 103 | g_hwnd = CreateWindow( 104 | g_strClassName, 105 | g_strWindowCaption, 106 | WS_OVERLAPPEDWINDOW, 107 | 0, 0, 108 | 800, 600, 109 | nullptr, 110 | nullptr, 111 | g_hInstance, 112 | nullptr); 113 | 114 | // Если не удалось создать окно 115 | if (!g_hwnd) { 116 | g_lastError = ErrorCode::eWindowCreationError; 117 | throw std::runtime_error("ERROR: Can't create main application window."); 118 | } 119 | 120 | // Показать окно 121 | ShowWindow(g_hwnd, SW_SHOWNORMAL); 122 | 123 | // Получение контекста отрисовки 124 | g_hdc = GetDC(g_hwnd); 125 | 126 | // Размеры клиентской области окна 127 | RECT clientRect; 128 | GetClientRect(g_hwnd, &clientRect); 129 | 130 | // Создать буффер кадра 131 | auto frameBuffer = gfx::ImageBuffer(clientRect.right, clientRect.bottom, {0, 0, 0, 0}); 132 | std::cout << "INFO: Frame-buffer initialized (resolution : " << frameBuffer.getWidth() << "x" << frameBuffer.getHeight() << ", size : " << frameBuffer.getSize() << " bytes)" << std::endl; 133 | 134 | // Пропорции области вида 135 | float aspectRatio = static_cast(clientRect.right) / static_cast(clientRect.bottom); 136 | 137 | /// И Н И Ц И А Л И З А Ц И Я С Ц Е Н Ы 138 | 139 | // Текущий угол поворота 140 | float rotationAngle = 0.0f; 141 | 142 | // Скорость вращения 143 | float angleSpeed = 0.03f; 144 | 145 | // Матрица проекции 146 | math::Mat4 mProjection = math::GetProjectionMatOrthogonal(-8.0f,8.0f,-8.0f,8.0f,0.1f,100.0f,aspectRatio); 147 | 148 | // Набор вершин 149 | std::vector vertices = { 150 | // Первый квдрат принадлежит корневой кости 151 | {{-1.0f,-1.0f,0.0f},0}, 152 | {{-1.0f,1.0f,0.0f},0}, 153 | {{1.0f,1.0f,0.0f},0}, 154 | {{1.0f,-1.0f,0.0f},0}, 155 | 156 | // 2-й квадрат принадлежит кости 1 157 | {{-1.0f,1.5f,0.0f},1}, 158 | {{-1.0f,3.5f,0.0f},1}, 159 | {{1.0f,3.5f,0.0f},1}, 160 | {{1.0f,1.5f,0.0f},1}, 161 | 162 | // 3-й квадрат принадлежит кости 2 163 | {{-1.0f,4.0f,0.0f},2}, 164 | {{-1.0f,6.0f,0.0f},2}, 165 | {{1.0f,6.0f,0.0f},2}, 166 | {{1.0f,4.0f,0.0f},2}, 167 | }; 168 | 169 | // Инициализация скелета (скелет из 3 суставов/костей) 170 | Skeleton skeleton(3); 171 | skeleton.getRootBone() 172 | ->addChildBone(1,math::GetTranslationMat4({0.0f,2.5f,0.0f})) 173 | ->addChildBone(2,math::GetTranslationMat4({0.0f,2.5f,0.0f})); 174 | 175 | 176 | /** MAIN LOOP **/ 177 | 178 | // Таймер основного цикла (для выяснения временной дельты и FPS) 179 | g_pTimer = new tools::Timer(); 180 | 181 | // Оконное сообщение 182 | MSG msg = {}; 183 | 184 | // Запуск цикла 185 | while (true) 186 | { 187 | // Обновить таймер 188 | g_pTimer->updateTimer(); 189 | 190 | // Обработка оконных сообщений 191 | if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) 192 | { 193 | DispatchMessage(&msg); 194 | 195 | if (msg.message == WM_QUIT) { 196 | break; 197 | } 198 | } 199 | 200 | // Поскольку показ FPS на окне уменьшает FPS - делаем это только тогда когда счетчик готов (примерно 1 раз в секунду) 201 | if (g_pTimer->isFpsCounterReady()){ 202 | std::string fps = std::string(g_strWindowCaption).append(" (").append(std::to_string(g_pTimer->getFps())).append(" FPS)"); 203 | SetWindowTextA(g_hwnd, fps.c_str()); 204 | } 205 | 206 | /// А Н И М А Ц И Я 207 | 208 | // Приращение угла поворота 209 | rotationAngle += (angleSpeed * g_pTimer->getDelta()); 210 | 211 | // Повороты суставов скелета 212 | skeleton.getRootBone()->setLocalTransform(math::GetRotationMat4({0.0f,0.0f,rotationAngle})); 213 | skeleton.getRootBone()->getChildrenBones().back()->setLocalTransform(math::GetRotationMat4({0.0f,0.0f,rotationAngle})); 214 | skeleton.getRootBone()->getChildrenBones().back()->getChildrenBones().back()->setLocalTransform(math::GetRotationMat4({0.0f,0.0f,rotationAngle})); 215 | 216 | /// D R A W 217 | 218 | // Точки после преобразований (2D) 219 | std::vector> pointsTransformed; 220 | 221 | // Дополнительные точки (для отметки костей) 222 | std::vector> bonesPointsTransformed; 223 | 224 | // Трансформация вершин 225 | // В данном цикле проходим по вершинам и применям к ним трансформации соответствующих костей 226 | for(const auto& v : vertices) 227 | { 228 | auto vt = mProjection * skeleton.getFinalBoneTransforms()[v.boneId] * math::Vec4({v.position.x,v.position.y,v.position.z,1.0f}); 229 | pointsTransformed.emplace_back(vt.x,vt.y); 230 | } 231 | 232 | // В данном цикле просто добавляем точки для дальнейшей индикации костей 233 | // Для этого используем нулевые точки в пространстве кости, и соостветствующие матрицы (fromBoneSpace - true) 234 | for(const auto& t : skeleton.getFinalBoneTransforms(true)) 235 | { 236 | auto vt = mProjection * t * math::Vec4({0.0f,0.0f,0.0f,1.0f}); 237 | bonesPointsTransformed.emplace_back(vt.x,vt.y); 238 | } 239 | 240 | 241 | // Рисование примитивов по преобразовнным точкам 242 | size_t pointsPerPrimitive = 4; 243 | for(size_t i = 0; i < pointsTransformed.size(); i+=pointsPerPrimitive) 244 | { 245 | for(size_t j = i; j < i + pointsPerPrimitive; j++) 246 | { 247 | // Индексы двух точек 248 | size_t i0 = j; 249 | size_t i1 = ((j+1) > (i+pointsPerPrimitive-1) ? i : j+1); 250 | 251 | // Перевести точки в координаты экрана 252 | auto ps0 = math::NdcToScreen(pointsTransformed[i0],frameBuffer.getWidth(),frameBuffer.getHeight()); 253 | auto ps1 = math::NdcToScreen(pointsTransformed[i1],frameBuffer.getWidth(),frameBuffer.getHeight()); 254 | 255 | // Нарисовать линию соединяющую точки 256 | gfx::SetLine(&frameBuffer,ps0.x,ps0.y,ps1.x,ps1.y,{0,255,0}); 257 | } 258 | } 259 | 260 | // Индикация костей 261 | for(size_t i = 0; i < bonesPointsTransformed.size(); i++) 262 | { 263 | // Индексы двух точек 264 | size_t i0 = i; 265 | size_t i1 = i+1; 266 | 267 | if(i1 < bonesPointsTransformed.size()) 268 | { 269 | // Перевести точки в координаты экрана 270 | auto ps0 = math::NdcToScreen(bonesPointsTransformed[i0],frameBuffer.getWidth(),frameBuffer.getHeight()); 271 | auto ps1 = math::NdcToScreen(bonesPointsTransformed[i1],frameBuffer.getWidth(),frameBuffer.getHeight()); 272 | 273 | // Круги в местах точек 274 | if(i == 0) gfx::SetCircle(&frameBuffer,ps0.x,ps0.y,5,{0,0,255}); 275 | gfx::SetCircle(&frameBuffer,ps1.x,ps1.y,5,{0,0,255}); 276 | 277 | // Нарисовать линию соединяющую точки 278 | gfx::SetLine(&frameBuffer,ps0.x,ps0.y,ps1.x,ps1.y,{0,0,255}); 279 | } 280 | } 281 | 282 | // Показ кадра 283 | PresentFrame(frameBuffer.getData(), static_cast(frameBuffer.getWidth()), static_cast(frameBuffer.getHeight()), g_hwnd); 284 | 285 | // Очистка кадра 286 | frameBuffer.clear({0,0,0,0}); 287 | } 288 | } 289 | catch(std::exception& ex) 290 | { 291 | std::cout << ex.what() << std::endl; 292 | } 293 | 294 | // Уничтожение окна 295 | DestroyWindow(g_hwnd); 296 | // Вырегистрировать класс окна 297 | UnregisterClass(g_strClassName, g_hInstance); 298 | 299 | // Код выполнения/ошибки 300 | return static_cast(g_lastError); 301 | } 302 | 303 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 304 | 305 | /** 306 | * Обработчик оконных сообщений 307 | * @param hWnd Дескриптор окна 308 | * @param message Сообщение 309 | * @param wParam Параметр сообщения 310 | * @param lParam Параметр сообщения 311 | * @return Код выполнения 312 | */ 313 | LRESULT CALLBACK WindowProcedure(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) 314 | { 315 | switch (message) 316 | { 317 | case WM_DESTROY: 318 | PostQuitMessage(0); 319 | break; 320 | 321 | case WM_RBUTTONDOWN: 322 | case WM_LBUTTONDOWN: 323 | case WM_MBUTTONDOWN: 324 | // При нажатии любой кнопки мыши 325 | break; 326 | 327 | case WM_MOUSEMOVE: 328 | // При движении мыши 329 | // Если зажата левая кнопка мыши 330 | if (wParam & MK_LBUTTON) { 331 | } 332 | break; 333 | 334 | default: 335 | return DefWindowProc(hWnd, message, wParam, lParam); 336 | } 337 | 338 | return 0; 339 | } 340 | 341 | /** 342 | * Копирование информации о пикселях изображения в буфер "поверхности" окна 343 | * @param pixels Массив пикселей 344 | * @param width Ширина области 345 | * @param height Высота области 346 | * @param hWnd Дескриптор окна 347 | */ 348 | void PresentFrame(void *pixels, int width, int height, HWND hWnd) 349 | { 350 | // Получить хендл на временный bit-map (4 байта на пиксель) 351 | HBITMAP hBitMap = CreateBitmap( 352 | width, 353 | height, 354 | 1, 355 | 8 * 4, 356 | pixels); 357 | 358 | // Получить device context окна 359 | HDC hdc = GetDC(hWnd); 360 | 361 | // Временный DC для переноса bit-map'а 362 | HDC srcHdc = CreateCompatibleDC(hdc); 363 | 364 | // Связать bit-map с временным DC 365 | SelectObject(srcHdc, hBitMap); 366 | 367 | // Копировать содержимое временного DC в DC окна 368 | BitBlt( 369 | hdc, // HDC назначения 370 | 0, // Начало вставки по оси X 371 | 0, // Начало вставки по оси Y 372 | static_cast(width), // Ширина 373 | static_cast(height), // Высота 374 | srcHdc, // Исходный HDC (из которого будут копироваться данные) 375 | 0, // Начало считывания по оси X 376 | 0, // Начало считывания по оси Y 377 | SRCCOPY // Копировать 378 | ); 379 | 380 | // Уничтожить bit-map 381 | DeleteObject(hBitMap); 382 | // Уничтожить временный DC 383 | DeleteDC(srcHdc); 384 | // Уничтожить DC 385 | ReleaseDC(hWnd,hdc); 386 | } 387 | -------------------------------------------------------------------------------- /Sources/Gfx/Gfx.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "ImageBuffer.hpp" 4 | 5 | #include 6 | #include 7 | 8 | namespace gfx 9 | { 10 | // Уровни проверки на выход за пределы буфера 11 | constexpr std::uint_fast8_t SAFE_CHECK_DISABLE {0}; 12 | constexpr std::uint_fast8_t SAFE_CHECK_KEY_POINTS {1u << 0u}; 13 | constexpr std::uint_fast8_t SAFE_CHECK_ALL_POINTS {1u << 1u}; 14 | 15 | /** 16 | * Точка на плокскости 17 | * @tparam T ип компонентов 2D-точки 18 | */ 19 | template 20 | struct Point2D 21 | { 22 | T x; 23 | T y; 24 | }; 25 | 26 | /** 27 | * Описывающий прямоугольник 28 | * @tparam T Тип компонентов 2D-точек 29 | */ 30 | template 31 | struct BBox2D 32 | { 33 | Point2D min; 34 | Point2D max; 35 | }; 36 | 37 | /** 38 | * Задать конкретной точке конкретный цвет 39 | * @tparam T Тип пикселей в буфере изображения 40 | * @param imageBuffer Указатель на объект буфера изображения 41 | * @param x Координаты по X 42 | * @param y Координаты по Y 43 | * @param color Цвет 44 | * @param safeChecks Осуществлять проверку на выход за пределы 45 | */ 46 | template 47 | void SetPint(ImageBuffer* imageBuffer, int x, int y, const T& color, bool safeChecks = true) 48 | { 49 | if(safeChecks){ 50 | if(!imageBuffer->isPointIn(x,y)) return; 51 | } 52 | 53 | (*imageBuffer)[y][x] = color; 54 | } 55 | 56 | /** 57 | * Задать конкретной точке конкретный цвет (учитывая глубину точки) 58 | * @tparam T0 Тип пикселей в буфере изображения 59 | * @tparam T1 Тип пикселей в буфере глубины 60 | * @param imageBuffer Указатель на объект буфера изображения 61 | * @param depthBuffer Указатель на объект буфера глубины 62 | * @param x Координаты по X 63 | * @param y Координаты по Y 64 | * @param color Цвет 65 | * @param depth Глубина 66 | * @param safeChecks Осуществлять проверку на выход за пределы 67 | */ 68 | template 69 | void SetPoint(ImageBuffer* imageBuffer, 70 | ImageBuffer* depthBuffer, 71 | int x, 72 | int y, 73 | const T0& color, 74 | const T1 depth, 75 | bool safeChecks = true) 76 | { 77 | if(safeChecks){ 78 | if(!imageBuffer->isPointIn(x,y)) return; 79 | if(!depthBuffer->isPointIn(x,y)) return; 80 | } 81 | 82 | if(depth < (*depthBuffer)[y][x]){ 83 | (*imageBuffer)[y][x] = color; 84 | (*depth)[y][x] = depth; 85 | } 86 | } 87 | 88 | /** 89 | * Растеризация линии в буфере изображения (алгоритм Брезенхэма) 90 | * @tparam T Тип пикселей в буфере изображения 91 | * @param imageBuffer Указатель на объект буфера изображения 92 | * @param x0 Координаты точки начала по X 93 | * @param y0 Координаты точки начала по Y 94 | * @param x1 Координаты точки конца по X 95 | * @param y1 Координаты точки конца по Y 96 | * @param color Цвет линии 97 | * @param safeChecks Осуществлять проверку на выход за пределы 98 | */ 99 | template 100 | void SetLine(ImageBuffer* imageBuffer, 101 | int x0, int y0, 102 | int x1, int y1, 103 | const T& color, 104 | std::uint_fast8_t safeChecks = SAFE_CHECK_KEY_POINTS) 105 | { 106 | if(safeChecks & SAFE_CHECK_KEY_POINTS){ 107 | if(!imageBuffer->isPointIn(x0,y0)) return; 108 | if(!imageBuffer->isPointIn(x1,y1)) return; 109 | } 110 | 111 | bool axisSwapped = false; 112 | if(abs(static_cast(x1) - static_cast(x0)) < abs(static_cast(y1) - static_cast(y0))) 113 | { 114 | axisSwapped = true; 115 | std::swap(x0,y0); 116 | std::swap(x1,y1); 117 | } 118 | 119 | int deltaX = abs(static_cast(x1) - static_cast(x0)); 120 | int deltaY = abs(static_cast(y1) - static_cast(y0)); 121 | 122 | if (x0 > x1){ 123 | std::swap(x0, x1); 124 | std::swap(y0, y1); 125 | } 126 | 127 | int error = 0; 128 | int deltaErr = (deltaY + 1); 129 | 130 | int y = y0; 131 | int dirY = y1 - y0; 132 | 133 | if (dirY > 0) dirY = 1; 134 | if (dirY < 0) dirY = -1; 135 | 136 | bool check = safeChecks & SAFE_CHECK_ALL_POINTS; 137 | 138 | for(int x = x0; x <= x1; x++) 139 | { 140 | if(!axisSwapped) SetPint(imageBuffer, x, y, color, check); 141 | else SetPint(imageBuffer, y, x, color, check); 142 | 143 | error += deltaErr; 144 | if(error >= (deltaX + 1)){ 145 | y += dirY; 146 | error -= (deltaX + 1); 147 | } 148 | } 149 | } 150 | 151 | /** 152 | * Растеризация окружности в буфере изображения (алгоритм Брезенхэма) 153 | * @tparam T Тип пикселей в буфере изображения 154 | * @param imageBuffer Указатель на объект буфера изображения 155 | * @param x1 Координаты точки центра окружности по X 156 | * @param y1 Координаты точки центра окружности по Y 157 | * @param r Радицс 158 | * @param color Цвет окружности 159 | * @param safeChecks Осуществлять проверку на выход за пределы 160 | */ 161 | template 162 | void SetCircle(ImageBuffer* imageBuffer, 163 | int x1, int y1, int r, 164 | const T& color, 165 | std::uint_fast8_t safeChecks = SAFE_CHECK_KEY_POINTS) 166 | { 167 | if(safeChecks & SAFE_CHECK_KEY_POINTS){ 168 | if(!imageBuffer->isPointIn(x1+r,y1)) return; 169 | if(!imageBuffer->isPointIn(x1-r,y1)) return; 170 | if(!imageBuffer->isPointIn(x1,y1+r)) return; 171 | if(!imageBuffer->isPointIn(x1,y1-r)) return; 172 | } 173 | 174 | int x = 0; 175 | int y = r; 176 | int delta = 1 - 2 * r; 177 | int error = 0; 178 | 179 | bool check = safeChecks & SAFE_CHECK_ALL_POINTS; 180 | 181 | while (y >= 0) 182 | { 183 | SetPint(imageBuffer, x1 + x, y1 + y, color, check); 184 | SetPint(imageBuffer, x1 + x, y1 - y, color, check); 185 | SetPint(imageBuffer, x1 - x, y1 + y, color, check); 186 | SetPint(imageBuffer, x1 - x, y1 - y, color, check); 187 | 188 | error = 2 * (delta + y) - 1; 189 | 190 | if((delta < 0) && (error <= 0)){ 191 | delta += 2 * ++x + 1; 192 | continue; 193 | } 194 | 195 | if ((delta > 0) && (error > 0)){ 196 | delta -= 2 * --y + 1; 197 | continue; 198 | } 199 | 200 | delta += 2 * (++x - y--); 201 | } 202 | } 203 | 204 | /** 205 | * Растеризация контуров прямоугольника в буфере изображения 206 | * @tparam T Тип пикселей в буфере изображения 207 | * @param imageBuffer Указатель на объект буфера изображения 208 | * @param x0 Координаты первой точки по X 209 | * @param y0 Координаты первой точки по Y 210 | * @param x1 Координаты второй точки конца по X 211 | * @param y1 Координаты первой точки конца по Y 212 | * @param color Цвет линий 213 | * @param safeChecks Осуществлять проверку на выход за пределы 214 | */ 215 | template 216 | void SetBox(ImageBuffer* imageBuffer, 217 | int x0, int y0, 218 | int x1, int y1, 219 | const T& color, 220 | std::uint_fast8_t safeChecks = SAFE_CHECK_KEY_POINTS) 221 | { 222 | SetLine(imageBuffer,x0,y0,x1,y0,color,safeChecks); 223 | SetLine(imageBuffer,x1,y0,x1,y1,color,safeChecks); 224 | SetLine(imageBuffer,x1,y1,x0,y1,color,safeChecks); 225 | SetLine(imageBuffer,x0,y1,x0,y0,color,safeChecks); 226 | } 227 | 228 | /** 229 | * Растеризация контуров прямоугольника в буфере изображения 230 | * @tparam T Тип пикселей в буфере изображения 231 | * @param imageBuffer Указатель на объект буфера изображения 232 | * @param x0 Координаты верхней левой точки по X 233 | * @param y0 Координаты верхней левой точки по X 234 | * @param width Ширина 235 | * @param height Высота 236 | * @param color Цвет линий 237 | * @param safeChecks Осуществлять проверку на выход за пределы 238 | */ 239 | template 240 | void SetRectangle(ImageBuffer* imageBuffer, 241 | int x0, int y0, 242 | int width, int height, 243 | const T& color, 244 | std::uint_fast8_t safeChecks = SAFE_CHECK_KEY_POINTS) 245 | { 246 | SetBox(imageBuffer,x0,y0,x0+width,y0+height,color,safeChecks); 247 | } 248 | 249 | /** 250 | * Заливка фрагмента буфера ограниченного контукром отличным от сцвета фона 251 | * @tparam T Тип пикселей в буфере изображения 252 | * @param imageBuffer Указатель на объект буфера изображения 253 | * @param x0 Точка начала заливки по X 254 | * @param y0 Точка начала заливки по Y 255 | * @param backgroundColor Фоновый цвет 256 | * @param newColor Новый цвет 257 | * @param isColorEqual Функция обратного вызова для сравнения цветов 258 | */ 259 | template 260 | void Fill(ImageBuffer* imageBuffer, 261 | int x0, int y0, 262 | const T& backgroundColor, 263 | const T& newColor, 264 | std::function isColorEqual) 265 | { 266 | if(x0 < 0 || x0 > (imageBuffer->getWidth()-1) || y0 < 0 || y0 > (imageBuffer->getHeight()-1)) 267 | return; 268 | 269 | if(!isColorEqual((*imageBuffer)[y0][x0],backgroundColor)) 270 | return; 271 | 272 | (*imageBuffer)[y0][x0] = newColor; 273 | 274 | Fill(imageBuffer,x0+1,y0,backgroundColor,newColor,isColorEqual); 275 | Fill(imageBuffer,x0-1,y0,backgroundColor,newColor,isColorEqual); 276 | Fill(imageBuffer,x0,y0+1,backgroundColor,newColor,isColorEqual); 277 | Fill(imageBuffer,x0,y0-1,backgroundColor,newColor,isColorEqual); 278 | } 279 | 280 | /** 281 | * Нахождение описывающего прямоугольника 282 | * @tparam T Тип компонентов точек 283 | * @param points Массив точек 284 | * @return Описывающий прямоугольник (строкутура из двух точек) 285 | */ 286 | template 287 | BBox2D FindBoundingBox2D(std::vector> points) 288 | { 289 | BBox2D result; 290 | result.min.x = std::min_element(points.begin(), points.end(), [](Point2D elem1, Point2D elem2) {return elem1.x < elem2.x; })->x; 291 | result.min.y = std::min_element(points.begin(), points.end(), [](Point2D elem1, Point2D elem2) {return elem1.y < elem2.y; })->y; 292 | 293 | result.max.x = std::max_element(points.begin(), points.end(), [](Point2D elem1, Point2D elem2) {return elem1.x < elem2.x; })->x; 294 | result.max.y = std::max_element(points.begin(), points.end(), [](Point2D elem1, Point2D elem2) {return elem1.y < elem2.y; })->y; 295 | return result; 296 | } 297 | 298 | /** 299 | * Находится ли точка внутри треугольника 300 | * @tparam T Тип компонентов точки 301 | * @param p Проверяемая точка 302 | * @param a Точка треугольника A 303 | * @param b Точка треугольника B 304 | * @param c Точка треугольника C 305 | * @return Да или нет 306 | */ 307 | template 308 | bool IsPointInTriangle(Point2D p, Point2D a, Point2D b, Point2D c) 309 | { 310 | // Используем преобразовнное уравнение прямой проходящих по двум точкам - (y-y1)/(y2-y1) = (x-x1)/(x2-x1) 311 | // Находим 3 уравнения прямых по двум точкам каждой стороны треугольника, подставляя координаты проверяемой точки 312 | // Если точка выше - значение будет выше ноля, если ниже - ниже ноля, если равно - на прямой 313 | // Слудет учитывать что уравнение прямой от точки A к B не совсем то же, что от точкии B к А, это своего рода инверсия, 314 | // поэтому следует учитывать ориентацию прямых (в каком порядке идут точки), либо делать 2 прверки, для универсальности 315 | int aSide = (a.y - b.y)*p.x + (b.x - a.x)*p.y + (a.x*b.y - b.x*a.y); 316 | int bSide = (b.y - c.y)*p.x + (c.x - b.x)*p.y + (b.x*c.y - c.x*b.y); 317 | int cSide = (c.y - a.y)*p.x + (a.x - c.x)*p.y + (c.x*a.y - a.x*c.y); 318 | 319 | return (aSide >= 0 && bSide >= 0 && cSide >= 0) || (aSide < 0 && bSide < 0 && cSide < 0); 320 | } 321 | 322 | /** 323 | * Растеризация треугольника в буфере изображения 324 | * @tparam T Тип пикселей в буфере изображения 325 | * @param imageBuffer Буфер изображения 326 | * @param x0 Координаты первой точки по X 327 | * @param y0 Координаты первой точки по y 328 | * @param x1 Координаты второй точки по X 329 | * @param y1 Координаты второй точки по Y 330 | * @param x2 Координаты третьей точки по X 331 | * @param y2 Координаты третьей точки по y 332 | * @param color Цвет контукров и заливки 333 | * @param fill Нужно ли закрашивать треугольник 334 | * @param safeChecks Проверка точек на выход за пределы буфера 335 | */ 336 | template 337 | void SetTriangle(ImageBuffer* imageBuffer, 338 | int x0, int y0, 339 | int x1, int y1, 340 | int x2, int y2, 341 | T color, 342 | bool fill = true, 343 | std::uint_fast8_t safeChecks = SAFE_CHECK_ALL_POINTS) 344 | { 345 | // Не рисовать если не прошло грубую проверку (если она включена) 346 | if(safeChecks & SAFE_CHECK_KEY_POINTS){ 347 | if(!imageBuffer->isPointIn(x0,y0)) return; 348 | if(!imageBuffer->isPointIn(x1,y1)) return; 349 | if(!imageBuffer->isPointIn(x2,y2)) return; 350 | } 351 | 352 | // Написовать линии 353 | SetLine(imageBuffer,x0,y0,x1,y1,color,safeChecks); 354 | SetLine(imageBuffer,x1,y1,x2,y2,color,safeChecks); 355 | SetLine(imageBuffer,x2,y2,x0,y0,color,safeChecks); 356 | 357 | // Если не надо закрашивать - завершаем 358 | if(!fill) return; 359 | 360 | // Получить описывающий прямоугольник 361 | auto bbox = FindBoundingBox2D({{x0,y0},{x1,y1},{x2,y2}}); 362 | 363 | // Пройтись по точкам прямугольника, и если точни принадлежат треугольнику - закрасить их 364 | for(int y = bbox.min.y; y < bbox.max.y; y++) 365 | { 366 | for(int x = bbox.min.x; x < bbox.max.x; x++) 367 | { 368 | if(IsPointInTriangle({x,y},{x0,y0},{x1,y1},{x2,y2})) 369 | { 370 | SetPint(imageBuffer, x, y, color, safeChecks & SAFE_CHECK_ALL_POINTS); 371 | } 372 | } 373 | } 374 | } 375 | } -------------------------------------------------------------------------------- /Sources/06_SampleSkeletalBasics/Skeleton.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Класс скелета. Используется для скелетной анимации одиночного меша 3 | * Copyright (C) 2020 by Alex "DarkWolf" Nem - https://github.com/darkoffalex 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | /** 15 | * Класс скелета 16 | */ 17 | class Skeleton 18 | { 19 | public: 20 | 21 | /** 22 | * Класс кости скелета (внутренний класс скелета) 23 | */ 24 | class Bone 25 | { 26 | public: 27 | /// Флаги вычисления матриц 28 | enum CalcFlags 29 | { 30 | eNone = (0), 31 | eFullTransform = (1u << 0u), 32 | eBindTransform = (1u << 1u), 33 | eInverseBindTransform = (1u << 2u), 34 | }; 35 | 36 | private: 37 | /// Открыть доступ для дружественных классов 38 | friend class Skeleton; 39 | 40 | /// Указатель на скелет 41 | Skeleton* pSkeleton_; 42 | /// Индекс кости в линейном массиве 43 | size_t index_; 44 | 45 | /// Указатель на родительскую кость 46 | Skeleton::Bone* pParentBone_; 47 | /// Массив указателей на дочерние кости 48 | std::vector> childrenBones_; 49 | 50 | /// Смещение (расположение) относительно родительской кости (можно считать это initial-положением) 51 | math::Mat4 localBindTransform_; 52 | /// Локальная трансформация относительно bind (та трансформация, которая может назначаться во время анимации) 53 | math::Mat4 localTransform_; 54 | 55 | /// Результирующая трансформация кости с учетом локальной трансформации и результирующий трансформаций родительских костей 56 | /// Данная трансформация может быть применена к точкам находящимся В ПРОСТРАНСТВЕ КОСТИ 57 | math::Mat4 totalTransform_; 58 | /// Результирующая трансформация кости БЕЗ учета задаваемой, но с учетом bind-трансформаций родительских костей 59 | math::Mat4 totalBindTransform_; 60 | /// Инвертированная bind матрица может быть использована для перехода в пространство кости ИЗ ПРОСТРАНСТВА МОДЕЛИ 61 | math::Mat4 totalBindTransformInverse_; 62 | 63 | /** 64 | * Рекурсивное вычисление матриц для текущей кости и всех дочерних её костей 65 | * @param callUpdateCallbackFunction Вызывать функцию обратного вызова установленную к скелета 66 | * @param calcFlags Опции вычисления матриц (какие матрицы считать) 67 | */ 68 | void calculateBranch(unsigned calcFlags = CalcFlags::eFullTransform | CalcFlags::eBindTransform | CalcFlags::eInverseBindTransform) 69 | { 70 | // Если у кости есть родительская кость 71 | if(pParentBone_ != nullptr) 72 | { 73 | // Общая initial (bind) трансформация для кости учитывает текущую и родительскую (что в свою очередь справедливо и для родительской) 74 | if(calcFlags & CalcFlags::eBindTransform) 75 | totalBindTransform_ = pParentBone_->totalBindTransform_ * this->localBindTransform_; 76 | 77 | // Общая полная (с учетом задаваемой) трансформация кости (смещаем на localTransform_, затем на initial, затем на общую родительскую трансформацию) 78 | if(calcFlags & CalcFlags::eFullTransform) 79 | totalTransform_ = pParentBone_->totalTransform_ * this->localBindTransform_ * this->localTransform_; 80 | } 81 | // Если нет родительской кости - считать кость корневой 82 | else 83 | { 84 | if(calcFlags & CalcFlags::eBindTransform) 85 | totalBindTransform_ = this->localBindTransform_; 86 | 87 | if(calcFlags & CalcFlags::eFullTransform) 88 | totalTransform_ = this->localBindTransform_ * this->localTransform_; 89 | } 90 | 91 | // Инвертированная матрица bind трансформации 92 | if(calcFlags & CalcFlags::eInverseBindTransform) 93 | totalBindTransformInverse_ = math::Inverse(totalBindTransform_); 94 | 95 | // Если есть указатель на объект скелета и индекс корректный 96 | if(pSkeleton_ != nullptr && index_ < pSkeleton_->modelSpaceFinalTransforms_.size()) 97 | { 98 | // Итоговая матрица трансформации для точек находящихся в пространстве модели 99 | // Поскольку общая трансформация кости работает с вершинами находящимися в пространстве модели, 100 | // они в начале должны быть переведены в пространство кости. 101 | pSkeleton_->modelSpaceFinalTransforms_[index_] = pSkeleton_->globalInverseTransform_ * totalTransform_ * totalBindTransformInverse_; 102 | 103 | // Для ситуаций, если вершины задаются сразу в пространстве кости 104 | pSkeleton_->boneSpaceFinalTransforms_[index_] = pSkeleton_->globalInverseTransform_ * totalTransform_; 105 | } 106 | 107 | // Рекурсивно выполнить для дочерних элементов (если они есть) 108 | if(!this->childrenBones_.empty()){ 109 | for(auto& childBone : this->childrenBones_){ 110 | childBone->calculateBranch(calcFlags); 111 | } 112 | } 113 | } 114 | 115 | public: 116 | /** 117 | * Конструктор по умолчанию 118 | */ 119 | Bone(): 120 | pSkeleton_(nullptr), 121 | index_(0), 122 | pParentBone_(nullptr), 123 | localBindTransform_(math::Mat4(1.0f)), 124 | localTransform_(math::Mat4(1.0f)), 125 | totalTransform_(math::Mat4(1.0f)), 126 | totalBindTransform_(math::Mat4(1.0f)), 127 | totalBindTransformInverse_(math::Mat4(1.0f)){} 128 | 129 | /** 130 | * Основной конструктор кости 131 | * @param pSkeleton Указатель на объект скелета 132 | * @param index Индекс кости в линейном массиве трансформаций 133 | * @param parentBone Указатель на родительскую кость 134 | * @param localBindTransform Смещение (расположение) относительно родительской кости 135 | * @param localTransform Локальная трансформация (та трансформация, которая может назначаться, например "поворот руки на n градусов") 136 | */ 137 | explicit Bone(Skeleton* pSkeleton, 138 | size_t index, Bone* parentBone = nullptr, 139 | const math::Mat4& localBindTransform = math::Mat4(1.0f), 140 | const math::Mat4& localTransform = math::Mat4(1.0f)): 141 | index_(index), 142 | pSkeleton_(pSkeleton), 143 | pParentBone_(parentBone), 144 | localBindTransform_(localBindTransform), 145 | localTransform_(localTransform), 146 | totalTransform_(math::Mat4(1.0f)), 147 | totalBindTransform_(math::Mat4(1.0f)), 148 | totalBindTransformInverse_(math::Mat4(1.0f)) 149 | { 150 | // Вычисление матриц кости 151 | calculateBranch(CalcFlags::eFullTransform|CalcFlags::eBindTransform|CalcFlags::eInverseBindTransform); 152 | } 153 | 154 | /** 155 | * Деструктор по умолчанию 156 | */ 157 | ~Bone() = default; 158 | 159 | /** 160 | * Добавление дочерней кости 161 | * @param index Индекс 162 | * @param localBindTransform Изначальная трансформация 163 | * @param localTransform Задаваемая трансформация 164 | * @return Указатель на добавленную кость 165 | */ 166 | std::shared_ptr addChildBone(size_t index, 167 | const math::Mat4& localBindTransform, 168 | const math::Mat4& localTransform = math::Mat4(1.0f)) 169 | { 170 | // Создать дочернюю кость 171 | std::shared_ptr child(new Bone(this->pSkeleton_,index,this,localBindTransform,localTransform)); 172 | // Добавить в массив дочерних костей 173 | this->childrenBones_.push_back(child); 174 | // Добавить в общий линейный массив по указанному индексу 175 | if(this->pSkeleton_ != nullptr) this->pSkeleton_->bones_[index] = child; 176 | // Вернуть указатель 177 | return child; 178 | } 179 | 180 | /** 181 | * Установить локальную (анимируемую) трансформацию 182 | * @param transform Матрица 4*4 183 | * @param recalculateBranch Пересчитать ветвь 184 | */ 185 | void setLocalTransform(const math::Mat4& transform, bool recalculateBranch = true) 186 | { 187 | this->localTransform_ = transform; 188 | if(recalculateBranch) this->calculateBranch(CalcFlags::eFullTransform); 189 | } 190 | 191 | /** 192 | * Установить изначальную (initial) трансформацию кости относительно родителя 193 | * @param transform Матрица 4*4 194 | * @param recalculateBranch Пересчитать ветвь 195 | */ 196 | void setLocalBindTransform(const math::Mat4& transform, bool recalculateBranch = true) 197 | { 198 | this->localBindTransform_ = transform; 199 | if(recalculateBranch) this->calculateBranch(CalcFlags::eBindTransform|CalcFlags::eInverseBindTransform); 200 | } 201 | 202 | /** 203 | * Установить изначальную (initial, bind) и добавочную (animated) трансформацию 204 | * @param localBind Матрица 4*4 205 | * @param local Матрица 4*4 206 | * @param recalculateBranch Пересчитать ветвь 207 | */ 208 | void setTransformations(const math::Mat4& localBind, const math::Mat4& local, bool recalculateBranch = true) 209 | { 210 | this->localBindTransform_ = localBind; 211 | this->localTransform_ = local; 212 | if(recalculateBranch) this->calculateBranch(CalcFlags::eFullTransform|CalcFlags::eBindTransform|CalcFlags::eInverseBindTransform); 213 | } 214 | 215 | /** 216 | * Получить массив дочерних костей 217 | * @return Ссылка на массив указателей 218 | */ 219 | std::vector>& getChildrenBones() 220 | { 221 | return this->childrenBones_; 222 | } 223 | 224 | /** 225 | * Получить указатель на родительскую кость 226 | * @return Указатель 227 | */ 228 | Bone* getParentBone() 229 | { 230 | return this->pParentBone_; 231 | } 232 | 233 | /** 234 | * Получить индекс кости 235 | * @return Целое положительное число 236 | */ 237 | size_t getIndex() 238 | { 239 | return index_; 240 | } 241 | }; 242 | 243 | /** 244 | * Smart-pointer объекта скелетной кости 245 | */ 246 | typedef std::shared_ptr BonePtr; 247 | 248 | /** 249 | * Состояние анимации 250 | */ 251 | enum struct AnimationState 252 | { 253 | eStopped, 254 | ePlaying 255 | }; 256 | 257 | private: 258 | /// Открыть доступ для класса Mesh 259 | friend class Mesh; 260 | 261 | /// Массив итоговых трансформаций для вершин в пространстве модели 262 | std::vector> modelSpaceFinalTransforms_; 263 | /// Массив итоговых трансформаций для вершин в пространстве костей 264 | std::vector> boneSpaceFinalTransforms_; 265 | /// Матрица глобальной инверсии (на случай если в программе для моделирования объекту задавалась глобальная трансформация) 266 | math::Mat4 globalInverseTransform_; 267 | 268 | /// Массив указателей на кости для доступа по индексам 269 | std::vector bones_; 270 | /// Корневая кость 271 | BonePtr rootBone_; 272 | 273 | public: 274 | /** 275 | * Конструктор по умолчанию 276 | * Изначально у скелета всегда есть одна кость 277 | */ 278 | Skeleton(): 279 | modelSpaceFinalTransforms_(1), 280 | boneSpaceFinalTransforms_(1), 281 | globalInverseTransform_(math::Mat4(1.0f)), 282 | bones_(1) 283 | { 284 | // Создать корневую кость 285 | rootBone_ = std::make_shared(this,0,nullptr,math::Mat4(1),math::Mat4(1)); 286 | // Нулевая кость в линейном массиве указателей 287 | bones_[0] = rootBone_; 288 | } 289 | 290 | /** 291 | * Основной конструктор 292 | * @param boneTotalCount Общее количество костей 293 | * @param updateCallback Функция обратного вызова при пересчете матриц 294 | */ 295 | explicit Skeleton(size_t boneTotalCount): 296 | globalInverseTransform_(math::Mat4(1.0f)) 297 | { 298 | // Изначально у скелета есть как минимум 1 кость 299 | modelSpaceFinalTransforms_.resize(std::max(1,boneTotalCount)); 300 | boneSpaceFinalTransforms_.resize(std::max(1,boneTotalCount)); 301 | bones_.resize(std::max(1,boneTotalCount)); 302 | 303 | // Создать корневую кость 304 | rootBone_ = std::make_shared(this,0,nullptr,math::Mat4(1),math::Mat4(1)); 305 | // Нулевая кость в линейном массиве указателей 306 | bones_[0] = rootBone_; 307 | } 308 | 309 | /** 310 | * Деструктор по умолчанию 311 | */ 312 | ~Skeleton() = default; 313 | 314 | /** 315 | * Получить корневую кость 316 | * @return Указатель на объект кости 317 | */ 318 | BonePtr getRootBone() 319 | { 320 | return rootBone_; 321 | } 322 | 323 | void setGlobalInverseTransform(const math::Mat4& m) 324 | { 325 | this->globalInverseTransform_ = m; 326 | this->getRootBone()->calculateBranch(Bone::CalcFlags::eNone); 327 | } 328 | 329 | /** 330 | * Получить массив итоговых трансформаций костей 331 | * @param fromBoneSpace Если точки заданы в пространстве кости 332 | * @return Ссылка на массив матриц 333 | */ 334 | const std::vector>& getFinalBoneTransforms(bool fromBoneSpace = false) const 335 | { 336 | return fromBoneSpace ? boneSpaceFinalTransforms_ : modelSpaceFinalTransforms_; 337 | } 338 | 339 | /** 340 | * Получить общее кол-во костей 341 | * @return Целое положительное число 342 | */ 343 | size_t getBonesCount() const 344 | { 345 | return bones_.size(); 346 | } 347 | 348 | /** 349 | * Получить размер массива трансформаций в байтах 350 | * @return Целое положительное число 351 | */ 352 | size_t getTransformsDataSize() const 353 | { 354 | return sizeof(math::Mat4) * this->modelSpaceFinalTransforms_.size(); 355 | } 356 | 357 | /** 358 | * Получить линейный массив костей 359 | * @return Массив указателей на кости 360 | */ 361 | const std::vector& getBones() 362 | { 363 | return bones_; 364 | } 365 | 366 | /** 367 | * Получить указатель на кость по индексу 368 | * @param index Индекс 369 | * @return Smart-pointer кости 370 | */ 371 | BonePtr getBoneByIndex(size_t index) 372 | { 373 | return bones_[index]; 374 | } 375 | }; 376 | 377 | /** 378 | * Smart-unique-pointer объекта скелета 379 | */ 380 | typedef std::unique_ptr UniqueSkeleton; -------------------------------------------------------------------------------- /Sources/05_SamplePolygonalDraw/Main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | /** 10 | * Коды ошибок 11 | */ 12 | enum ErrorCode 13 | { 14 | eNoErrors, 15 | eClassRegistrationError, 16 | eWindowCreationError, 17 | }; 18 | 19 | /// Дескриптор исполняемого модуля программы 20 | HINSTANCE g_hInstance = nullptr; 21 | /// Дескриптор осноного окна отрисовки 22 | HWND g_hwnd = nullptr; 23 | /// Дескриптор контекста отрисовки 24 | HDC g_hdc = nullptr; 25 | /// Наименование класса 26 | const char* g_strClassName = "MainWindowClass"; 27 | /// Заголовок окна 28 | const char* g_strWindowCaption = "DemoApp"; 29 | /// Код последней ошибки 30 | ErrorCode g_lastError = ErrorCode::eNoErrors; 31 | /// Таймер 32 | tools::Timer* g_pTimer = nullptr; 33 | 34 | /** 35 | * Обработчик оконных сообщений 36 | * @param hWnd Дескриптор окна 37 | * @param message Сообщение 38 | * @param wParam Параметр сообщения 39 | * @param lParam Параметр сообщения 40 | * @return Код выполнения 41 | */ 42 | LRESULT CALLBACK WindowProcedure(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); 43 | 44 | /** 45 | * Копирование информации о пикселях изображения в буфер "поверхности" окна 46 | * @param pixels Массив пикселей 47 | * @param width Ширина области 48 | * @param height Высота области 49 | * @param hWnd Дескриптор окна 50 | */ 51 | void PresentFrame(void *pixels, int width, int height, HWND hWnd); 52 | 53 | /** 54 | * Нарисовать полигональный меш 55 | * @param frameBuffer Указатель на кадровый буфер 56 | * @param vertices Массив вершин 57 | * @param indices Массив индексов 58 | * @param position Положение меша 59 | * @param orientation Ориентация меша 60 | * @param color Цвет (RGB в диапозоне от 0 до 1) 61 | * @param projectPerspective Использовать перспективную проекцию 62 | * @param backFaceCulling Отбрасывать задние грани 63 | * @param fillFaces Заливка граней (false для сеточной отрисовки) 64 | */ 65 | void DrawMesh(gfx::ImageBuffer* frameBuffer, 66 | const std::vector>& vertices, 67 | const std::vector& indices, 68 | const math::Vec3& position, 69 | const math::Vec3& orientation, 70 | const math::Vec3& color = {0.0f,1.0f,0.0f}, 71 | bool projectPerspective = true, 72 | bool backFaceCulling = false, 73 | bool fillFaces = false); 74 | 75 | /** 76 | * Точка входа 77 | * @param argc Кол-во аргументов 78 | * @param argv Аргмуенты 79 | * @return Код исполнения 80 | */ 81 | int main(int argc, char* argv[]) 82 | { 83 | try { 84 | // Получение дескриптора исполняемого модуля программы 85 | g_hInstance = GetModuleHandle(nullptr); 86 | 87 | // Информация о классе 88 | WNDCLASSEX classInfo; 89 | classInfo.cbSize = sizeof(WNDCLASSEX); 90 | classInfo.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; 91 | classInfo.cbClsExtra = 0; 92 | classInfo.cbWndExtra = 0; 93 | classInfo.hInstance = g_hInstance; 94 | classInfo.hIcon = LoadIcon(g_hInstance, IDI_APPLICATION); 95 | classInfo.hIconSm = LoadIcon(g_hInstance, IDI_APPLICATION); 96 | classInfo.hCursor = LoadCursor(nullptr, IDC_ARROW); 97 | classInfo.hbrBackground = CreateSolidBrush(RGB(240, 240, 240)); 98 | classInfo.lpszMenuName = nullptr; 99 | classInfo.lpszClassName = g_strClassName; 100 | classInfo.lpfnWndProc = WindowProcedure; 101 | 102 | // Пытаемся зарегистрировать оконный класс 103 | if (!RegisterClassEx(&classInfo)) { 104 | g_lastError = ErrorCode::eClassRegistrationError; 105 | throw std::runtime_error("ERROR: Can't register window class."); 106 | } 107 | 108 | // Создание окна 109 | g_hwnd = CreateWindow( 110 | g_strClassName, 111 | g_strWindowCaption, 112 | WS_OVERLAPPEDWINDOW, 113 | 0, 0, 114 | 800, 600, 115 | nullptr, 116 | nullptr, 117 | g_hInstance, 118 | nullptr); 119 | 120 | // Если не удалось создать окно 121 | if (!g_hwnd) { 122 | g_lastError = ErrorCode::eWindowCreationError; 123 | throw std::runtime_error("ERROR: Can't create main application window."); 124 | } 125 | 126 | // Показать окно 127 | ShowWindow(g_hwnd, SW_SHOWNORMAL); 128 | 129 | // Получение контекста отрисовки 130 | g_hdc = GetDC(g_hwnd); 131 | 132 | // Размеры клиентской области окна 133 | RECT clientRect; 134 | GetClientRect(g_hwnd, &clientRect); 135 | 136 | // Создать буффер кадра 137 | auto frameBuffer = gfx::ImageBuffer(clientRect.right, clientRect.bottom, {0, 0, 0, 0}); 138 | std::cout << "INFO: Frame-buffer initialized (resolution : " << frameBuffer.getWidth() << "x" << frameBuffer.getHeight() << ", size : " << frameBuffer.getSize() << " bytes)" << std::endl; 139 | 140 | // Положения вершин куба 141 | std::vector> vertices { 142 | {-1.0f,1.0f,1.0f}, 143 | {1.0f,1.0f,1.0f}, 144 | {1.0f,-1.0f,1.0f}, 145 | {-1.0f,-1.0f,1.0f}, 146 | 147 | {-1.0f,1.0f,-1.0f}, 148 | {1.0f,1.0f,-1.0f}, 149 | {1.0f,-1.0f,-1.0f}, 150 | {-1.0f,-1.0f,-1.0f} 151 | }; 152 | 153 | // Индексы (тройки вершин) 154 | std::vector indices { 155 | 0,1,2, 2,3,0, 156 | 1,5,6, 6,2,1, 157 | 5,4,7, 7,6,5, 158 | 4,0,3, 3,7,4, 159 | 4,5,1, 1,0,4, 160 | 3,2,6, 6,7,3 161 | }; 162 | 163 | // Текущий угол поворота 164 | float rotationAngle = 0.0f; 165 | 166 | // Скорость вращения 167 | float angleSpeed = 0.02f; 168 | 169 | /** MAIN LOOP **/ 170 | 171 | // Таймер основного цикла (для выяснения временной дельты и FPS) 172 | g_pTimer = new tools::Timer(); 173 | 174 | // Оконное сообщение 175 | MSG msg = {}; 176 | 177 | // Запуск цикла 178 | while (true) 179 | { 180 | // Обновить таймер 181 | g_pTimer->updateTimer(); 182 | 183 | // Обработка оконных сообщений 184 | if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) 185 | { 186 | DispatchMessage(&msg); 187 | 188 | if (msg.message == WM_QUIT) { 189 | break; 190 | } 191 | } 192 | 193 | // Поскольку показ FPS на окне уменьшает FPS - делаем это только тогда когда счетчик готов (примерно 1 раз в секунду) 194 | if (g_pTimer->isFpsCounterReady()){ 195 | std::string fps = std::string(g_strWindowCaption).append(" (").append(std::to_string(g_pTimer->getFps())).append(" FPS)"); 196 | SetWindowTextA(g_hwnd, fps.c_str()); 197 | } 198 | 199 | // Приращение угла поворота 200 | rotationAngle += (angleSpeed * g_pTimer->getDelta()); 201 | 202 | // Нарисовать полигональный меш 203 | DrawMesh( 204 | &frameBuffer, 205 | vertices, 206 | indices, 207 | {0.0f,0.0f,-4.0f}, 208 | {rotationAngle,rotationAngle,0.0f}, 209 | {0.0f,1.0f,0.0f}, 210 | true, 211 | true, 212 | true); 213 | 214 | // Показ кадра 215 | PresentFrame(frameBuffer.getData(), static_cast(frameBuffer.getWidth()), static_cast(frameBuffer.getHeight()), g_hwnd); 216 | 217 | // Очистка кадра 218 | frameBuffer.clear({0,0,0,0}); 219 | } 220 | } 221 | catch(std::exception& ex) 222 | { 223 | std::cout << ex.what() << std::endl; 224 | } 225 | 226 | // Уничтожение окна 227 | DestroyWindow(g_hwnd); 228 | // Вырегистрировать класс окна 229 | UnregisterClass(g_strClassName, g_hInstance); 230 | 231 | // Код выполнения/ошибки 232 | return static_cast(g_lastError); 233 | } 234 | 235 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 236 | 237 | /** 238 | * Обработчик оконных сообщений 239 | * @param hWnd Дескриптор окна 240 | * @param message Сообщение 241 | * @param wParam Параметр сообщения 242 | * @param lParam Параметр сообщения 243 | * @return Код выполнения 244 | */ 245 | LRESULT CALLBACK WindowProcedure(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) 246 | { 247 | switch (message) 248 | { 249 | case WM_DESTROY: 250 | PostQuitMessage(0); 251 | break; 252 | 253 | case WM_RBUTTONDOWN: 254 | case WM_LBUTTONDOWN: 255 | case WM_MBUTTONDOWN: 256 | // При нажатии любой кнопки мыши 257 | break; 258 | 259 | case WM_MOUSEMOVE: 260 | // При движении мыши 261 | // Если зажата левая кнопка мыши 262 | if (wParam & MK_LBUTTON) { 263 | } 264 | break; 265 | 266 | default: 267 | return DefWindowProc(hWnd, message, wParam, lParam); 268 | } 269 | 270 | return 0; 271 | } 272 | 273 | /** 274 | * Копирование информации о пикселях изображения в буфер "поверхности" окна 275 | * @param pixels Массив пикселей 276 | * @param width Ширина области 277 | * @param height Высота области 278 | * @param hWnd Дескриптор окна 279 | */ 280 | void PresentFrame(void *pixels, int width, int height, HWND hWnd) 281 | { 282 | // Получить хендл на временный bit-map (4 байта на пиксель) 283 | HBITMAP hBitMap = CreateBitmap( 284 | width, 285 | height, 286 | 1, 287 | 8 * 4, 288 | pixels); 289 | 290 | // Получить device context окна 291 | HDC hdc = GetDC(hWnd); 292 | 293 | // Временный DC для переноса bit-map'а 294 | HDC srcHdc = CreateCompatibleDC(hdc); 295 | 296 | // Связать bit-map с временным DC 297 | SelectObject(srcHdc, hBitMap); 298 | 299 | // Копировать содержимое временного DC в DC окна 300 | BitBlt( 301 | hdc, // HDC назначения 302 | 0, // Начало вставки по оси X 303 | 0, // Начало вставки по оси Y 304 | static_cast(width), // Ширина 305 | static_cast(height), // Высота 306 | srcHdc, // Исходный HDC (из которого будут копироваться данные) 307 | 0, // Начало считывания по оси X 308 | 0, // Начало считывания по оси Y 309 | SRCCOPY // Копировать 310 | ); 311 | 312 | // Уничтожить bit-map 313 | DeleteObject(hBitMap); 314 | // Уничтожить временный DC 315 | DeleteDC(srcHdc); 316 | // Уничтожить DC 317 | ReleaseDC(hWnd,hdc); 318 | } 319 | 320 | /** 321 | * Нарисовать полигональный меш 322 | * @param frameBuffer Указатель на кадровый буфер 323 | * @param vertices Массив вершин 324 | * @param indices Массив индексов 325 | * @param position Положение меша 326 | * @param orientation Ориентация меша 327 | * @param color Цвет (RGB в диапозоне от 0 до 1) 328 | * @param projectPerspective Использовать перспективную проекцию 329 | * @param backFaceCulling Отбрасывать задние грани 330 | * @param fillFaces Заливка граней (false для сеточной отрисовки) 331 | */ 332 | void DrawMesh(gfx::ImageBuffer* frameBuffer, 333 | const std::vector>& vertices, 334 | const std::vector& indices, 335 | const math::Vec3& position, 336 | const math::Vec3& orientation, 337 | const math::Vec3& color, 338 | bool projectPerspective, 339 | bool backFaceCulling, 340 | bool fillFaces) 341 | { 342 | // Пропорции области вида 343 | float aspectRatio = static_cast(frameBuffer->getWidth()) / static_cast(frameBuffer->getHeight()); 344 | 345 | // Пройти по всем индексам (шаг - 3 индекса) 346 | for(size_t i = 3; i <= indices.size(); i+=3) 347 | { 348 | // Точки треугольника (в координатаъ экрана) 349 | std::vector> triangleScreen; 350 | // Точки в NDC-координатах 351 | std::vector> triangleNdc; 352 | 353 | // Произвести необходимые преобразования кординат точек, для отображения на экране 354 | for(size_t j = 0; j < 3; j++) 355 | { 356 | // Получить точку 357 | auto p = vertices[indices[(i-3)+j]]; 358 | 359 | // Вращение точки 360 | p = math::RotateAroundX(p,orientation.x); 361 | p = math::RotateAroundY(p,orientation.y); 362 | p = math::RotateAroundZ(p,orientation.z); 363 | 364 | // Смещение (перемещение) точки 365 | p = p + position; 366 | 367 | // Проекция точки 368 | auto pp = projectPerspective ? 369 | math::ProjectPerspective(p,90.0f,0.1f,100.0f,aspectRatio) : 370 | math::ProjectOrthogonal(p,-2.0f,2.0f,-2.0f,2.0f,0.1f,100.0f,aspectRatio); 371 | 372 | // Перевод в координаты экрана 373 | auto sp = math::NdcToScreen({pp.x, pp.y}, frameBuffer->getWidth(), frameBuffer->getHeight()); 374 | 375 | // Добавить в треугольник 376 | triangleScreen.push_back(sp); 377 | triangleNdc.push_back(p); 378 | } 379 | 380 | // Получить нормаль для отбрасывания задних граней (инвертируем, поскольку ось Y в координатах экрана инвертирована) 381 | auto normalForCulling = -math::Normalize(math::Cross( 382 | math::Normalize(math::Vec3(triangleScreen[2].x - triangleScreen[0].x, triangleScreen[2].y - triangleScreen[0].y,0.0f)), 383 | math::Normalize(math::Vec3(triangleScreen[1].x - triangleScreen[0].x, triangleScreen[1].y - triangleScreen[0].y,0.0f)) 384 | )); 385 | 386 | // Скалярное произведения вектора к зрителю и нормали треугольника (показывает насколько сильно треугольник повернут к зрителю) 387 | auto dotForCulling = math::Dot(normalForCulling,{0.0f, 0.0f, 1.0f}); 388 | 389 | // Если нужно отрисовывать треугольник (если включен backFaceCulling, то отрисовывается в случае если повернут к зрителю) 390 | if(!backFaceCulling || dotForCulling > 0) 391 | { 392 | // Яркость цвета 393 | float brightness = 1.0f; 394 | 395 | // Если нужна заливка граней 396 | if(fillFaces) 397 | { 398 | // Нормаль для вычисления освещенности 399 | auto normal = math::Normalize(math::Cross( 400 | math::Normalize(triangleNdc[2] - triangleNdc[0]), 401 | math::Normalize(triangleNdc[1] - triangleNdc[0]) 402 | )); 403 | 404 | // Яркость тем сильнее, чем больше грань обернута к свету (считаем что свет исходит от зрителя) 405 | brightness = std::max(math::Dot(normal,{0.0f, 0.0f, 1.0f}),0.0f); 406 | } 407 | 408 | // Написовать треугольник 409 | gfx::SetTriangle( 410 | frameBuffer, 411 | triangleScreen[0].x,triangleScreen[0].y, 412 | triangleScreen[1].x,triangleScreen[1].y, 413 | triangleScreen[2].x,triangleScreen[2].y, 414 | { 415 | static_cast(color.b * brightness * 255.0f), 416 | static_cast(color.g * brightness * 255.0f), 417 | static_cast(color.r * brightness * 255.0f), 418 | 0 419 | }, 420 | fillFaces); 421 | } 422 | 423 | 424 | } 425 | } 426 | -------------------------------------------------------------------------------- /Sources/Math/Math.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Мини-библиотека для работы с математикой 3 | * Copyright (C) 2020 by Alex "DarkWolf" Nem - https://github.com/darkoffalex 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #define M_PI 3.14159265358979323846 /* pi */ 13 | 14 | namespace math 15 | { 16 | /** 17 | * 2-мерный вектор или точка на плоскости 18 | * @tparam T Тип компонентов вектора 19 | */ 20 | template 21 | struct Vec2 22 | { 23 | T x, y; 24 | 25 | Vec2() noexcept :x(0.0f),y(0.0f){}; 26 | Vec2(const T& s1, const T& s2) noexcept :x(s1), y(s2) {} 27 | 28 | Vec2 operator*(const T& value) const 29 | { 30 | return {this->x * value, this->y * value}; 31 | } 32 | 33 | Vec2 operator/(const T& value) const 34 | { 35 | return {this->x / value, this->y / value}; 36 | } 37 | 38 | Vec2 operator*(const Vec2& other) const 39 | { 40 | return {this->x * other.x, this->y * other.y}; 41 | } 42 | 43 | Vec2 operator+(const Vec2& other) const 44 | { 45 | return {this->x + other.x, this->y + other.y}; 46 | } 47 | 48 | Vec2 operator-(const Vec2& other) const 49 | { 50 | return {this->x - other.x, this->y - other.y}; 51 | } 52 | 53 | Vec2 operator-() const 54 | { 55 | return {-this->x, -this->y}; 56 | } 57 | }; 58 | 59 | /** 60 | * 3-мерный вектор или точка в пространстве 61 | * @tparam T Тип компонентов вектора 62 | */ 63 | template 64 | struct Vec3 65 | { 66 | union { public: T x, r; }; 67 | union { public: T y, g; }; 68 | union { public: T z, b; }; 69 | 70 | Vec3() noexcept :x(0.0f),y(0.0f),z(0.0f){}; 71 | Vec3(const T& s1, const T& s2, const T& s3) noexcept :x(s1), y(s2), z(s3) {} 72 | 73 | Vec2 getVec2(){return {this->x,this->y};} 74 | 75 | Vec3 operator*(const T& value) const 76 | { 77 | return {this->x * value, this->y * value, this->z * value}; 78 | } 79 | 80 | Vec3 operator/(const T& value) const 81 | { 82 | return {this->x / value, this->y / value, this->z / value}; 83 | } 84 | 85 | Vec3 operator*(const Vec3& other) const 86 | { 87 | return {this->x * other.x, this->y * other.y, this->z * other.z}; 88 | } 89 | 90 | Vec3 operator+(const Vec3& other) const 91 | { 92 | return {this->x + other.x, this->y + other.y, this->z + other.z}; 93 | } 94 | 95 | Vec3 operator-(const Vec3& other) const 96 | { 97 | return {this->x - other.x, this->y - other.y, this->z - other.z}; 98 | } 99 | 100 | Vec3 operator-() const 101 | { 102 | return {-this->x, -this->y, -this->z}; 103 | } 104 | }; 105 | 106 | /** 107 | * 4-мерный вектор или точка в 4D-пространстве 108 | * @tparam T 109 | */ 110 | template 111 | struct Vec4 112 | { 113 | union { public: T x, r; }; 114 | union { public: T y, g; }; 115 | union { public: T z, b; }; 116 | union { public: T w, a; }; 117 | 118 | Vec4() noexcept :x(0.0f),y(0.0f),z(0.0f),w(0.0f){}; 119 | Vec4(const T& s1, const T& s2, const T& s3, const T& s4) noexcept :x(s1), y(s2), z(s3), w(s4) {} 120 | 121 | Vec3 getVec3(){return {this->x,this->y,this->z};} 122 | 123 | Vec4 operator*(const T& value) const 124 | { 125 | return {this->x * value, this->y * value, this->z * value, this->w * value}; 126 | } 127 | 128 | Vec4 operator/(const T& value) const 129 | { 130 | return {this->x / value, this->y / value, this->z / value, this->w / value}; 131 | } 132 | 133 | Vec4 operator*(const Vec4& other) const 134 | { 135 | return {this->x * other.x, this->y * other.y, this->z * other.z, this->w * other.w}; 136 | } 137 | 138 | Vec4 operator+(const Vec4& other) const 139 | { 140 | return {this->x + other.x, this->y + other.y, this->z + other.z, this->w + other.w}; 141 | } 142 | 143 | Vec4 operator-(const Vec4& other) const 144 | { 145 | return {this->x - other.x, this->y - other.y, this->z - other.z, this->w - other.w}; 146 | } 147 | 148 | Vec4 operator-() const 149 | { 150 | return {-this->x, -this->y, -this->z, -this->w}; 151 | } 152 | }; 153 | 154 | /** 155 | * Описывающий объект отрезок/прямоугольник/параллелипипе/тессеракт 156 | * @tparam T Тип (размерность) точки 157 | */ 158 | template > 159 | struct BBox 160 | { 161 | T min; 162 | T max; 163 | }; 164 | 165 | /** 166 | * Длина 2-мерного вектора 167 | * @tparam T Тип компонентов вектора 168 | * @param v Вектор 169 | * @return Длинна вектора 170 | */ 171 | template 172 | float Length(const Vec2& v) 173 | { 174 | return static_cast(sqrtf((v.x * v.x) + (v.y * v.y))); 175 | } 176 | 177 | /** 178 | * Длина 3-мерного вектора 179 | * @tparam T Тип компонентов вектора 180 | * @param v Вектор 181 | * @return Длинна вектора 182 | */ 183 | template 184 | float Length(const Vec3& v) 185 | { 186 | return static_cast(sqrtf((v.x * v.x) + (v.y * v.y) + (v.z * v.z))); 187 | } 188 | 189 | /** 190 | * Нормализация вектора 191 | * @tparam T Тип компонентов вектора 192 | * @param v Исходный 2-мерный вектор 193 | * @return Нормализованный 2-мерный вектор 194 | */ 195 | template 196 | Vec2 Normalize(const Vec2& v) 197 | { 198 | float len = Length(v); 199 | if(len > 0) return {v.x / len, v.y / len}; 200 | return {0,0}; 201 | } 202 | 203 | /** 204 | * Нормализация вектора 205 | * @tparam T Тип компонентов вектора 206 | * @param v Исходный 3-мерный вектор 207 | * @return Нормализованный 3-мерный вектор 208 | */ 209 | template 210 | Vec3 Normalize(const Vec3& v) 211 | { 212 | float len = Length(v); 213 | if(len > 0) return {v.x / len, v.y / len, v.z / len}; 214 | return {0,0,0}; 215 | } 216 | 217 | /** 218 | * Скалярное произведение 2-мерных векторов 219 | * @tparam T тип компонентов вектора 220 | * @param v1 Вектор 1 221 | * @param v2 Вектор 2 222 | * @return Произведение 223 | */ 224 | template 225 | T Dot(const Vec2& v1, const Vec2& v2) 226 | { 227 | return (v1.x + v2.x) * (v1.y + v2.y); 228 | } 229 | 230 | /** 231 | * Скалярное произведение 3-мерных векторов 232 | * @tparam T тип компонентов вектора 233 | * @param v1 Вектор 1 234 | * @param v2 Вектор 2 235 | * @return Произведение 236 | */ 237 | template 238 | T Dot(const Vec3& v1, const Vec3& v2) 239 | { 240 | return (v1.x * v2.x) + (v1.y * v2.y) + (v1.z * v2.z); 241 | } 242 | 243 | /** 244 | * Векторное произведение 3-мерных векторов 245 | * @tparam T тип компонентов вектора 246 | * @param v1 Вектор 1 247 | * @param v2 Вектор 2 248 | * @return Векторное произведение 249 | */ 250 | template 251 | Vec3 Cross(const Vec3& v1, const Vec3& v2) 252 | { 253 | return { 254 | (v1.y * v2.z - v1.z * v2.y), 255 | (v1.z * v2.x - v1.x * v2.z), 256 | (v1.x * v2.y - v1.y * v2.x) 257 | }; 258 | } 259 | 260 | /** 261 | * Получить отраженный вектор относительно нормали 262 | * @tparam T тип компонентов вектора 263 | * @param v Исходный вектор 264 | * @param normal Нормаль 265 | * @return Отраженный вектор 266 | */ 267 | template 268 | Vec3 Reflect(const Vec3& v, const Vec3& normal) 269 | { 270 | return v - normal * 2.0f * Dot(v, normal); 271 | } 272 | 273 | /** 274 | * Получить преломленный вектор (спизжено из GLM, что происходит - хер знает) 275 | * @tparam T тип компонентов вектора 276 | * @param v Исходный вектор 277 | * @param normal Нормаль 278 | * @param eta Коэффициент преломления 279 | * @return Отраженный вектор 280 | */ 281 | template 282 | Vec3 Refract(const Vec3& v, const Vec3& normal, float eta) 283 | { 284 | auto dot = math::Dot(v,normal); 285 | auto k = 1.0f - eta * eta * (1.0f - dot * dot); 286 | if (k < 0.0f) return {0.0f,0.0f,0.0f}; 287 | return (v * eta) - (normal * (eta * dot + sqrtf(k))); 288 | } 289 | 290 | /** 291 | * Интерполяция между двумя значениями 292 | * @tparam T тип значений 293 | * @param a Первое значение 294 | * @param b Второе значение 295 | * @param ratio Соотношение 296 | * @return Итоговое значение вектора 297 | */ 298 | template 299 | T Mix(const T& a, const T& b, R ratio) 300 | { 301 | return a + ((b - a) * ratio); 302 | } 303 | 304 | /** 305 | * Матрица 2x2 306 | * @tparam T Тип ячеек матрицы 307 | */ 308 | template 309 | struct Mat2 310 | { 311 | T data[4] = {}; 312 | 313 | Mat2() = default; 314 | 315 | explicit Mat2(T val){ 316 | data[0] = val; 317 | data[3] = val; 318 | } 319 | 320 | explicit Mat2(const Vec2& i, const Vec2& j) 321 | { 322 | data[0] = i.x; data[1] = j.x; 323 | data[2] = i.y; data[3] = j.y; 324 | } 325 | 326 | const T* row(size_t row) const { 327 | return this->data + 2 * row; 328 | } 329 | 330 | T* operator[](size_t row) { 331 | return this->data + 2 * row; 332 | } 333 | 334 | Mat2 operator*(const T& value) 335 | { 336 | Mat2 result = *this; 337 | for(T &n : result.data){ 338 | n *= value; 339 | } 340 | return result; 341 | } 342 | 343 | Vec2 operator*(const Vec2& v) const 344 | { 345 | return { 346 | data[0] * v.x + data[1] * v.y, 347 | data[2] * v.x + data[3] * v.y 348 | }; 349 | } 350 | 351 | Mat2 operator*(const Mat2& m) const 352 | { 353 | return Mat2( 354 | (*(this) * Vec2(m.data[0],m.data[2])), 355 | (*(this) * Vec2(m.data[1],m.data[3])) 356 | ); 357 | } 358 | }; 359 | 360 | /** 361 | * Матрица 3x3 362 | * @tparam T Тип ячеек матрицы 363 | */ 364 | template 365 | struct Mat3 366 | { 367 | T data[9] = {}; 368 | 369 | Mat3() = default; 370 | 371 | explicit Mat3(T val){ 372 | data[0] = val; 373 | data[4] = val; 374 | data[8] = val; 375 | } 376 | 377 | explicit Mat3(const Vec3& i, const Vec3& j, const Vec3& k) 378 | { 379 | data[0] = i.x; data[1] = j.x; data[2] = k.x; 380 | data[3] = i.y; data[4] = j.y; data[5] = k.y; 381 | data[6] = i.z; data[7] = j.z; data[8] = k.z; 382 | } 383 | 384 | const T* row(size_t row) const { 385 | return this->data + 3 * row; 386 | } 387 | 388 | T* operator[](size_t row) { 389 | return this->data + 3 * row; 390 | } 391 | 392 | Mat3 operator*(const T& value) 393 | { 394 | Mat3 result = *this; 395 | for(T &n : result.data){ 396 | n *= value; 397 | } 398 | return result; 399 | } 400 | 401 | Vec3 operator*(const Vec3& v) const 402 | { 403 | return { 404 | data[0] * v.x + data[1] * v.y + data[2] * v.z, 405 | data[3] * v.x + data[4] * v.y + data[5] * v.z, 406 | data[6] * v.x + data[7] * v.y + data[8] * v.z 407 | }; 408 | } 409 | 410 | Mat3 operator*(const Mat3& m) const 411 | { 412 | return Mat3( 413 | (*(this) * Vec3(m.data[0],m.data[3],m.data[6])), 414 | (*(this) * Vec3(m.data[1],m.data[4],m.data[7])), 415 | (*(this) * Vec3(m.data[2],m.data[5],m.data[8])) 416 | ); 417 | } 418 | }; 419 | 420 | /** 421 | * Матрица 4x4 422 | * @tparam T 423 | */ 424 | template 425 | struct Mat4 426 | { 427 | T data[16] = {}; 428 | 429 | Mat4() = default; 430 | 431 | explicit Mat4(T val){ 432 | data[0] = val; 433 | data[5] = val; 434 | data[10] = val; 435 | data[15] = val; 436 | } 437 | 438 | explicit Mat4(const Vec4& i, const Vec4& j, const Vec4& k, const Vec4& t) 439 | { 440 | data[0] = i.x; data[1] = j.x; data[2] = k.x; data[3] = t.x; 441 | data[4] = i.y; data[5] = j.y; data[6] = k.y; data[7] = t.y; 442 | data[8] = i.z; data[9] = j.z; data[10] = k.z; data[11] = t.z; 443 | data[12] = i.w; data[13] = j.w; data[14] = k.w; data[15] = t.w; 444 | } 445 | 446 | explicit Mat4(const Mat3& m3, const Vec4& t = {0.0f,0.0f,0.0f,1.0f}) 447 | { 448 | data[0] = m3.data[0]; data[1] = m3.data[1]; data[2] = m3.data[2]; data[3] = t.x; 449 | data[4] = m3.data[3]; data[5] = m3.data[4]; data[6] = m3.data[5]; data[7] = t.y; 450 | data[8] = m3.data[6]; data[9] = m3.data[7]; data[10] = m3.data[8]; data[11] = t.z; 451 | data[12] = 0.0f; data[13] = 0.0f; data[14] = 0.0f; data[15] = t.w; 452 | } 453 | 454 | const T* row(size_t row) const { 455 | return this->data + 4 * row; 456 | } 457 | 458 | T* operator[](size_t row) { 459 | return this->data + 4 * row; 460 | } 461 | 462 | Mat4 operator*(const T& value) 463 | { 464 | Mat4 result = *this; 465 | for(T &n : result.data){ 466 | n *= value; 467 | } 468 | return result; 469 | } 470 | 471 | Vec4 operator*(const Vec4& v) const 472 | { 473 | return { 474 | data[0] * v.x + data[1] * v.y + data[2] * v.z + data[3] * v.w, 475 | data[4] * v.x + data[5] * v.y + data[6] * v.z + data[7] * v.w, 476 | data[8] * v.x + data[9] * v.y + data[10] * v.z + data[11] * v.w, 477 | data[12] * v.x + data[13] * v.y + data[14] * v.z + data[15] * v.w, 478 | }; 479 | } 480 | 481 | Mat4 operator*(const Mat4& m) const 482 | { 483 | return Mat4( 484 | (*(this) * Vec4(m.data[0],m.data[4],m.data[8],m.data[12])), 485 | (*(this) * Vec4(m.data[1],m.data[5],m.data[9],m.data[13])), 486 | (*(this) * Vec4(m.data[2],m.data[6],m.data[10],m.data[14])), 487 | (*(this) * Vec4(m.data[3],m.data[7],m.data[11],m.data[15])) 488 | ); 489 | } 490 | }; 491 | 492 | /** 493 | * Транспонировать матрицу 2x2 494 | * @tparam T Тип ячеек матрицы 495 | * @param m Исходная матрица 496 | * @return Транспонированная матрица 497 | */ 498 | template 499 | Mat2 Transpose(const Mat2& m) 500 | { 501 | return Mat2( 502 | {m.data[0],m.data[1]}, 503 | {m.data[2],m.data[3]} 504 | ); 505 | } 506 | 507 | /** 508 | * Транспонировать матрицу 3x3 509 | * @tparam T Тип ячеек матрицы 510 | * @param m Исходная матрица 511 | * @return Транспонированная матрица 512 | */ 513 | template 514 | Mat3 Transpose(const Mat3& m) 515 | { 516 | return Mat3( 517 | {m.data[0],m.data[1],m.data[2]}, 518 | {m.data[3],m.data[4],m.data[5]}, 519 | {m.data[6],m.data[7],m.data[8]} 520 | ); 521 | } 522 | 523 | /** 524 | * Транспонировать матрицу 4x4 525 | * @tparam T Тип ячеек матрицы 526 | * @param m Исходная матрица 527 | * @return Транспонированная матрица 528 | */ 529 | template 530 | Mat4 Transpose(const Mat4& m) 531 | { 532 | return Mat4( 533 | {m.data[0],m.data[1],m.data[2],m.data[3]}, 534 | {m.data[4],m.data[5],m.data[6],m.data[7]}, 535 | {m.data[8],m.data[9],m.data[10],m.data[11]}, 536 | {m.data[12],m.data[13],m.data[14],m.data[15]} 537 | ); 538 | } 539 | 540 | /** 541 | * Определитель 2-го порядка 542 | * @tparam T Тип ячеек матрицы 543 | * @param m Матрица 2x2 544 | * @return Значение определителя 545 | */ 546 | template 547 | T Determinant(const Mat2& m) 548 | { 549 | return (m.row(0)[0] * m.row(1)[1]) - (m.row(0)[1] * m.row(1)[0]); 550 | } 551 | 552 | /** 553 | * Определитель 3-го порядка 554 | * @tparam T Тип ячеек матрицы 555 | * @param m Матрица 3x3 556 | * @return Значение определителя 557 | */ 558 | template 559 | T Determinant(const Mat3& m) 560 | { 561 | return (m.row(0)[0] * m.row(1)[1] * m.row(2)[2]) 562 | + (m.row(0)[1] * m.row(1)[2] * m.row(2)[0]) 563 | + (m.row(0)[2] * m.row(1)[0] * m.row(2)[1]) 564 | - (m.row(0)[2] * m.row(1)[1] * m.row(2)[0]) 565 | - (m.row(0)[0] * m.row(1)[2] * m.row(2)[1]) 566 | - (m.row(0)[1] * m.row(1)[0] * m.row(2)[2]); 567 | } 568 | 569 | /** 570 | * Определитель 4-го порядка 571 | * @tparam T Тип ячеек матрицы 572 | * @param m Матрица 4x4 573 | * @return Значение определителя 574 | */ 575 | template 576 | T Determinant(const Mat4& m) 577 | { 578 | return (m.row(0)[0] * Determinant(Mat3({m.row(1)[1],m.row(2)[1],m.row(3)[1]},{m.row(1)[2],m.row(2)[2],m.row(3)[2]},{m.row(1)[3],m.row(2)[3],m.row(3)[3]}))) 579 | - (m.row(0)[1] * Determinant(Mat3({m.row(1)[0],m.row(2)[0],m.row(3)[0]},{m.row(1)[2],m.row(2)[2],m.row(3)[2]},{m.row(1)[3],m.row(2)[3],m.row(3)[3]}))) 580 | + (m.row(0)[2] * Determinant(Mat3({m.row(1)[0],m.row(2)[0],m.row(3)[0]},{m.row(1)[1],m.row(2)[1],m.row(3)[1]},{m.row(1)[3],m.row(2)[3],m.row(3)[3]}))) 581 | - (m.row(0)[3] * Determinant(Mat3({m.row(1)[0],m.row(2)[0],m.row(3)[0]},{m.row(1)[1],m.row(2)[1],m.row(3)[1]},{m.row(1)[2],m.row(2)[2],m.row(3)[2]}))); 582 | } 583 | 584 | 585 | /** 586 | * Обратная матрица для матрицы 2x2 587 | * @tparam T Тип ячеек матрицы 588 | * @param m Исходная матрица 2x2 589 | * @return Обратная матрица 590 | */ 591 | template 592 | Mat2 Inverse(const Mat2& m) 593 | { 594 | Mat2 result; 595 | auto det = Determinant(m); 596 | 597 | if(det != 0){ 598 | result = Mat2({m.data[3],-m.data[2]},{-m.data[1],m.data[0]}) * (1 / det); 599 | } 600 | 601 | return result; 602 | } 603 | 604 | /** 605 | * Обратная матрица для матрицы 3x3 606 | * @tparam T Тип ячеек матрицы 607 | * @param m Исходная матрица 3x3 608 | * @return Обратная матрица 609 | */ 610 | template 611 | Mat3 Inverse(const Mat3& m) 612 | { 613 | Mat3 result; 614 | auto det = Determinant(m); 615 | 616 | if(det != 0){ 617 | T detInverse = 1 / det; 618 | result[0][0] = (m.row(1)[1] * m.row(2)[2] - m.row(2)[1] * m.row(1)[2]) * detInverse; 619 | result[0][1] = (m.row(0)[2] * m.row(2)[1] - m.row(0)[1] * m.row(2)[2]) * detInverse; 620 | result[0][2] = (m.row(0)[1] * m.row(1)[2] - m.row(0)[2] * m.row(1)[1]) * detInverse; 621 | result[1][0] = (m.row(1)[2] * m.row(2)[0] - m.row(1)[0] * m.row(2)[2]) * detInverse; 622 | result[1][1] = (m.row(0)[0] * m.row(2)[2] - m.row(0)[2] * m.row(2)[0]) * detInverse; 623 | result[1][2] = (m.row(1)[0] * m.row(0)[2] - m.row(0)[0] * m.row(1)[2]) * detInverse; 624 | result[2][0] = (m.row(1)[0] * m.row(2)[1] - m.row(2)[0] * m.row(1)[1]) * detInverse; 625 | result[2][1] = (m.row(2)[0] * m.row(0)[1] - m.row(0)[0] * m.row(2)[1]) * detInverse; 626 | result[2][2] = (m.row(0)[0] * m.row(1)[1] - m.row(1)[0] * m.row(0)[1]) * detInverse; 627 | } 628 | 629 | return result; 630 | } 631 | 632 | /** 633 | * Обратная матрица для матрицы 4x4 634 | * @tparam T Тип ячеек матрицы 635 | * @param m Исходная матрица 4x4 636 | * @return Обратная матрица 637 | */ 638 | template 639 | Mat4 Inverse(const Mat4& m) 640 | { 641 | Mat4 inv; 642 | 643 | inv.data[0] = m.data[5] * m.data[10] * m.data[15] - 644 | m.data[5] * m.data[11] * m.data[14] - 645 | m.data[9] * m.data[6] * m.data[15] + 646 | m.data[9] * m.data[7] * m.data[14] + 647 | m.data[13] * m.data[6] * m.data[11] - 648 | m.data[13] * m.data[7] * m.data[10]; 649 | 650 | inv.data[4] = -m.data[4] * m.data[10] * m.data[15] + 651 | m.data[4] * m.data[11] * m.data[14] + 652 | m.data[8] * m.data[6] * m.data[15] - 653 | m.data[8] * m.data[7] * m.data[14] - 654 | m.data[12] * m.data[6] * m.data[11] + 655 | m.data[12] * m.data[7] * m.data[10]; 656 | 657 | inv.data[8] = m.data[4] * m.data[9] * m.data[15] - 658 | m.data[4] * m.data[11] * m.data[13] - 659 | m.data[8] * m.data[5] * m.data[15] + 660 | m.data[8] * m.data[7] * m.data[13] + 661 | m.data[12] * m.data[5] * m.data[11] - 662 | m.data[12] * m.data[7] * m.data[9]; 663 | 664 | inv.data[12] = -m.data[4] * m.data[9] * m.data[14] + 665 | m.data[4] * m.data[10] * m.data[13] + 666 | m.data[8] * m.data[5] * m.data[14] - 667 | m.data[8] * m.data[6] * m.data[13] - 668 | m.data[12] * m.data[5] * m.data[10] + 669 | m.data[12] * m.data[6] * m.data[9]; 670 | 671 | inv.data[1] = -m.data[1] * m.data[10] * m.data[15] + 672 | m.data[1] * m.data[11] * m.data[14] + 673 | m.data[9] * m.data[2] * m.data[15] - 674 | m.data[9] * m.data[3] * m.data[14] - 675 | m.data[13] * m.data[2] * m.data[11] + 676 | m.data[13] * m.data[3] * m.data[10]; 677 | 678 | inv.data[5] = m.data[0] * m.data[10] * m.data[15] - 679 | m.data[0] * m.data[11] * m.data[14] - 680 | m.data[8] * m.data[2] * m.data[15] + 681 | m.data[8] * m.data[3] * m.data[14] + 682 | m.data[12] * m.data[2] * m.data[11] - 683 | m.data[12] * m.data[3] * m.data[10]; 684 | 685 | inv.data[9] = -m.data[0] * m.data[9] * m.data[15] + 686 | m.data[0] * m.data[11] * m.data[13] + 687 | m.data[8] * m.data[1] * m.data[15] - 688 | m.data[8] * m.data[3] * m.data[13] - 689 | m.data[12] * m.data[1] * m.data[11] + 690 | m.data[12] * m.data[3] * m.data[9]; 691 | 692 | inv.data[13] = m.data[0] * m.data[9] * m.data[14] - 693 | m.data[0] * m.data[10] * m.data[13] - 694 | m.data[8] * m.data[1] * m.data[14] + 695 | m.data[8] * m.data[2] * m.data[13] + 696 | m.data[12] * m.data[1] * m.data[10] - 697 | m.data[12] * m.data[2] * m.data[9]; 698 | 699 | inv.data[2] = m.data[1] * m.data[6] * m.data[15] - 700 | m.data[1] * m.data[7] * m.data[14] - 701 | m.data[5] * m.data[2] * m.data[15] + 702 | m.data[5] * m.data[3] * m.data[14] + 703 | m.data[13] * m.data[2] * m.data[7] - 704 | m.data[13] * m.data[3] * m.data[6]; 705 | 706 | inv.data[6] = -m.data[0] * m.data[6] * m.data[15] + 707 | m.data[0] * m.data[7] * m.data[14] + 708 | m.data[4] * m.data[2] * m.data[15] - 709 | m.data[4] * m.data[3] * m.data[14] - 710 | m.data[12] * m.data[2] * m.data[7] + 711 | m.data[12] * m.data[3] * m.data[6]; 712 | 713 | inv.data[10] = m.data[0] * m.data[5] * m.data[15] - 714 | m.data[0] * m.data[7] * m.data[13] - 715 | m.data[4] * m.data[1] * m.data[15] + 716 | m.data[4] * m.data[3] * m.data[13] + 717 | m.data[12] * m.data[1] * m.data[7] - 718 | m.data[12] * m.data[3] * m.data[5]; 719 | 720 | inv.data[14] = -m.data[0] * m.data[5] * m.data[14] + 721 | m.data[0] * m.data[6] * m.data[13] + 722 | m.data[4] * m.data[1] * m.data[14] - 723 | m.data[4] * m.data[2] * m.data[13] - 724 | m.data[12] * m.data[1] * m.data[6] + 725 | m.data[12] * m.data[2] * m.data[5]; 726 | 727 | inv.data[3] = -m.data[1] * m.data[6] * m.data[11] + 728 | m.data[1] * m.data[7] * m.data[10] + 729 | m.data[5] * m.data[2] * m.data[11] - 730 | m.data[5] * m.data[3] * m.data[10] - 731 | m.data[9] * m.data[2] * m.data[7] + 732 | m.data[9] * m.data[3] * m.data[6]; 733 | 734 | inv.data[7] = m.data[0] * m.data[6] * m.data[11] - 735 | m.data[0] * m.data[7] * m.data[10] - 736 | m.data[4] * m.data[2] * m.data[11] + 737 | m.data[4] * m.data[3] * m.data[10] + 738 | m.data[8] * m.data[2] * m.data[7] - 739 | m.data[8] * m.data[3] * m.data[6]; 740 | 741 | inv.data[11] = -m.data[0] * m.data[5] * m.data[11] + 742 | m.data[0] * m.data[7] * m.data[9] + 743 | m.data[4] * m.data[1] * m.data[11] - 744 | m.data[4] * m.data[3] * m.data[9] - 745 | m.data[8] * m.data[1] * m.data[7] + 746 | m.data[8] * m.data[3] * m.data[5]; 747 | 748 | inv.data[15] = m.data[0] * m.data[5] * m.data[10] - 749 | m.data[0] * m.data[6] * m.data[9] - 750 | m.data[4] * m.data[1] * m.data[10] + 751 | m.data[4] * m.data[2] * m.data[9] + 752 | m.data[8] * m.data[1] * m.data[6] - 753 | m.data[8] * m.data[2] * m.data[5]; 754 | 755 | auto det = m.data[0] * inv.data[0] + m.data[1] * inv.data[4] + m.data[2] * inv.data[8] + m.data[3] * inv.data[12]; 756 | 757 | if (det == 0) return Mat4(); 758 | 759 | return inv * (1/det); 760 | } 761 | 762 | /** 763 | * Вращать вектор или точку вокуруг оси X 764 | * @tparam T Тип компонентов 765 | * @param v Вектор или точка 766 | * @param angle Угол 767 | * @return Вектор или точка после вращения 768 | */ 769 | template 770 | Vec3 RotateAroundX(const Vec3& v, const float& angle) 771 | { 772 | auto angleRad = angle * (M_PI / 180.0f); 773 | 774 | return { 775 | v.x, 776 | (v.y * cosf(angleRad)) - (v.z * sinf(angleRad)), 777 | (v.y * sinf(angleRad)) + (v.z * cosf(angleRad)) 778 | }; 779 | } 780 | 781 | /** 782 | * Вращать вектор или точку вокуруг оси Y 783 | * @tparam T Тип компонентов 784 | * @param v Вектор или точка 785 | * @param angle Угол 786 | * @return Вектор или точка после вращения 787 | */ 788 | template 789 | Vec3 RotateAroundY(const Vec3& v, const float& angle) 790 | { 791 | auto angleRad = angle * (M_PI / 180.0f); 792 | 793 | return { 794 | (v.x * cosf(angleRad)) + (v.z * sinf(angleRad)), 795 | v.y, 796 | -(v.x * sinf(angleRad)) + (v.z * cosf(angleRad)) 797 | }; 798 | } 799 | 800 | /** 801 | * Вращать вектор или точку вокуруг оси Z 802 | * @tparam T Тип компонентов 803 | * @param v Вектор или точка 804 | * @param angle Угол 805 | * @return Вектор или точка после вращения 806 | */ 807 | template 808 | Vec3 RotateAroundZ(const Vec3& v, const float& angle) 809 | { 810 | auto angleRad = angle * (M_PI / 180.0f); 811 | 812 | return { 813 | (v.x * cosf(angleRad)) - (v.y * sinf(angleRad)), 814 | (v.x * sinf(angleRad)) + (v.y * cosf(angleRad)), 815 | v.z 816 | }; 817 | } 818 | 819 | /** 820 | * Вращать вектор или точку на плоскости 821 | * @tparam T Тип компонентов 822 | * @param v Вектор или точка 823 | * @param angle Угол 824 | * @return Вектор или точка после вращения 825 | */ 826 | template 827 | Vec2 Rotate2D(const Vec2& v, const float& angle) 828 | { 829 | auto angleRad = angle * (M_PI / 180.0f); 830 | 831 | return { 832 | (v.x * cosf(angleRad)) - (v.y * sinf(angleRad)), 833 | (v.x * sinf(angleRad)) + (v.y * cosf(angleRad)) 834 | }; 835 | } 836 | 837 | 838 | /** 839 | * Получить матрицу поворта вокруг оси X 840 | * @tparam T Тип компонентов 841 | * @param angle Угол в градусах 842 | * @return Матрица 3*3 843 | */ 844 | template 845 | Mat3 GetRotationMatX(const float& angle) 846 | { 847 | auto angleRad = angle * (M_PI / 180.0f); 848 | return Mat3( 849 | {1.0f,0.0f,0.0f}, 850 | {0.0f,cosf(angleRad),sinf(angleRad)}, 851 | {0.0f,-sinf(angleRad),cosf(angleRad)}); 852 | } 853 | 854 | /** 855 | * Получить матрицу поворта вокруг оси Y 856 | * @tparam T Тип компонентов 857 | * @param angle Угол в градусах 858 | * @return Матрица 3*3 859 | */ 860 | template 861 | Mat3 GetRotationMatY(const float& angle) 862 | { 863 | auto angleRad = angle * (M_PI / 180.0f); 864 | return Mat3( 865 | {cosf(angleRad),0.0f,-sinf(angleRad)}, 866 | {0.0f,1.0f,0.0f}, 867 | {sinf(angleRad),0.0f,cosf(angleRad)}); 868 | } 869 | 870 | /** 871 | * Получить матрицу поворта вокруг оси Z 872 | * @tparam T Тип компонентов 873 | * @param angle Угол в градусах 874 | * @return Матрица 3*3 875 | */ 876 | template 877 | Mat3 GetRotationMatZ(const float& angle) 878 | { 879 | auto angleRad = angle * (M_PI / 180.0f); 880 | return Mat3( 881 | {cosf(angleRad),sinf(angleRad),0.0f}, 882 | {-sinf(angleRad),cosf(angleRad),0.0f}, 883 | {0.0f,0.0f,1.0f}); 884 | } 885 | 886 | /** 887 | * Получить матрицу поворота вокруг всех осей 888 | * @tparam T Тип компонентов 889 | * @param angles Углы (в градусах) 890 | * @return Матрица 3*3 891 | */ 892 | template 893 | Mat3 GetRotationMat(const Vec3& angles) 894 | { 895 | return GetRotationMatY(angles.y) * GetRotationMatX(angles.x) * GetRotationMatZ(angles.z); 896 | } 897 | 898 | /** 899 | * Получить матрицу поворота вокруг всех осей (4x4) 900 | * @tparam T Тип компонентов 901 | * @param angles Углы (в градусах) 902 | * @return Матрица 4*4 903 | */ 904 | template 905 | Mat4 GetRotationMat4(const Vec3& angles) 906 | { 907 | auto rotMat3 = GetRotationMat(angles); 908 | return math::Mat4( 909 | {rotMat3[0][0],rotMat3[0][1],rotMat3[0][2],0}, 910 | {rotMat3[1][0],rotMat3[1][1],rotMat3[1][2],0}, 911 | {rotMat3[2][0],rotMat3[2][1],rotMat3[2][2],0}, 912 | {0,0,0,1}); 913 | } 914 | 915 | /** 916 | * Получить матрицу масштабирования 917 | * @tparam T Тип компонентов 918 | * @param scale Масштаб по всем осям 919 | * @return Матрица 3*3 920 | */ 921 | template 922 | Mat3 GetScaleMat(const Vec3& scale) 923 | { 924 | return Mat3({scale.x,0.0f,0.0f},{0.0f,scale.y,0.0f},{0.0f,0.0f,scale.z}); 925 | } 926 | 927 | /** 928 | * Получить матрицу масштабирования (4x4) 929 | * @tparam T Тип компонентов 930 | * @param scale Масштаб по всем осям 931 | * @return Матрица 4*4 932 | */ 933 | template 934 | Mat4 GetScaleMat4(const Vec3& scale) 935 | { 936 | auto scaleMat3 = GetScaleMat(scale); 937 | return math::Mat4( 938 | {scaleMat3[0][0], scaleMat3[0][1], scaleMat3[0][2], 0}, 939 | {scaleMat3[1][0], scaleMat3[1][1], scaleMat3[1][2], 0}, 940 | {scaleMat3[2][0], scaleMat3[2][1], scaleMat3[2][2], 0}, 941 | {0,0,0,1}); 942 | } 943 | 944 | /** 945 | * Получить матрицу смещения (4x4) 946 | * @tparam T Тип компонентов 947 | * @param v Вектор смещения 948 | * @return Матрица 4*4 949 | */ 950 | template 951 | Mat4 GetTranslationMat4(const Vec3& v) 952 | { 953 | return math::Mat4( 954 | {1,0,0,0}, 955 | {0,1,0,0}, 956 | {0,0,1,0}, 957 | {v.x,v.y,v.z,1}); 958 | } 959 | 960 | /** 961 | * Ортогональная проекция точки 962 | * @tparam T Тип компонентов 963 | * @param point Исходная точка 964 | * @param left Левая грань видимой области (в NDC-координатах) 965 | * @param right Права грань видимой области (в NDC-координатах) 966 | * @param bottom Нижняя грань видимой области (в NDC-координатах) 967 | * @param top Верхняя грань видимой области (в NDC-координатах) 968 | * @param zNear Ближняя грань видимой области (0 для Z значения) 969 | * @param zFar Дальняя грань видимой области (1 для Z значения) 970 | * @param aspectRatio Пропорции экрана 971 | * @return Спроецированная точка 972 | */ 973 | template 974 | Vec3 ProjectOrthogonal(const Vec3& point, T left, T right, T bottom, T top, T zNear, T zFar, T aspectRatio = 1) 975 | { 976 | left *= aspectRatio; 977 | right *= aspectRatio; 978 | 979 | return { 980 | ((point.x - left) / ((right - left) / static_cast(2))) - static_cast(1), 981 | ((point.y - bottom) / ((top - bottom) / static_cast(2))) - static_cast(1), 982 | (point.z + zNear) / (zNear - zFar) 983 | }; 984 | } 985 | 986 | /** 987 | * Перспективная проекция точки 988 | * @tparam T Тип компонентов 989 | * @param point Исходная точка 990 | * @param fov Угол обзора 991 | * @param zNear Ближняя грань видимой области (0 для Z значения) 992 | * @param zFar Дальняя грань видимой области (1 для Z значения) 993 | * @param aspectRatio Пропорции экрана 994 | * @return Спроецированная точка 995 | */ 996 | template 997 | Vec3 ProjectPerspective(const Vec3& point, const float& fov, T zNear, T zFar, T aspectRatio = 1) 998 | { 999 | auto halfFovRad = (fov / 2) * (M_PI / 180.0f); 1000 | 1001 | return { 1002 | (point.x * (-1/(tanf(halfFovRad) * aspectRatio))) / point.z, 1003 | (point.y * (-1/tanf(halfFovRad))) / point.z, 1004 | //(point.z + zNear) / (zNear - zFar) 1005 | ((point.z * (-zFar / (zNear - zFar))) + ((zFar * zNear) / (zFar - zNear))) / point.z 1006 | }; 1007 | } 1008 | 1009 | /** 1010 | * Получить матрицу ортогональной проекции 1011 | * @tparam T Тип компонентов 1012 | * @param left Левая грань видимой области (в NDC-координатах) 1013 | * @param right Права грань видимой области (в NDC-координатах) 1014 | * @param bottom Нижняя грань видимой области (в NDC-координатах) 1015 | * @param top Верхняя грань видимой области (в NDC-координатах) 1016 | * @param zNear Ближняя грань видимой области (0 для Z значения) 1017 | * @param zFar Дальняя грань видимой области (1 для Z значения) 1018 | * @param aspectRatio Пропорции экрана 1019 | * @return Матрица 4*4 1020 | */ 1021 | template 1022 | Mat4 GetProjectionMatOrthogonal(T left, T right, T bottom, T top, T zNear, T zFar, T aspectRatio = 1) 1023 | { 1024 | left *= aspectRatio; 1025 | right *= aspectRatio; 1026 | 1027 | auto dx = (right - left); 1028 | auto dy = (top - bottom); 1029 | auto dz = (zFar - zNear); 1030 | 1031 | return Mat4( 1032 | {(static_cast(2) / dx),0,0,0}, 1033 | {0,(static_cast(2) / dy),0,0}, 1034 | {0,0,-(static_cast(1) / dz),0}, 1035 | {-(right + left) / dx, -(top + bottom) / dy,-zNear / dz,1}); 1036 | } 1037 | 1038 | /** 1039 | * Получить матрицу перспективной проекции 1040 | * @tparam T Тип компонентов 1041 | * @param fov Угол обзора 1042 | * @param aspectRatio Пропорции экрана 1043 | * @param zNear Ближняя грань видимой области (0 для Z значения) 1044 | * @param zFar Дальняя грань видимой области (1 для Z значения) 1045 | * @return Матрица 4*4 1046 | */ 1047 | template 1048 | Mat4 GetProjectionMatPerspective(T fov, T aspectRatio, T zNear, T zFar) 1049 | { 1050 | auto halfFovRad = (fov / 2) * (M_PI / 180.0f); 1051 | 1052 | return Mat4( 1053 | {static_cast(1)/(tanf(halfFovRad) * aspectRatio), 0, 0, 0}, 1054 | {0,static_cast(1)/tanf(halfFovRad), 0, 0}, 1055 | {0,0,-zFar / (zNear - zFar),-1}, 1056 | {0,0,(zFar * zNear) / (zFar - zNear),0}); 1057 | } 1058 | 1059 | /** 1060 | * Перевод координат точки из клип-пространства в пространство экрана 1061 | * @tparam T Тип компонентов 1062 | * @param point Исходнач точка в клип-пространстве 1063 | * @param width Ширина экрана в пикселях 1064 | * @param height Высота экрана в пикселях 1065 | * @return Точка в координатах экрана (верхний левый угол - начало координат) 1066 | */ 1067 | template 1068 | Vec2 NdcToScreen(const Vec2& point, unsigned width, unsigned height) 1069 | { 1070 | return { 1071 | static_cast(((point.x + 1.0f)/2.0f) * (width-1)), 1072 | static_cast(((-point.y + 1.0f)/2.0f) * (height-1)), 1073 | }; 1074 | } 1075 | } --------------------------------------------------------------------------------