From f0cec011447aa4334905a4e92eccc34e457e0795 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Sun, 1 Feb 2026 19:40:50 -0500 Subject: [PATCH 01/58] [imageloader] add stb image support --- .gitignore | 4 + dev/src/config.h | 46 +++++----- dev/src/core.cpp | 48 +++++------ dev/src/imload_stb_image.cpp | 67 ++++++++++++++ dev/src/imload_stb_image.h | 37 ++++++++ dev/src/sh_template.h | 15 ++-- olcPixelGameEngine3.h | 163 ++++++++++++++++++++++++++--------- 7 files changed, 285 insertions(+), 95 deletions(-) create mode 100644 dev/src/imload_stb_image.cpp create mode 100644 dev/src/imload_stb_image.h diff --git a/.gitignore b/.gitignore index 30693aa5..2f86d111 100644 --- a/.gitignore +++ b/.gitignore @@ -81,3 +81,7 @@ examples/xdg-decoration.o examples/xdg-shell.c examples/xdg-shell.h examples/xdg-shell.o + +# stb image headerss +stb_image_write.h +stb_image.h diff --git a/dev/src/config.h b/dev/src/config.h index 9967d0a6..5b5f0b87 100644 --- a/dev/src/config.h +++ b/dev/src/config.h @@ -71,38 +71,44 @@ #define OLC_GPU OLC_GPU_OPENGL33 #endif - - #define OLC_IMAGELOADER_NONE 1 #define OLC_IMAGELOADER_WINGDI 2 #define OLC_IMAGELOADER_MACOS 3 #define OLC_IMAGELOADER_LIB_PNG 4 #define OLC_IMAGELOADER_NDK_IMAGEDECODER 5 +#define OLC_IMAGELOADER_STB_IMAGE 6 -#if OLC_HOST == OLC_HOST_MACOS - #undef OLC_IMAGELOADER - #define OLC_IMAGELOADER OLC_IMAGELOADER_MACOS +#if defined(OLC_USE_STB_IMAGE) + #define OLC_IMAGELOADER OLC_IMAGELOADER_STB_IMAGE + #define OLC_IMAGELOADER_CLASS ImageLoader_STB_Image #endif -#if OLC_HOST == OLC_HOST_LINUX_X11 || OLC_HOST == OLC_HOST_LINUX_WAYLAND - #undef OLC_IMAGELOADER - #define OLC_IMAGELOADER OLC_IMAGELOADER_LIB_PNG -#endif +#if !defined(OLC_IMAGELOADER) + #if OLC_HOST == OLC_HOST_WINDOWS + #define OLC_IMAGELOADER OLC_IMAGELOADER_WINGDI + #define OLC_IMAGELOADER_CLASS ImageLoader_WinGDI + #endif -#if OLC_HOST == OLC_HOST_EMSCRIPTEN - #undef OLC_IMAGELOADER - #define OLC_IMAGELOADER OLC_IMAGELOADER_LIB_PNG -#endif + #if OLC_HOST == OLC_HOST_MACOS + #define OLC_IMAGELOADER OLC_IMAGELOADER_MACOS + #define OLC_IMAGELOADER_CLASS ImageLoader_MacOS + #endif -#if OLC_HOST == OLC_HOST_ANDROID - #undef OLC_IMAGELOADER - #define OLC_IMAGELOADER OLC_IMAGELOADER_NDK_IMAGEDECODER -#endif + #if OLC_HOST == OLC_HOST_LINUX_X11 || OLC_HOST == OLC_HOST_LINUX_WAYLAND + #define OLC_IMAGELOADER OLC_IMAGELOADER_LIB_PNG + #define OLC_IMAGELOADER_CLASS ImageLoader_LibPNG + #endif -#if !defined(OLC_IMAGELOADER) - #define OLC_IMAGELOADER OLC_IMAGELOADER_WINGDI -#endif + #if OLC_HOST == OLC_HOST_EMSCRIPTEN + #define OLC_IMAGELOADER OLC_IMAGELOADER_LIB_PNG + #define OLC_IMAGELOADER_CLASS ImageLoader_LibPNG + #endif + #if OLC_HOST == OLC_HOST_ANDROID + #define OLC_IMAGELOADER OLC_IMAGELOADER_NDK_IMAGEDECODER + #define OLC_IMAGELOADER_CLASS ImageLoader_NDKImageDecoder + #endif +#endif #define OLC_MULTIWINDOW_NO 1 #define OLC_MULTIWINDOW_YES 2 diff --git a/dev/src/core.cpp b/dev/src/core.cpp index 7cec77a0..b9061120 100644 --- a/dev/src/core.cpp +++ b/dev/src/core.cpp @@ -4,35 +4,49 @@ #if OLC_HOST == OLC_HOST_WINDOWS #include "host_win_winapi.h" -#include "imload_wingdi.h" #endif // Johnnyg63: Added define for MACOS #if OLC_HOST == OLC_HOST_MACOS #include "host_apple_macos.h" -#include "imload_macos.h" #endif #if OLC_HOST == OLC_HOST_LINUX_X11 #include "host_lin_x11.h" -#include "imload_lib_png.h" #endif #if OLC_HOST == OLC_HOST_LINUX_WAYLAND #include "host_lin_wayland.h" -#include "imload_lib_png.h" #endif #if OLC_HOST == OLC_HOST_EMSCRIPTEN #include "host_web_emscripten.h" -#include "imload_lib_png.h" #endif #if OLC_HOST == OLC_HOST_ANDROID #include "host_android.h" +#endif + +#if OLC_IMAGELOADER == OLC_IMAGELOADER_WINGDI +#include "imload_wingdi.h" +#endif + +#if OLC_IMAGELOADER == OLC_IMAGELOADER_MACOS +#include "imload_macos.h" +#endif + +#if OLC_IMAGELOADER == OLC_IMAGELOADER_LIB_PNG +#include "imload_lib_png.h" +#endif + +#if OLC_IMAGELOADER == OLC_IMAGELOADER_NDK_IMAGEDECODER #include "imload_android.h" #endif +#if OLC_IMAGELOADER == OLC_IMAGELOADER_STB_IMAGE +#include "imload_stb_image.h" +#endif + //! START IMPLEMENTATION namespace olc { @@ -305,30 +319,12 @@ namespace olc // DEVS!! Please don't merge these just yet // Initialise ImageLoader Interface -#if OLC_HOST == OLC_HOST_WINDOWS - imageloader = std::make_unique(); -#endif - -#if OLC_HOST == OLC_HOST_MACOS - imageloader = std::make_unique(); -#endif - -#if OLC_HOST == OLC_HOST_LINUX_X11 - imageloader = std::make_unique(); -#endif - -#if OLC_HOST == OLC_HOST_LINUX_WAYLAND - imageloader = std::make_unique(); -#endif - -#if OLC_HOST == OLC_HOST_EMSCRIPTEN - imageloader = std::make_unique(); -#endif - #if OLC_HOST == OLC_HOST_ANDROID - imageloader = std::make_unique( + imageloader = std::make_unique( olc::host::Host_Android::androidApp->activity->assetManager ); +#else + imageloader = std::make_unique(); #endif // Allow host to prepare itself diff --git a/dev/src/imload_stb_image.cpp b/dev/src/imload_stb_image.cpp new file mode 100644 index 00000000..029c2140 --- /dev/null +++ b/dev/src/imload_stb_image.cpp @@ -0,0 +1,67 @@ +#include "imload_stb_image.h" + +//! START IMPLEMENTATION +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "stb_image_write.h" + +namespace olc::imload +{ + + // Create an image resource based on an image file asset on disk + bool ImageLoader_STB_Image::CreateImageFromFile(olc::Image& image, const std::string& sFileName) + { + std::cout << "ImageLoader: using stb image to load " << sFileName << ".\n"; + + // Open file + if(!std::filesystem::exists(sFileName)) + { + std::cout << "Error: failed to load image <" << sFileName << "> - file not found.\n"; + return false; + } + + stbi_uc* bytes = nullptr; + int width = 0, height = 0, cmp = 0; + bytes = stbi_load(sFileName.c_str(), &width, &height, &cmp, 4); + + if(!bytes) + { + std::cout << "Error: failed to load image <" << sFileName << "> - failed to allocate memory.\n"; + return false; + } + + image.Create({width, height}); + std::memcpy(reinterpret_cast(image.Data()), bytes, width * height * 4); + + delete[] bytes; + + return true; + } + + // Create an image resource based on an image file asset in memory + bool ImageLoader_STB_Image::CreateImageFromMemory(olc::Image& image, const uint8_t* data, const size_t bytes) + { + return false; + } + + // Create an image resource based on an image file asset in memory + bool ImageLoader_STB_Image::CreateImageFromMemory(olc::Image& image, const std::vector& data) + { + return false; + } + + // Store an image as a file asset on disk + bool ImageLoader_STB_Image::WriteImageToFile(const olc::Image& image, const std::string& sFileName) + { + return false; + } + + // Store an image as a file asset in memory + bool ImageLoader_STB_Image::WriteImageToMemoryFile(olc::Image& image, const std::vector& data) + { + return false; + } + +} +//! END IMPLEMENTATION diff --git a/dev/src/imload_stb_image.h b/dev/src/imload_stb_image.h new file mode 100644 index 00000000..a2002808 --- /dev/null +++ b/dev/src/imload_stb_image.h @@ -0,0 +1,37 @@ +#pragma once + +//! START CUSTOMHEADER +#include "imload_iface.h" +//! END CUSTOMHEADER + +//! START STDHEADER GLOBAL +#include +//! END STDHEADER + +//! START DECLARATION +#if !defined(PGE_IMAGELOADER_LIB_PNG_DECLARED) +namespace olc::imload +{ + class ImageLoader_STB_Image : public ImageLoader + { + // Create an image resource based on an image file asset on disk + bool CreateImageFromFile(olc::Image& image, const std::string& sFileName) override; + + // Create an image resource based on an image file asset in memory + bool CreateImageFromMemory(olc::Image& image, const uint8_t* data, const size_t bytes) override; + + // Create an image resource based on an image file asset in memory + bool CreateImageFromMemory(olc::Image& image, const std::vector& data) override; + + // Store an image as a file asset on disk + bool WriteImageToFile(const olc::Image& image, const std::string& sFileName) override; + + // Store an image as a file asset in memory + bool WriteImageToMemoryFile(olc::Image& image, const std::vector& data) override; + + }; +} + +#define PGE_IMAGELOADER_LIB_PNG_DECLARED 1 +#endif +//! END DECLARATION diff --git a/dev/src/sh_template.h b/dev/src/sh_template.h index 40ca4410..c6e06c00 100644 --- a/dev/src/sh_template.h +++ b/dev/src/sh_template.h @@ -213,14 +213,13 @@ //! GRAB imload_lib_png.h DECLARATION #endif -#if OLC_HOST == OLC_HOST_ANDROID +#if OLC_IMAGELOADER == OLC_IMAGELOADER_NDK_IMAGEDECODER //! GRAB imload_android.h DECLARATION #endif - - - - +#if OLC_IMAGELOADER == OLC_IMAGELOADER_STB_IMAGE +//! GRAB imload_stb_image.h DECLARATION +#endif #if defined(OLC_PGE3_APPLICATION) && !defined(PGE_HOST_IMPLEMENTED) @@ -308,9 +307,13 @@ #if OLC_IMAGELOADER == OLC_IMAGELOADER_LIB_PNG //! GRAB imload_lib_png.cpp IMPLEMENTATION #endif -#if OLC_HOST == OLC_HOST_ANDROID +#if OLC_IMAGELOADER == OLC_IMAGELOADER_NDK_IMAGEDECODER //! GRAB imload_android.cpp IMPLEMENTATION #endif +#if OLC_IMAGELOADER == OLC_IMAGELOADER_STB_IMAGE +//! GRAB imload_stb_image.cpp IMPLEMENTATION +#endif + #define PGE_IMAGELOADER_IMPLEMENTED 1 #endif diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 4e5930ee..aa0eb0f5 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -205,38 +205,44 @@ #define OLC_GPU OLC_GPU_OPENGL33 #endif - - #define OLC_IMAGELOADER_NONE 1 #define OLC_IMAGELOADER_WINGDI 2 #define OLC_IMAGELOADER_MACOS 3 #define OLC_IMAGELOADER_LIB_PNG 4 #define OLC_IMAGELOADER_NDK_IMAGEDECODER 5 +#define OLC_IMAGELOADER_STB_IMAGE 6 -#if OLC_HOST == OLC_HOST_MACOS - #undef OLC_IMAGELOADER - #define OLC_IMAGELOADER OLC_IMAGELOADER_MACOS +#if defined(OLC_USE_STB_IMAGE) + #define OLC_IMAGELOADER OLC_IMAGELOADER_STB_IMAGE + #define OLC_IMAGELOADER_CLASS ImageLoader_STB_Image #endif -#if OLC_HOST == OLC_HOST_LINUX_X11 || OLC_HOST == OLC_HOST_LINUX_WAYLAND - #undef OLC_IMAGELOADER - #define OLC_IMAGELOADER OLC_IMAGELOADER_LIB_PNG -#endif +#if !defined(OLC_IMAGELOADER) + #if OLC_HOST == OLC_HOST_WINDOWS + #define OLC_IMAGELOADER OLC_IMAGELOADER_WINGDI + #define OLC_IMAGELOADER_CLASS ImageLoader_WinGDI + #endif -#if OLC_HOST == OLC_HOST_EMSCRIPTEN - #undef OLC_IMAGELOADER - #define OLC_IMAGELOADER OLC_IMAGELOADER_LIB_PNG -#endif + #if OLC_HOST == OLC_HOST_MACOS + #define OLC_IMAGELOADER OLC_IMAGELOADER_MACOS + #define OLC_IMAGELOADER_CLASS ImageLoader_MacOS + #endif -#if OLC_HOST == OLC_HOST_ANDROID - #undef OLC_IMAGELOADER - #define OLC_IMAGELOADER OLC_IMAGELOADER_NDK_IMAGEDECODER -#endif + #if OLC_HOST == OLC_HOST_LINUX_X11 || OLC_HOST == OLC_HOST_LINUX_WAYLAND + #define OLC_IMAGELOADER OLC_IMAGELOADER_LIB_PNG + #define OLC_IMAGELOADER_CLASS ImageLoader_LibPNG + #endif -#if !defined(OLC_IMAGELOADER) - #define OLC_IMAGELOADER OLC_IMAGELOADER_WINGDI -#endif + #if OLC_HOST == OLC_HOST_EMSCRIPTEN + #define OLC_IMAGELOADER OLC_IMAGELOADER_LIB_PNG + #define OLC_IMAGELOADER_CLASS ImageLoader_LibPNG + #endif + #if OLC_HOST == OLC_HOST_ANDROID + #define OLC_IMAGELOADER OLC_IMAGELOADER_NDK_IMAGEDECODER + #define OLC_IMAGELOADER_CLASS ImageLoader_NDKImageDecoder + #endif +#endif #define OLC_MULTIWINDOW_NO 1 #define OLC_MULTIWINDOW_YES 2 @@ -5648,7 +5654,7 @@ namespace olc::imload #endif #endif -#if OLC_HOST == OLC_HOST_ANDROID +#if OLC_IMAGELOADER == OLC_IMAGELOADER_NDK_IMAGEDECODER #include #if !defined(PGE_IMAGELOADER_NDK_IMAGEDECODER_DECLARED) @@ -5685,10 +5691,33 @@ namespace olc::imload #endif #endif +#if OLC_IMAGELOADER == OLC_IMAGELOADER_STB_IMAGE +#if !defined(PGE_IMAGELOADER_LIB_PNG_DECLARED) +namespace olc::imload +{ + class ImageLoader_STB_Image : public ImageLoader + { + // Create an image resource based on an image file asset on disk + bool CreateImageFromFile(olc::Image& image, const std::string& sFileName) override; + + // Create an image resource based on an image file asset in memory + bool CreateImageFromMemory(olc::Image& image, const uint8_t* data, const size_t bytes) override; + // Create an image resource based on an image file asset in memory + bool CreateImageFromMemory(olc::Image& image, const std::vector& data) override; + // Store an image as a file asset on disk + bool WriteImageToFile(const olc::Image& image, const std::string& sFileName) override; + // Store an image as a file asset in memory + bool WriteImageToMemoryFile(olc::Image& image, const std::vector& data) override; + }; +} + +#define PGE_IMAGELOADER_LIB_PNG_DECLARED 1 +#endif +#endif #if defined(OLC_PGE3_APPLICATION) && !defined(PGE_HOST_IMPLEMENTED) @@ -15071,30 +15100,12 @@ namespace olc // DEVS!! Please don't merge these just yet // Initialise ImageLoader Interface -#if OLC_HOST == OLC_HOST_WINDOWS - imageloader = std::make_unique(); -#endif - -#if OLC_HOST == OLC_HOST_MACOS - imageloader = std::make_unique(); -#endif - -#if OLC_HOST == OLC_HOST_LINUX_X11 - imageloader = std::make_unique(); -#endif - -#if OLC_HOST == OLC_HOST_LINUX_WAYLAND - imageloader = std::make_unique(); -#endif - -#if OLC_HOST == OLC_HOST_EMSCRIPTEN - imageloader = std::make_unique(); -#endif - #if OLC_HOST == OLC_HOST_ANDROID - imageloader = std::make_unique( + imageloader = std::make_unique( olc::host::Host_Android::androidApp->activity->assetManager ); +#else + imageloader = std::make_unique(); #endif // Allow host to prepare itself @@ -16229,7 +16240,7 @@ namespace olc::imload } #endif -#if OLC_HOST == OLC_HOST_ANDROID +#if OLC_IMAGELOADER == OLC_IMAGELOADER_NDK_IMAGEDECODER #include #include #include @@ -16375,6 +16386,72 @@ namespace olc::imload } } #endif +#if OLC_IMAGELOADER == OLC_IMAGELOADER_STB_IMAGE +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include "stb_image_write.h" + +namespace olc::imload +{ + + // Create an image resource based on an image file asset on disk + bool ImageLoader_STB_Image::CreateImageFromFile(olc::Image& image, const std::string& sFileName) + { + std::cout << "ImageLoader: using stb image to load " << sFileName << ".\n"; + + // Open file + if(!std::filesystem::exists(sFileName)) + { + std::cout << "Error: failed to load image <" << sFileName << "> - file not found.\n"; + return false; + } + + stbi_uc* bytes = nullptr; + int width = 0, height = 0, cmp = 0; + bytes = stbi_load(sFileName.c_str(), &width, &height, &cmp, 4); + + if(!bytes) + { + std::cout << "Error: failed to load image <" << sFileName << "> - failed to allocate memory.\n"; + return false; + } + + image.Create({width, height}); + std::memcpy(reinterpret_cast(image.Data()), bytes, width * height * 4); + + delete[] bytes; + + return true; + } + + // Create an image resource based on an image file asset in memory + bool ImageLoader_STB_Image::CreateImageFromMemory(olc::Image& image, const uint8_t* data, const size_t bytes) + { + return false; + } + + // Create an image resource based on an image file asset in memory + bool ImageLoader_STB_Image::CreateImageFromMemory(olc::Image& image, const std::vector& data) + { + return false; + } + + // Store an image as a file asset on disk + bool ImageLoader_STB_Image::WriteImageToFile(const olc::Image& image, const std::string& sFileName) + { + return false; + } + + // Store an image as a file asset in memory + bool ImageLoader_STB_Image::WriteImageToMemoryFile(olc::Image& image, const std::vector& data) + { + return false; + } + +} +#endif + #define PGE_IMAGELOADER_IMPLEMENTED 1 #endif From bd64bde2d247a852e052eb4a1109f196e9b12316 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Sun, 1 Feb 2026 20:31:59 -0500 Subject: [PATCH 02/58] [cmake] add USE_STB flag to build examples using stb_image.h/stb_image-write.h for image loader. --- examples/CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 09920f29..65fbdf8c 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -7,6 +7,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) option(BUILD_WAYLAND "Build example programs with Wayland" OFF) +option(USE_STB "Use stb_image.h for image loading" OFF) ###################################################################### # Directories @@ -95,6 +96,10 @@ foreach(source ${SOURCES}) get_filename_component(EXE_NAME ${source} NAME_WE) add_executable(${EXE_NAME} ${source}) target_link_libraries(${EXE_NAME} PRIVATE olcPixelGameEngine3) + + if(USE_STB) + target_compile_definitions(${EXE_NAME} PRIVATE OLC_USE_STB_IMAGE=1) + endif() if(APPLE) if(CMAKE_SYSTEM_NAME STREQUAL "iOS") From 5327831e744a3f4a6b3af34bca4b8b9b9bc6482f Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Sun, 1 Feb 2026 20:56:54 -0500 Subject: [PATCH 03/58] [repo] clean up the readme and provide instructions to use stb_image --- README.md | 55 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 02c08010..643a3d9e 100644 --- a/README.md +++ b/README.md @@ -1,58 +1,59 @@ -# Linux build +# Building -## Cmake +## CMake -### Building X11 (Default) +Configure with: ```bash -cmake -B build -DBUILD_EXAMPLES=ON -cmake --build build +cmake -S . -B build ``` -Header file should now be located at `build/olcPixelGameEngine3.h`. - -Examples will be located at `build/examples` - -### Building for Wayland +**Note**: If using emscripten prepend the configuration command with ``emcmake`` +Build with: ```bash -cmake -B build -DBUILD_EXAMPLES=ON -DBUILD_WAYLAND=ON cmake --build build ``` - Header file should now be located at `build/olcPixelGameEngine3.h`. -Examples will be located at `build/examples` +### Available CMake Configuration Flags +* ``-DBUILD_EXAMPLES=ON`` - builds examples, will be located at `build/examples` +* ``-DBUILD_WAYLAND=ON`` - if examples are built, build them using wayland. Linux ONLY! +* ``-DUSE_STB=ON`` - build using stb_image for the image loader (See section on STB Image) -## Manually build a single example with GCC +## Manually on Linux ```bash cd examples +``` + +### Using GCC + +```bash g++ -ggdb olcPGE3_ImageQuads.cpp -o olcPGE3_ImageQuads -std=c++20 -lpng -lGL -lX11 -lpthread ``` -## Manually build a single example with Clang +### Using Clang ```bash -cd examples clang++ -ggdb olcPGE3_ImageQuads.cpp -o olcPGE3_ImageQuads -std=c++20 -lpng -lGL -lX11 -lpthread ``` -# Emscripten build +### Using Emscripten -## Cmake ```bash -emcmake cmake -B build -DBUILD_EXAMPLES=ON -cmake --build build +emcc olcPGE3_ImageQuads.cpp -o olcPGE3_ImageQuads.html -sASYNCIFY -sALLOW_MEMORY_GROWTH=1 -sSTACK_SIZE=1048576 -sEXPORTED_RUNTIME_METHODS=HEAPF32 -sMAX_WEBGL_VERSION=2 -sMIN_WEBGL_VERSION=2 -sUSE_LIBPNG=1 -sLLD_REPORT_UNDEFINED --preload-file assets@assets ``` -Header file should now be located at `build/olcPixelGameEngine3.h`. -Examples will be located at `build/examples` +# Using STB Image +## Get the headers +Aquire [stb_image.h](https://github.com/nothings/stb/blob/master/stb_image.h) and [stb_image_write.h](https://github.com/nothings/stb/blob/master/stb_image_write.h). Place them in the root directory of the repo. -## Manually build a single example +### CMake +Use the same configuration command you've chosen from above, and simply add ``-DUSE_STB=ON`` to the end of it. + +### GCC or Clang or EM++ + +Use the build commands you've chosen from above, and simply add ``-DOLC_USE_STB_IMAGE=1`` to the end of it. -```bash -cd examples -emcc olcPGE3_ImageQuads.cpp -o olcPGE3_ImageQuads.html -sASYNCIFY -sALLOW_MEMORY_GROWTH=1 -sSTACK_SIZE=1048576 -sEXPORTED_RUNTIME_METHODS=HEAPF32 -sMAX_WEBGL_VERSION=2 -sMIN_WEBGL_VERSION=2 -sUSE_LIBPNG=1 -sLLD_REPORT_UNDEFINED --preload-file assets@assets -``` From d706d5e15b926cf8d51762ce121dff4a364c7926 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Mon, 2 Feb 2026 02:26:13 -0500 Subject: [PATCH 04/58] [imageloader][stb] add CreateFromMemory implementations --- dev/src/imload_stb_image.cpp | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/dev/src/imload_stb_image.cpp b/dev/src/imload_stb_image.cpp index 029c2140..39ff2baa 100644 --- a/dev/src/imload_stb_image.cpp +++ b/dev/src/imload_stb_image.cpp @@ -42,15 +42,37 @@ namespace olc::imload // Create an image resource based on an image file asset in memory bool ImageLoader_STB_Image::CreateImageFromMemory(olc::Image& image, const uint8_t* data, const size_t bytes) { - return false; + stbi_uc* pixelData = nullptr; + int width = 0, height = 0, cmp = 0; + pixelData = stbi_load_from_memory(data, bytes, &width, &height, &cmp, 4); + if(!pixelData) + return false; + + image.Create({width, height}); + std::memcpy(reinterpret_cast(image.Data()), pixelData, width * height * 4); + + delete[] pixelData; + + return true; } // Create an image resource based on an image file asset in memory bool ImageLoader_STB_Image::CreateImageFromMemory(olc::Image& image, const std::vector& data) { - return false; + stbi_uc* pixelData = nullptr; + int width = 0, height = 0, cmp = 0; + pixelData = stbi_load_from_memory(data.data(), data.size(), &width, &height, &cmp, 4); + if(!pixelData) + return false; + + image.Create({width, height}); + std::memcpy(reinterpret_cast(image.Data()), pixelData, width * height * 4); + + delete[] pixelData; + + return true; } - + // Store an image as a file asset on disk bool ImageLoader_STB_Image::WriteImageToFile(const olc::Image& image, const std::string& sFileName) { From adbcc416c5e6e9b1ba2d4c71a01f084d6dc773ee Mon Sep 17 00:00:00 2001 From: John Galvin <96908304+Johnnyg63@users.noreply.github.com> Date: Mon, 2 Feb 2026 12:56:32 +0000 Subject: [PATCH 05/58] Update MacOS to support the new and improve host --- dev/src/api_macos.cpp | 16 ++- dev/src/api_macos.h | 1 + dev/src/api_macos_wrapper.hpp | 22 ++++ dev/src/host_apple_macos.cpp | 194 +++++++++++++++++++++++++--------- dev/src/host_apple_macos.h | 27 +++-- 5 files changed, 198 insertions(+), 62 deletions(-) diff --git a/dev/src/api_macos.cpp b/dev/src/api_macos.cpp index 47fb2d9d..515eb92f 100644 --- a/dev/src/api_macos.cpp +++ b/dev/src/api_macos.cpp @@ -29,6 +29,7 @@ static constexpr const char* kSharedApplicationSel = "sharedApplica static constexpr const char* kActivateIgnoringOtherAppsSel = "activateIgnoringOtherApps:"; static constexpr const char* kSetActivationPolicySel = "setActivationPolicy:"; static constexpr const char* kRunSel = "run"; +static constexpr const char* kTerminateSel = "terminate:"; // NSApplicationDelegate lifecycle methods static constexpr const char* kApplicationWillFinishLaunchingSel = "applicationWillFinishLaunching:"; @@ -126,7 +127,7 @@ static constexpr const char* kCurrentLocaleSel = "currentLocale static constexpr const char* kLocaleIdentifierSel = "localeIdentifier"; -// Default values and configuration settings +// Default values and configuration settings static constexpr const char* kWindowTitle = "C macOS OpenGL Framework"; static constexpr double kDefaultWindowWidth = 800.0; static constexpr double kDefaultWindowHeight = 600.0; @@ -142,7 +143,7 @@ static constexpr int kFlippedOffset = 1; static constexpr int kNoButton = -1; -// Objective-C method type encoding constants +// Objective-C method type encoding constants // Type encoding for methods returning BOOL with no parameters: "c@:" static constexpr const char* kBoolMethodTypeEncoding = "c@:"; @@ -161,7 +162,7 @@ namespace ObjectiveCSEL { static SEL allocSel, initSel, setDelegateSel, releaseSel, isKindOfClassSel = nullptr; // NSApplication lifecycle and management selectors - static SEL sharedApplicationSel, activateIgnoringOtherAppsSel, setActivationPolicySel,runSel = nullptr; + static SEL sharedApplicationSel, activateIgnoringOtherAppsSel, setActivationPolicySel,runSel, terminateSEL = nullptr; // NSApplicationDelegate lifecycle methods static SEL applicationWillFinishLaunchingSel, applicationDidFinishLaunchingSel, applicationWillTerminateSel, applicationDidBecomeActiveSel, applicationWillResignActiveSel = nullptr; @@ -209,6 +210,7 @@ namespace ObjectiveCSEL { activateIgnoringOtherAppsSel = sel_registerName(kActivateIgnoringOtherAppsSel); setActivationPolicySel = sel_registerName(kSetActivationPolicySel); runSel = sel_registerName(kRunSel); + terminateSEL = sel_registerName(kTerminateSel); // NSApplicationDelegate lifecycle methods applicationWillFinishLaunchingSel = sel_registerName(kApplicationWillFinishLaunchingSel); @@ -1212,6 +1214,12 @@ extern "C" { ((void(*)(id, SEL))objc_msgSend)(self->nsApp, ObjectiveCSEL::runSel); } + void application_stop(Application* self) { + if (self && self->nsApp) { + ((void(*)(id, SEL, id))objc_msgSend)(self->nsApp, ObjectiveCSEL::terminateSEL, self->nsApp); + } + } + // Destroy the application void application_destroy(Application* self) { if (self) { @@ -1221,7 +1229,7 @@ extern "C" { // Get system locale identifier const char* application_getSystemLocale(Application* self) { - (void)self; + (void)self; // Get NSLocale class Class NSLocaleClass = objc_getClass(kNSLocaleClass); diff --git a/dev/src/api_macos.h b/dev/src/api_macos.h index af4adc8c..f8b6c0e9 100644 --- a/dev/src/api_macos.h +++ b/dev/src/api_macos.h @@ -49,6 +49,7 @@ extern "C" { void application_initialize (struct Application* self); void application_activate (struct Application* self); void application_run (struct Application* self); + void application_stop (struct Application* self); void application_destroy (struct Application* self); const char* application_getSystemLocale (struct Application* self); diff --git a/dev/src/api_macos_wrapper.hpp b/dev/src/api_macos_wrapper.hpp index c2eceb67..ce561d64 100644 --- a/dev/src/api_macos_wrapper.hpp +++ b/dev/src/api_macos_wrapper.hpp @@ -95,6 +95,14 @@ namespace olc { void run() noexcept { if (app_) application_run(app_); } + + void terminate() noexcept { + if (app_) { + application_stop(app_); + app_ = nullptr; + } + } + // Add this method to get system locale std::string getSystemLocale() const { @@ -201,6 +209,13 @@ namespace olc { } } + void destoryWindow() { + if (window_) { + window_destroy(window_); + free(window_); + window_ = nullptr; + } + } // Get underlying C handle (Are you brave enough to use it?) struct ::Window* getCHandle() const noexcept { return window_; } @@ -485,6 +500,13 @@ namespace olc { } } + void destoryContext() noexcept { + if (renderer_) { + opengl_destroy(renderer_); + renderer_ = nullptr; + } + } + void attachToWindow(Window& window) noexcept { if (renderer_) { opengl_initialize(renderer_, window.getCHandle()); diff --git a/dev/src/host_apple_macos.cpp b/dev/src/host_apple_macos.cpp index 02b4410a..ed4d3464 100644 --- a/dev/src/host_apple_macos.cpp +++ b/dev/src/host_apple_macos.cpp @@ -1,5 +1,6 @@ #include "config.h" #include "host_apple_macos.h" +#include "core.h" #include #if OLC_HOST == OLC_HOST_MACOS @@ -126,7 +127,7 @@ namespace olc::host { mapKeys[33] = Key::OEM_4; // On US and UK keyboards this is the '[{' key mapKeys[42] = Key::OEM_5; // On US keyboard this is '\|' key. mapKeys[30] = Key::OEM_6; // On US and UK keyboards this is the ']}' key - mapKeys[39] = Key::OEM_7; // On US keyboard this is the single/double quote key. On UK, this is the single quote/@ symbol key (TODO: I think MAC is always @) + mapKeys[39] = Key::OEM_7; // On US keyboard this is the single/double quote key. On UK, this is the single quote/@ symbol key mapKeys[10] = Key::OEM_8; // Section sign § (varies by keyboard) mapKeys[24] = Key::EQUALS; // Equal sign = mapKeys[43] = Key::COMMA; // Comma , @@ -135,9 +136,69 @@ namespace olc::host { } - bool Host_Apple_MacOS::StartSystemEventLoop(bool bBlockIfPossible){ - (void)(bBlockIfPossible); // Remove unused variable warning +bool Host_Apple_MacOS::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen){ + pPGEwindow = pWindow; + pPGEwindow->SetWindowPosition(vWindowPos); + pPGEwindow->SetWindowSize(vWindowSize); + pPGEwindow->LinkToHost(this); + + frameBounds.x = 0.0; + frameBounds.y = 0.0; + frameBounds.width = static_cast(vWindowSize.x); + frameBounds.height = static_cast(vWindowSize.y); + + return true; +} + +bool Host_Apple_MacOS::CloseWindowFrame(olc::Window* pWindow){ + pWindow->olc_OnWindowClose(); + return true; +} + +bool Host_Apple_MacOS::UpdateWindowFrameTitle(olc::Window* pWindow){ + if (!pMacOSWindow) return false; + dispatch_async(dispatch_get_main_queue(), ^{ + pMacOSWindow->setTitle(pWindow->GetWindowTitle().c_str()); + }); + return true; +} + +std::vector Host_Apple_MacOS::GetHostWindowDescriptor(olc::Window* pWindow){ + + // Ensure OpenGL renderer is created + if(pMacOSOpenGLRenderer == nullptr) + CreateCGLContextObj(); + + return vMacOSWindowDescriptors; + +} + + +bool Host_Apple_MacOS::SyncWithDesktopComposite() +{ + /* + core.h SyncWithDesktopComposite is only called when vSync is enabled on each frame, + the method of enabling vSync varies between platforms, For macos we use a local var enableVSync, + set to false and toggle it on first call, so that vSync is only enabled once + */ + + if(!enableVSync) + { + pMacOSOpenGLRenderer->enableVsync(); + enableVSync = true; + } + + return enableVSync; +} + + bool Host_Apple_MacOS::OnApplicationStart(olc::PixelGameEngine* pPrimary){ + pPrimaryPGE = pPrimary; + return true; + } + + bool Host_Apple_MacOS::StartSystem(){ + // Create MacOS Application instance pMacApplication = std::make_unique(); @@ -166,76 +227,106 @@ namespace olc::host { pMacOSWindow->show(); pMacOSEventHandler->enable(); - // Start the main event loop (this will block) - pMacApplication->run(); + //--- Start up our engine threading system ----- + // Pre-context start hook + pPrimaryPGE->OnPreContextStart(); + + // Start the PGE context on the main thread + // Mark system as active + systemActive = true; - return true; - } + // Create system thread - handles gpu context + std::thread threadSystem([this]() + { + // Notify start of system thread + if (!this->OnSystemThreadStart()) + { + // PGE->OnContextStart() failed, or user aborted OnUserCreate() + return; + } - bool Host_Apple_MacOS::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen){ - pPGEwindow = pWindow; - pPGEwindow->SetWindowPosition(vWindowPos); - pPGEwindow->SetWindowSize(vWindowSize); // Temporary small size to avoid large window on creation - pPGEwindow->LinkToHost(this); + // Main system loop + while (systemActive) + { + // Perform primary window update + if (!this->OnSystemTick()) + { + StopSystem(); + } + } + + // Notify end of system thread + if (!this->OnSystemThreadEnd()) + { + // PGE->OnContextEnd() failed + return; + } + }); - frameBounds.x = 0.0; - frameBounds.y = 0.0; - frameBounds.width = static_cast(vWindowSize.x); - frameBounds.height = static_cast(vWindowSize.y); - return true; - } + // Start the main event loop (this will block) + pMacApplication->run(); + + // Once the application run loop ends, join the system thread + systemActive = false; + if(threadSystem.joinable()) + threadSystem.join(); + // Post-context end hook + return pPrimaryPGE->OnPostContextEnd(); - bool Host_Apple_MacOS::CloseWindowFrame(olc::Window* pWindow){ - if (!pMacOSWindow) return false; - if (!pWindow) return false; - pWindow->olc_OnWindowClose(); - return true; } - bool Host_Apple_MacOS::UpdateWindowFrameTitle(olc::Window* pWindow){ - if (!pMacOSWindow) return false; - dispatch_async(dispatch_get_main_queue(), ^{ - pMacOSWindow->setTitle(pWindow->GetWindowTitle().c_str()); + bool Host_Apple_MacOS::StopSystem() + { + dispatch_sync(dispatch_get_main_queue(), ^{ + // clean up and close application + if (pMacOSOpenGLRenderer) + { + pMacOSOpenGLRenderer->destoryContext(); + pMacOSOpenGLRenderer = nullptr; + } + if (pMacOSWindow) + { + pMacOSWindow->destoryWindow(); + pMacOSWindow = nullptr; + } + if (pMacApplication) + { + pMacApplication->terminate(); + } + }); return true; } - std::vector Host_Apple_MacOS::GetHostWindowDescriptor(olc::Window* pWindow){ - // While the PGE is running, if there are pending main thread tasks, process them, this causes PGE to wait + bool Host_Apple_MacOS::OnSystemThreadStart() + { + // Hold back threading until application is fully initialized bSkipFrame = ExecutePendingMainThreadTasks(); - - // Ensure OpenGL renderer is created - if(pMacOSOpenGLRenderer == nullptr) - CreateCGLContextObj(); + return pPrimaryPGE->OnContextStart(); + } - return vMacOSWindowDescriptors; - + bool Host_Apple_MacOS::OnSystemTick() + { + // Execute any pending main thread tasks + bSkipFrame = ExecutePendingMainThreadTasks(); + return pPrimaryPGE->OnContextTick(); } - bool Host_Apple_MacOS::ConnectHostResourceToRenderer() + bool Host_Apple_MacOS::OnSystemThreadEnd() { - return false; + return pPrimaryPGE->OnContextEnd(); } - bool Host_Apple_MacOS::SyncWithDesktopComposite() + bool Host_Apple_MacOS::OnApplicationEnd() { - /* - core.h SyncWithDesktopComposite is only called when vSync is enabled on each frame, - the method of enabling vSync varies between platforms, For macos we use a local var enableVSync, - set to false and toggle it on first call, so that vSync is only enabled once - */ - - if(!enableVSync) - { - pMacOSOpenGLRenderer->enableVsync(); - enableVSync = true; - } - - return enableVSync; + return true; } + +//-- OS Window Event Handling ----- + olc::KeyboardLayout Host_Apple_MacOS::GetKeyboardLayout() const { // Get system locale from MacOS Application @@ -432,7 +523,6 @@ namespace olc::host { }); pMacOSWindow->setWindowWillCloseCallback([&]() { - // TODO: Johnngy63 - Implement any pre-close logic if needed pPGEwindow->olc_OnWindowClose(); pPGEwindow->olc_ShouldRemove(); }); diff --git a/dev/src/host_apple_macos.h b/dev/src/host_apple_macos.h index 70578d93..7aa4aa02 100644 --- a/dev/src/host_apple_macos.h +++ b/dev/src/host_apple_macos.h @@ -48,19 +48,34 @@ namespace olc HostError GetLastError() const { return lastError; } public: - virtual bool StartSystemEventLoop(bool bBlockIfPossible = false) override; virtual bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; virtual bool CloseWindowFrame(olc::Window* pWindow) override; virtual bool UpdateWindowFrameTitle(olc::Window* pWindow) override; virtual std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; - - virtual bool ConnectHostResourceToRenderer() override; // Wait for entire host desktop refresh (for smooooth vsync), virtual bool SyncWithDesktopComposite() override; - virtual olc::KeyboardLayout GetKeyboardLayout() const override; + public: // OS Specific Environment Information + virtual olc::KeyboardLayout GetKeyboardLayout() const override; + + public: // Platform Specific OS<->PGE Linkage + // Called at very start of application + virtual bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; + // Called to start the host - this may mean different things on different hosts + // It MUST block until system is requested to exit + virtual bool StartSystem() override; + // Called to stop the host, and shutdown all resources + virtual bool StopSystem() override; + // Called at start of system event loop + virtual bool OnSystemThreadStart() override; + // Called to perform primary window update + virtual bool OnSystemTick() override; + // Called at end of system event loop + virtual bool OnSystemThreadEnd() override; + // Called at very end of application + virtual bool OnApplicationEnd() override; protected: HostError lastError = HostError::None; @@ -108,6 +123,8 @@ namespace olc mutable std::mutex pgeThreadPendingTasksMutex; // Mutex for PGE thread pending tasks std::condition_variable pgeThreadResetCondition; // Condition variable for PGE thread reset std::atomic isPGEThreadResetting{true}; // Atomic flag for resetting PGE thread + + std::atomic systemActive = false; // Atomic flag for system active state struct sFrameBounds { @@ -122,10 +139,8 @@ namespace olc void MacEventsHandler(); void MacOpenGLContextEventsHandler(); - // When modifier flag changes the keycode it will return true, else false bool ModifiersFlagsHandler(const olc::apis::macos::KeyEvent& data, bool pressed); - bool bNumLockActive = true; // Num Lock state, we assume it's active at start }; From 8a5da41ad28cc16313135a55a874f8d065b29dd7 Mon Sep 17 00:00:00 2001 From: John Galvin <96908304+Johnnyg63@users.noreply.github.com> Date: Mon, 2 Feb 2026 14:07:51 +0000 Subject: [PATCH 06/58] PGE3 SH File --- olcPixelGameEngine3.h | 259 ++++++++++++++++++++++++++++++++---------- 1 file changed, 197 insertions(+), 62 deletions(-) diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 4e5930ee..37eb99bd 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -3466,6 +3466,7 @@ extern "C" { void application_initialize (struct Application* self); void application_activate (struct Application* self); void application_run (struct Application* self); + void application_stop (struct Application* self); void application_destroy (struct Application* self); const char* application_getSystemLocale (struct Application* self); @@ -3641,6 +3642,14 @@ namespace olc { void run() noexcept { if (app_) application_run(app_); } + + void terminate() noexcept { + if (app_) { + application_stop(app_); + app_ = nullptr; + } + } + // Add this method to get system locale std::string getSystemLocale() const { @@ -3747,6 +3756,13 @@ namespace olc { } } + void destoryWindow() { + if (window_) { + window_destroy(window_); + free(window_); + window_ = nullptr; + } + } // Get underlying C handle (Are you brave enough to use it?) struct ::Window* getCHandle() const noexcept { return window_; } @@ -4031,6 +4047,13 @@ namespace olc { } } + void destoryContext() noexcept { + if (renderer_) { + opengl_destroy(renderer_); + renderer_ = nullptr; + } + } + void attachToWindow(Window& window) noexcept { if (renderer_) { opengl_initialize(renderer_, window.getCHandle()); @@ -4448,19 +4471,34 @@ namespace olc HostError GetLastError() const { return lastError; } public: - virtual bool StartSystemEventLoop(bool bBlockIfPossible = false) override; virtual bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; virtual bool CloseWindowFrame(olc::Window* pWindow) override; virtual bool UpdateWindowFrameTitle(olc::Window* pWindow) override; virtual std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; - - virtual bool ConnectHostResourceToRenderer() override; // Wait for entire host desktop refresh (for smooooth vsync), virtual bool SyncWithDesktopComposite() override; - virtual olc::KeyboardLayout GetKeyboardLayout() const override; + public: // OS Specific Environment Information + virtual olc::KeyboardLayout GetKeyboardLayout() const override; + + public: // Platform Specific OS<->PGE Linkage + // Called at very start of application + virtual bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; + // Called to start the host - this may mean different things on different hosts + // It MUST block until system is requested to exit + virtual bool StartSystem() override; + // Called to stop the host, and shutdown all resources + virtual bool StopSystem() override; + // Called at start of system event loop + virtual bool OnSystemThreadStart() override; + // Called to perform primary window update + virtual bool OnSystemTick() override; + // Called at end of system event loop + virtual bool OnSystemThreadEnd() override; + // Called at very end of application + virtual bool OnApplicationEnd() override; protected: HostError lastError = HostError::None; @@ -4508,6 +4546,8 @@ namespace olc mutable std::mutex pgeThreadPendingTasksMutex; // Mutex for PGE thread pending tasks std::condition_variable pgeThreadResetCondition; // Condition variable for PGE thread reset std::atomic isPGEThreadResetting{true}; // Atomic flag for resetting PGE thread + + std::atomic systemActive = false; // Atomic flag for system active state struct sFrameBounds { @@ -4522,10 +4562,8 @@ namespace olc void MacEventsHandler(); void MacOpenGLContextEventsHandler(); - // When modifier flag changes the keycode it will return true, else false bool ModifiersFlagsHandler(const olc::apis::macos::KeyEvent& data, bool pressed); - bool bNumLockActive = true; // Num Lock state, we assume it's active at start }; @@ -6367,7 +6405,7 @@ namespace olc::host { mapKeys[33] = Key::OEM_4; // On US and UK keyboards this is the '[{' key mapKeys[42] = Key::OEM_5; // On US keyboard this is '\|' key. mapKeys[30] = Key::OEM_6; // On US and UK keyboards this is the ']}' key - mapKeys[39] = Key::OEM_7; // On US keyboard this is the single/double quote key. On UK, this is the single quote/@ symbol key (TODO: I think MAC is always @) + mapKeys[39] = Key::OEM_7; // On US keyboard this is the single/double quote key. On UK, this is the single quote/@ symbol key mapKeys[10] = Key::OEM_8; // Section sign § (varies by keyboard) mapKeys[24] = Key::EQUALS; // Equal sign = mapKeys[43] = Key::COMMA; // Comma , @@ -6376,9 +6414,69 @@ namespace olc::host { } - bool Host_Apple_MacOS::StartSystemEventLoop(bool bBlockIfPossible){ - (void)(bBlockIfPossible); // Remove unused variable warning +bool Host_Apple_MacOS::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen){ + pPGEwindow = pWindow; + pPGEwindow->SetWindowPosition(vWindowPos); + pPGEwindow->SetWindowSize(vWindowSize); + pPGEwindow->LinkToHost(this); + + frameBounds.x = 0.0; + frameBounds.y = 0.0; + frameBounds.width = static_cast(vWindowSize.x); + frameBounds.height = static_cast(vWindowSize.y); + + return true; +} + +bool Host_Apple_MacOS::CloseWindowFrame(olc::Window* pWindow){ + pWindow->olc_OnWindowClose(); + return true; +} + +bool Host_Apple_MacOS::UpdateWindowFrameTitle(olc::Window* pWindow){ + if (!pMacOSWindow) return false; + dispatch_async(dispatch_get_main_queue(), ^{ + pMacOSWindow->setTitle(pWindow->GetWindowTitle().c_str()); + }); + return true; +} + +std::vector Host_Apple_MacOS::GetHostWindowDescriptor(olc::Window* pWindow){ + + // Ensure OpenGL renderer is created + if(pMacOSOpenGLRenderer == nullptr) + CreateCGLContextObj(); + + return vMacOSWindowDescriptors; + +} + + +bool Host_Apple_MacOS::SyncWithDesktopComposite() +{ + /* + core.h SyncWithDesktopComposite is only called when vSync is enabled on each frame, + the method of enabling vSync varies between platforms, For macos we use a local var enableVSync, + set to false and toggle it on first call, so that vSync is only enabled once + */ + + if(!enableVSync) + { + pMacOSOpenGLRenderer->enableVsync(); + enableVSync = true; + } + + return enableVSync; +} + + bool Host_Apple_MacOS::OnApplicationStart(olc::PixelGameEngine* pPrimary){ + pPrimaryPGE = pPrimary; + return true; + } + + bool Host_Apple_MacOS::StartSystem(){ + // Create MacOS Application instance pMacApplication = std::make_unique(); @@ -6407,76 +6505,106 @@ namespace olc::host { pMacOSWindow->show(); pMacOSEventHandler->enable(); - // Start the main event loop (this will block) - pMacApplication->run(); + //--- Start up our engine threading system ----- + // Pre-context start hook + pPrimaryPGE->OnPreContextStart(); + + // Start the PGE context on the main thread + // Mark system as active + systemActive = true; - return true; - } + // Create system thread - handles gpu context + std::thread threadSystem([this]() + { + // Notify start of system thread + if (!this->OnSystemThreadStart()) + { + // PGE->OnContextStart() failed, or user aborted OnUserCreate() + return; + } - bool Host_Apple_MacOS::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen){ - pPGEwindow = pWindow; - pPGEwindow->SetWindowPosition(vWindowPos); - pPGEwindow->SetWindowSize(vWindowSize); // Temporary small size to avoid large window on creation - pPGEwindow->LinkToHost(this); + // Main system loop + while (systemActive) + { + // Perform primary window update + if (!this->OnSystemTick()) + { + StopSystem(); + } + } + + // Notify end of system thread + if (!this->OnSystemThreadEnd()) + { + // PGE->OnContextEnd() failed + return; + } + }); - frameBounds.x = 0.0; - frameBounds.y = 0.0; - frameBounds.width = static_cast(vWindowSize.x); - frameBounds.height = static_cast(vWindowSize.y); - return true; - } + // Start the main event loop (this will block) + pMacApplication->run(); + + // Once the application run loop ends, join the system thread + systemActive = false; + if(threadSystem.joinable()) + threadSystem.join(); + // Post-context end hook + return pPrimaryPGE->OnPostContextEnd(); - bool Host_Apple_MacOS::CloseWindowFrame(olc::Window* pWindow){ - if (!pMacOSWindow) return false; - if (!pWindow) return false; - pWindow->olc_OnWindowClose(); - return true; } - bool Host_Apple_MacOS::UpdateWindowFrameTitle(olc::Window* pWindow){ - if (!pMacOSWindow) return false; - dispatch_async(dispatch_get_main_queue(), ^{ - pMacOSWindow->setTitle(pWindow->GetWindowTitle().c_str()); + bool Host_Apple_MacOS::StopSystem() + { + dispatch_sync(dispatch_get_main_queue(), ^{ + // clean up and close application + if (pMacOSOpenGLRenderer) + { + pMacOSOpenGLRenderer->destoryContext(); + pMacOSOpenGLRenderer = nullptr; + } + if (pMacOSWindow) + { + pMacOSWindow->destoryWindow(); + pMacOSWindow = nullptr; + } + if (pMacApplication) + { + pMacApplication->terminate(); + } + }); return true; } - std::vector Host_Apple_MacOS::GetHostWindowDescriptor(olc::Window* pWindow){ - // While the PGE is running, if there are pending main thread tasks, process them, this causes PGE to wait + bool Host_Apple_MacOS::OnSystemThreadStart() + { + // Hold back threading until application is fully initialized bSkipFrame = ExecutePendingMainThreadTasks(); - - // Ensure OpenGL renderer is created - if(pMacOSOpenGLRenderer == nullptr) - CreateCGLContextObj(); + return pPrimaryPGE->OnContextStart(); + } - return vMacOSWindowDescriptors; - + bool Host_Apple_MacOS::OnSystemTick() + { + // Execute any pending main thread tasks + bSkipFrame = ExecutePendingMainThreadTasks(); + return pPrimaryPGE->OnContextTick(); } - bool Host_Apple_MacOS::ConnectHostResourceToRenderer() + bool Host_Apple_MacOS::OnSystemThreadEnd() { - return false; + return pPrimaryPGE->OnContextEnd(); } - bool Host_Apple_MacOS::SyncWithDesktopComposite() + bool Host_Apple_MacOS::OnApplicationEnd() { - /* - core.h SyncWithDesktopComposite is only called when vSync is enabled on each frame, - the method of enabling vSync varies between platforms, For macos we use a local var enableVSync, - set to false and toggle it on first call, so that vSync is only enabled once - */ - - if(!enableVSync) - { - pMacOSOpenGLRenderer->enableVsync(); - enableVSync = true; - } - - return enableVSync; + return true; } + +//-- OS Window Event Handling ----- + olc::KeyboardLayout Host_Apple_MacOS::GetKeyboardLayout() const { // Get system locale from MacOS Application @@ -6673,7 +6801,6 @@ namespace olc::host { }); pMacOSWindow->setWindowWillCloseCallback([&]() { - // TODO: Johnngy63 - Implement any pre-close logic if needed pPGEwindow->olc_OnWindowClose(); pPGEwindow->olc_ShouldRemove(); }); @@ -6841,6 +6968,7 @@ static constexpr const char* kSharedApplicationSel = "sharedApplica static constexpr const char* kActivateIgnoringOtherAppsSel = "activateIgnoringOtherApps:"; static constexpr const char* kSetActivationPolicySel = "setActivationPolicy:"; static constexpr const char* kRunSel = "run"; +static constexpr const char* kTerminateSel = "terminate:"; // NSApplicationDelegate lifecycle methods static constexpr const char* kApplicationWillFinishLaunchingSel = "applicationWillFinishLaunching:"; @@ -6938,7 +7066,7 @@ static constexpr const char* kCurrentLocaleSel = "currentLocale static constexpr const char* kLocaleIdentifierSel = "localeIdentifier"; -// Default values and configuration settings +// Default values and configuration settings static constexpr const char* kWindowTitle = "C macOS OpenGL Framework"; static constexpr double kDefaultWindowWidth = 800.0; static constexpr double kDefaultWindowHeight = 600.0; @@ -6954,7 +7082,7 @@ static constexpr int kFlippedOffset = 1; static constexpr int kNoButton = -1; -// Objective-C method type encoding constants +// Objective-C method type encoding constants // Type encoding for methods returning BOOL with no parameters: "c@:" static constexpr const char* kBoolMethodTypeEncoding = "c@:"; @@ -6973,7 +7101,7 @@ namespace ObjectiveCSEL { static SEL allocSel, initSel, setDelegateSel, releaseSel, isKindOfClassSel = nullptr; // NSApplication lifecycle and management selectors - static SEL sharedApplicationSel, activateIgnoringOtherAppsSel, setActivationPolicySel,runSel = nullptr; + static SEL sharedApplicationSel, activateIgnoringOtherAppsSel, setActivationPolicySel,runSel, terminateSEL = nullptr; // NSApplicationDelegate lifecycle methods static SEL applicationWillFinishLaunchingSel, applicationDidFinishLaunchingSel, applicationWillTerminateSel, applicationDidBecomeActiveSel, applicationWillResignActiveSel = nullptr; @@ -7021,6 +7149,7 @@ namespace ObjectiveCSEL { activateIgnoringOtherAppsSel = sel_registerName(kActivateIgnoringOtherAppsSel); setActivationPolicySel = sel_registerName(kSetActivationPolicySel); runSel = sel_registerName(kRunSel); + terminateSEL = sel_registerName(kTerminateSel); // NSApplicationDelegate lifecycle methods applicationWillFinishLaunchingSel = sel_registerName(kApplicationWillFinishLaunchingSel); @@ -8024,6 +8153,12 @@ extern "C" { ((void(*)(id, SEL))objc_msgSend)(self->nsApp, ObjectiveCSEL::runSel); } + void application_stop(Application* self) { + if (self && self->nsApp) { + ((void(*)(id, SEL, id))objc_msgSend)(self->nsApp, ObjectiveCSEL::terminateSEL, self->nsApp); + } + } + // Destroy the application void application_destroy(Application* self) { if (self) { @@ -8033,7 +8168,7 @@ extern "C" { // Get system locale identifier const char* application_getSystemLocale(Application* self) { - (void)self; + (void)self; // Get NSLocale class Class NSLocaleClass = objc_getClass(kNSLocaleClass); From 18699f9a33cfda5dae9a9d10938d86caad5edeed Mon Sep 17 00:00:00 2001 From: DCubix Date: Mon, 2 Feb 2026 20:36:42 -0400 Subject: [PATCH 07/58] update Android host --- dev/src/core.cpp | 4 +- dev/src/host_android.cpp | 141 +++++++++++++++++++++------ dev/src/host_android.h | 43 ++++++-- dev/src/imload_android.cpp | 4 +- dev/src/imload_android.h | 6 -- olcPixelGameEngine3.h | 194 ++++++++++++++++++++++++++++--------- 6 files changed, 298 insertions(+), 94 deletions(-) diff --git a/dev/src/core.cpp b/dev/src/core.cpp index 7cec77a0..def6da8c 100644 --- a/dev/src/core.cpp +++ b/dev/src/core.cpp @@ -326,9 +326,7 @@ namespace olc #endif #if OLC_HOST == OLC_HOST_ANDROID - imageloader = std::make_unique( - olc::host::Host_Android::androidApp->activity->assetManager - ); + imageloader = std::make_unique(); #endif // Allow host to prepare itself diff --git a/dev/src/host_android.cpp b/dev/src/host_android.cpp index b5f38f48..7482f9a1 100644 --- a/dev/src/host_android.cpp +++ b/dev/src/host_android.cpp @@ -146,56 +146,126 @@ namespace olc::host return host->OnInputEvent(app, event); } - bool Host_Android::StartSystemEventLoop(bool bBlockIfPossible) + bool Host_Android::StartSystem() { - int events; - struct android_poll_source* source = nullptr; - - if (ALooper_pollOnce( - bBlockIfPossible || !initialized ? -1 : 0, - nullptr, - &events, - (void**)&source - ) >= 0) { - if (source) source->process(androidApp, source); + pPrimaryPGE->OnPreContextStart(); + + PollEvents( + [this]() { + return !initialized.load(); + }, + true + ); + + if (androidApp->destroyRequested) + { + return false; + } + + systemActive = true; + + std::thread threadSys([this]() { + if (!OnSystemThreadStart()) + { + StopSystem(); + return; + } + + while (systemActive.load()) + { + if (!OnSystemTick()) + { + StopSystem(); + } + } + + if (!this->OnSystemThreadEnd()) + { + return; + } + }); + + PollEvents( + [this]() { + return systemActive.load() && !androidApp->destroyRequested; + }, + true + ); + + systemActive = false; + if (threadSys.joinable()) + { + threadSys.join(); } - if (!androidApp || !initialized) return true; + return pPrimaryPGE->OnPostContextEnd(); + } + + bool Host_Android::StopSystem() + { + systemActive = false; + return true; + } + + bool Host_Android::OnSystemThreadStart() + { + return pPrimaryPGE->OnContextStart(); + } + + bool Host_Android::OnSystemTick() + { + return pPrimaryPGE->OnContextTick();; + } + + bool Host_Android::OnSystemThreadEnd() + { + return pPrimaryPGE->OnContextEnd(); + } - if (androidApp->destroyRequested != 0) return false; + bool Host_Android::OnApplicationStart(olc::PixelGameEngine* pPrimary) + { + pPrimaryPGE = pPrimary; + return true; + } + bool Host_Android::OnApplicationEnd() + { return true; } void Host_Android::OnAppCmd(struct android_app *app, int32_t cmd) { auto host = reinterpret_cast(app->userData); + if (!host->pgeWindow) return; + switch (cmd) { case APP_CMD_WINDOW_RESIZED: host->pgeWindow->olc_OnWindowSize({ ANativeWindow_getWidth(app->window), ANativeWindow_getHeight(app->window) }); - __android_log_print(ANDROID_LOG_DEBUG, "PGE ANDROID", - "APP_CMD_WINDOW_RESIZED received: %dx%d", - ANativeWindow_getWidth(app->window), - ANativeWindow_getHeight(app->window)); + LOGD("APP_CMD_WINDOW_RESIZED: %dx%d", + ANativeWindow_getWidth(app->window), + ANativeWindow_getHeight(app->window)); break; case APP_CMD_INIT_WINDOW: if (app->window) { host->initialized = true; - __android_log_print(ANDROID_LOG_DEBUG, "PGE ANDROID", - "APP_CMD_INIT_WINDOW received with Window"); + LOGD("APP_CMD_INIT_WINDOW received with Window"); } break; case APP_CMD_TERM_WINDOW: { host->pgeWindow->olc_OnWindowClose(); + host->initialized = false; + LOGD("APP_CMD_TERM_WINDOW received"); } break; case APP_CMD_GAINED_FOCUS: { host->pgeWindow->olc_OnMouseFocus(true); + LOGD("APP_CMD_GAINED_FOCUS received"); } break; case APP_CMD_LOST_FOCUS: { host->pgeWindow->olc_OnMouseFocus(false); + LOGD("APP_CMD_LOST_FOCUS received"); } break; default: break; } @@ -204,6 +274,8 @@ namespace olc::host int32_t Host_Android::OnInputEvent(AndroidApp *app, AInputEvent *event) { auto host = reinterpret_cast(app->userData); + if (!host->pgeWindow) return 0; + auto type = AInputEvent_getType(event); if (type == AINPUT_EVENT_TYPE_MOTION) { @@ -309,11 +381,6 @@ namespace olc::host return { reinterpret_cast(androidApp->window) }; } - bool Host_Android::ConnectHostResourceToRenderer() - { - return true; - } - bool Host_Android::SyncWithDesktopComposite() { return true; @@ -528,15 +595,35 @@ namespace olc::host return content; } + + void Host_Android::PollEvents(const std::function& funcContinue, bool bBlocking) + { + while (funcContinue()) { + int events; + struct android_poll_source* source; + + const int timeOut = bBlocking ? -1 : 0; + const int ident = ALooper_pollOnce(timeOut, nullptr, &events, (void**)&source); + + if (ident >= 0) { + if (source) { + source->process(androidApp, source); + } + } else if (!bBlocking) { + break; + } + } + } + } void android_main(struct android_app* app) { - char arg0[] = "olcPixelGameEngine 3.0"; + char arg0[] = "olc::PixelGameEngine 3.0"; char* argv[] = { arg0, nullptr }; - olc::host::JNI::Init(app); olc::host::Host_Android::androidApp = app; + olc::host::JNI::Init(app); (void)main(1, argv); @@ -546,7 +633,7 @@ void android_main(struct android_app* app) int events; struct android_poll_source* source; - while (ALooper_pollOnce(0, nullptr, &events, (void**)&source) > ALOOPER_POLL_TIMEOUT) { + if (ALooper_pollOnce(0, nullptr, &events, (void**)&source) > ALOOPER_POLL_TIMEOUT) { if (source) { source->process(app, source); } diff --git a/dev/src/host_android.h b/dev/src/host_android.h index 62bdcfed..81eb67b1 100644 --- a/dev/src/host_android.h +++ b/dev/src/host_android.h @@ -8,6 +8,8 @@ #include #include #include +#include +#include //! END STDHEADER //! START DECLARATION @@ -15,6 +17,12 @@ #include #include +#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "olcPGE3", __VA_ARGS__)) +#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "olcPGE3", __VA_ARGS__)) +#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "olcPGE3", __VA_ARGS__)) +#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, "olcPGE3", __VA_ARGS__)) +#define LOGF(...) ((void)__android_log_print(ANDROID_LOG_FATAL, "olcPGE3", __VA_ARGS__)) + // We allow users to create a normal main function for android apps extern int main(int argc, char** argv); @@ -25,20 +33,30 @@ namespace olc::host { public: Host_Android(); - bool StartSystemEventLoop(bool bBlockIfPossible) override; + bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; bool CloseWindowFrame(olc::Window* pWindow) override; bool UpdateWindowFrameTitle(olc::Window* pWindow) override; - - std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; - - bool ConnectHostResourceToRenderer() override; - - // Wait for entire host desktop refresh (for smooooth vsync) bool SyncWithDesktopComposite() override; - + std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; olc::KeyboardLayout GetKeyboardLayout() const override; + // Called at very start of application + bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; + // Called to start the host - this may mean different things on different hosts + // It MUST block until system is requested to exit + bool StartSystem() override; + // Called to stop the host, and shutdown all resources + bool StopSystem() override; + // Called at start of system event loop + bool OnSystemThreadStart() override; + // Called to perform primary window update + bool OnSystemTick() override; + // Called at end of system event loop + bool OnSystemThreadEnd() override; + // Called at very end of application + bool OnApplicationEnd() override; + void OnAppCmd(AndroidApp* app, int32_t cmd); int32_t OnInputEvent(AndroidApp* app, AInputEvent* event); @@ -46,15 +64,20 @@ namespace olc::host void ShowKeyboard(bool bShow); - // TODO: file loading support (temporaru) + // TODO: file loading support (temporary) std::vector OpenFile(const std::string& sFileName); std::string OpenTextFile(const std::string& sFileName); static AndroidApp* androidApp; protected: olc::Window* pgeWindow = nullptr; - std::atomic initialized{false}; + std::atomic initialized{false}, systemActive{false}; bool shiftOn = false; + + void PollEvents( + const std::function& funcContinue, + bool bBlocking = false + ); }; class JNI diff --git a/dev/src/imload_android.cpp b/dev/src/imload_android.cpp index 155ef8dd..3226115c 100644 --- a/dev/src/imload_android.cpp +++ b/dev/src/imload_android.cpp @@ -1,5 +1,7 @@ #include "imload_android.h" +#include "host_android.h" + //! START IMPLEMENTATION #include #include @@ -10,7 +12,7 @@ namespace olc::imload bool ImageLoader_NDKImageDecoder::CreateImageFromFile(olc::Image& image, const std::string& sFileName) { AAsset* asset = AAssetManager_open( - assetManager, + olc::host::Host_Android::androidApp->activity->assetManager, sFileName.c_str(), AASSET_MODE_BUFFER ); diff --git a/dev/src/imload_android.h b/dev/src/imload_android.h index 43b0919b..c9eb0f8a 100644 --- a/dev/src/imload_android.h +++ b/dev/src/imload_android.h @@ -18,9 +18,6 @@ namespace olc::imload class ImageLoader_NDKImageDecoder : public ImageLoader { public: - ImageLoader_NDKImageDecoder() = default; - ImageLoader_NDKImageDecoder(AAssetManager* assetManager) : assetManager(assetManager) {} - // Create an image resource based on an image file asset on disk bool CreateImageFromFile(olc::Image& image, const std::string& sFileName) override; @@ -35,9 +32,6 @@ namespace olc::imload // Store an image as a file asset in memory bool WriteImageToMemoryFile(olc::Image& image, const std::vector& data) override; - - protected: - AAssetManager* assetManager = nullptr; }; } #define PGE_IMAGELOADER_NDK_IMAGEDECODER_DECLARED 1 diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 4e5930ee..b109cbdd 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -4875,6 +4875,12 @@ namespace olc::host #include #include +#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "olcPGE3", __VA_ARGS__)) +#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, "olcPGE3", __VA_ARGS__)) +#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "olcPGE3", __VA_ARGS__)) +#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, "olcPGE3", __VA_ARGS__)) +#define LOGF(...) ((void)__android_log_print(ANDROID_LOG_FATAL, "olcPGE3", __VA_ARGS__)) + // We allow users to create a normal main function for android apps extern int main(int argc, char** argv); @@ -4885,20 +4891,30 @@ namespace olc::host { public: Host_Android(); - bool StartSystemEventLoop(bool bBlockIfPossible) override; + bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; bool CloseWindowFrame(olc::Window* pWindow) override; bool UpdateWindowFrameTitle(olc::Window* pWindow) override; - - std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; - - bool ConnectHostResourceToRenderer() override; - - // Wait for entire host desktop refresh (for smooooth vsync) bool SyncWithDesktopComposite() override; - + std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; olc::KeyboardLayout GetKeyboardLayout() const override; + // Called at very start of application + bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; + // Called to start the host - this may mean different things on different hosts + // It MUST block until system is requested to exit + bool StartSystem() override; + // Called to stop the host, and shutdown all resources + bool StopSystem() override; + // Called at start of system event loop + bool OnSystemThreadStart() override; + // Called to perform primary window update + bool OnSystemTick() override; + // Called at end of system event loop + bool OnSystemThreadEnd() override; + // Called at very end of application + bool OnApplicationEnd() override; + void OnAppCmd(AndroidApp* app, int32_t cmd); int32_t OnInputEvent(AndroidApp* app, AInputEvent* event); @@ -4906,15 +4922,20 @@ namespace olc::host void ShowKeyboard(bool bShow); - // TODO: file loading support (temporaru) + // TODO: file loading support (temporary) std::vector OpenFile(const std::string& sFileName); std::string OpenTextFile(const std::string& sFileName); static AndroidApp* androidApp; protected: olc::Window* pgeWindow = nullptr; - std::atomic initialized{false}; + std::atomic initialized{false}, systemActive{false}; bool shiftOn = false; + + void PollEvents( + const std::function& funcContinue, + bool bBlocking = false + ); }; class JNI @@ -5659,9 +5680,6 @@ namespace olc::imload class ImageLoader_NDKImageDecoder : public ImageLoader { public: - ImageLoader_NDKImageDecoder() = default; - ImageLoader_NDKImageDecoder(AAssetManager* assetManager) : assetManager(assetManager) {} - // Create an image resource based on an image file asset on disk bool CreateImageFromFile(olc::Image& image, const std::string& sFileName) override; @@ -5676,9 +5694,6 @@ namespace olc::imload // Store an image as a file asset in memory bool WriteImageToMemoryFile(olc::Image& image, const std::vector& data) override; - - protected: - AAssetManager* assetManager = nullptr; }; } #define PGE_IMAGELOADER_NDK_IMAGEDECODER_DECLARED 1 @@ -10750,56 +10765,126 @@ namespace olc::host return host->OnInputEvent(app, event); } - bool Host_Android::StartSystemEventLoop(bool bBlockIfPossible) + bool Host_Android::StartSystem() { - int events; - struct android_poll_source* source = nullptr; - - if (ALooper_pollOnce( - bBlockIfPossible || !initialized ? -1 : 0, - nullptr, - &events, - (void**)&source - ) >= 0) { - if (source) source->process(androidApp, source); + pPrimaryPGE->OnPreContextStart(); + + PollEvents( + [this]() { + return !initialized.load(); + }, + true + ); + + if (androidApp->destroyRequested) + { + return false; + } + + systemActive = true; + + std::thread threadSys([this]() { + if (!OnSystemThreadStart()) + { + StopSystem(); + return; + } + + while (systemActive.load()) + { + if (!OnSystemTick()) + { + StopSystem(); + } + } + + if (!this->OnSystemThreadEnd()) + { + return; + } + }); + + PollEvents( + [this]() { + return systemActive.load() && !androidApp->destroyRequested; + }, + true + ); + + systemActive = false; + if (threadSys.joinable()) + { + threadSys.join(); } - if (!androidApp || !initialized) return true; + return pPrimaryPGE->OnPostContextEnd(); + } + + bool Host_Android::StopSystem() + { + systemActive = false; + return true; + } + + bool Host_Android::OnSystemThreadStart() + { + return pPrimaryPGE->OnContextStart(); + } + + bool Host_Android::OnSystemTick() + { + return pPrimaryPGE->OnContextTick();; + } + + bool Host_Android::OnSystemThreadEnd() + { + return pPrimaryPGE->OnContextEnd(); + } - if (androidApp->destroyRequested != 0) return false; + bool Host_Android::OnApplicationStart(olc::PixelGameEngine* pPrimary) + { + pPrimaryPGE = pPrimary; + return true; + } + bool Host_Android::OnApplicationEnd() + { return true; } void Host_Android::OnAppCmd(struct android_app *app, int32_t cmd) { auto host = reinterpret_cast(app->userData); + if (!host->pgeWindow) return; + switch (cmd) { case APP_CMD_WINDOW_RESIZED: host->pgeWindow->olc_OnWindowSize({ ANativeWindow_getWidth(app->window), ANativeWindow_getHeight(app->window) }); - __android_log_print(ANDROID_LOG_DEBUG, "PGE ANDROID", - "APP_CMD_WINDOW_RESIZED received: %dx%d", - ANativeWindow_getWidth(app->window), - ANativeWindow_getHeight(app->window)); + LOGD("APP_CMD_WINDOW_RESIZED: %dx%d", + ANativeWindow_getWidth(app->window), + ANativeWindow_getHeight(app->window)); break; case APP_CMD_INIT_WINDOW: if (app->window) { host->initialized = true; - __android_log_print(ANDROID_LOG_DEBUG, "PGE ANDROID", - "APP_CMD_INIT_WINDOW received with Window"); + LOGD("APP_CMD_INIT_WINDOW received with Window"); } break; case APP_CMD_TERM_WINDOW: { host->pgeWindow->olc_OnWindowClose(); + host->initialized = false; + LOGD("APP_CMD_TERM_WINDOW received"); } break; case APP_CMD_GAINED_FOCUS: { host->pgeWindow->olc_OnMouseFocus(true); + LOGD("APP_CMD_GAINED_FOCUS received"); } break; case APP_CMD_LOST_FOCUS: { host->pgeWindow->olc_OnMouseFocus(false); + LOGD("APP_CMD_LOST_FOCUS received"); } break; default: break; } @@ -10808,6 +10893,8 @@ namespace olc::host int32_t Host_Android::OnInputEvent(AndroidApp *app, AInputEvent *event) { auto host = reinterpret_cast(app->userData); + if (!host->pgeWindow) return 0; + auto type = AInputEvent_getType(event); if (type == AINPUT_EVENT_TYPE_MOTION) { @@ -10913,11 +11000,6 @@ namespace olc::host return { reinterpret_cast(androidApp->window) }; } - bool Host_Android::ConnectHostResourceToRenderer() - { - return true; - } - bool Host_Android::SyncWithDesktopComposite() { return true; @@ -11132,15 +11214,35 @@ namespace olc::host return content; } + + void Host_Android::PollEvents(const std::function& funcContinue, bool bBlocking) + { + while (funcContinue()) { + int events; + struct android_poll_source* source; + + const int timeOut = bBlocking ? -1 : 0; + const int ident = ALooper_pollOnce(timeOut, nullptr, &events, (void**)&source); + + if (ident >= 0) { + if (source) { + source->process(androidApp, source); + } + } else if (!bBlocking) { + break; + } + } + } + } void android_main(struct android_app* app) { - char arg0[] = "olcPixelGameEngine 3.0"; + char arg0[] = "olc::PixelGameEngine 3.0"; char* argv[] = { arg0, nullptr }; - olc::host::JNI::Init(app); olc::host::Host_Android::androidApp = app; + olc::host::JNI::Init(app); (void)main(1, argv); @@ -11150,7 +11252,7 @@ void android_main(struct android_app* app) int events; struct android_poll_source* source; - while (ALooper_pollOnce(0, nullptr, &events, (void**)&source) > ALOOPER_POLL_TIMEOUT) { + if (ALooper_pollOnce(0, nullptr, &events, (void**)&source) > ALOOPER_POLL_TIMEOUT) { if (source) { source->process(app, source); } @@ -15092,9 +15194,7 @@ namespace olc #endif #if OLC_HOST == OLC_HOST_ANDROID - imageloader = std::make_unique( - olc::host::Host_Android::androidApp->activity->assetManager - ); + imageloader = std::make_unique(); #endif // Allow host to prepare itself @@ -16239,7 +16339,7 @@ namespace olc::imload bool ImageLoader_NDKImageDecoder::CreateImageFromFile(olc::Image& image, const std::string& sFileName) { AAsset* asset = AAssetManager_open( - assetManager, + olc::host::Host_Android::androidApp->activity->assetManager, sFileName.c_str(), AASSET_MODE_BUFFER ); From 53a1b9423df0655ba3a6c4eb45636a61b9e4b812 Mon Sep 17 00:00:00 2001 From: dandistine <34634876+dandistine@users.noreply.github.com> Date: Mon, 2 Feb 2026 19:40:30 -0600 Subject: [PATCH 08/58] Fix key repeat behavior under wayland to match x11 --- dev/src/host_lin_wayland.cpp | 13 ++++++++++++- olcPixelGameEngine3.h | 13 ++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/dev/src/host_lin_wayland.cpp b/dev/src/host_lin_wayland.cpp index dd011154..e1653dbf 100644 --- a/dev/src/host_lin_wayland.cpp +++ b/dev/src/host_lin_wayland.cpp @@ -663,7 +663,18 @@ namespace olc::host if(itr != mapKeys.end()) { auto olc_key = itr->second; auto* pge_window = mapUID2OlcWindow[active_window_id]; - pge_window->olc_OnKeyPress(olc_key, state == WL_KEYBOARD_KEY_STATE_PRESSED); + + switch (state) { + case WL_KEYBOARD_KEY_STATE_RELEASED: + pge_window->olc_OnKeyPress(olc_key, false); + break; + case WL_KEYBOARD_KEY_STATE_REPEATED: + pge_window->olc_OnKeyPress(olc_key, false); + // Intentional fallthrough + case WL_KEYBOARD_KEY_STATE_PRESSED: + pge_window->olc_OnKeyPress(olc_key, true); + break; + } } } diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 4e5930ee..3c78617a 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -9915,7 +9915,18 @@ namespace olc::host if(itr != mapKeys.end()) { auto olc_key = itr->second; auto* pge_window = mapUID2OlcWindow[active_window_id]; - pge_window->olc_OnKeyPress(olc_key, state == WL_KEYBOARD_KEY_STATE_PRESSED); + + switch (state) { + case WL_KEYBOARD_KEY_STATE_RELEASED: + pge_window->olc_OnKeyPress(olc_key, false); + break; + case WL_KEYBOARD_KEY_STATE_REPEATED: + pge_window->olc_OnKeyPress(olc_key, false); + // Intentional fallthrough + case WL_KEYBOARD_KEY_STATE_PRESSED: + pge_window->olc_OnKeyPress(olc_key, true); + break; + } } } From fd14912bf3fd6ee151fcf979e7756b7612dea3fe Mon Sep 17 00:00:00 2001 From: dandistine <34634876+dandistine@users.noreply.github.com> Date: Mon, 2 Feb 2026 20:17:16 -0600 Subject: [PATCH 09/58] Should now build for Ubuntu, but they won't get key repeat --- dev/src/host_lin_wayland.cpp | 30 ++++++++++++++++++++---------- dev/src/host_lin_wayland.h | 1 + olcPixelGameEngine3.h | 31 +++++++++++++++++++++---------- 3 files changed, 42 insertions(+), 20 deletions(-) diff --git a/dev/src/host_lin_wayland.cpp b/dev/src/host_lin_wayland.cpp index e1653dbf..795e9b0e 100644 --- a/dev/src/host_lin_wayland.cpp +++ b/dev/src/host_lin_wayland.cpp @@ -355,6 +355,7 @@ namespace olc::host if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD && keyboard == nullptr) { keyboard = wl_seat_get_keyboard(seat); + keyboard_version = wl_keyboard_get_version(keyboard); wl_keyboard_add_listener(keyboard, &wayland::keyboard_listener, this); } } @@ -664,16 +665,25 @@ namespace olc::host auto olc_key = itr->second; auto* pge_window = mapUID2OlcWindow[active_window_id]; - switch (state) { - case WL_KEYBOARD_KEY_STATE_RELEASED: - pge_window->olc_OnKeyPress(olc_key, false); - break; - case WL_KEYBOARD_KEY_STATE_REPEATED: - pge_window->olc_OnKeyPress(olc_key, false); - // Intentional fallthrough - case WL_KEYBOARD_KEY_STATE_PRESSED: - pge_window->olc_OnKeyPress(olc_key, true); - break; + // Wayland keyboard version 10 and above support key repeat and release states + if(keyboard_version >= 10) + { + switch (state) { + case WL_KEYBOARD_KEY_STATE_RELEASED: + pge_window->olc_OnKeyPress(olc_key, false); + break; + case WL_KEYBOARD_KEY_STATE_REPEATED: + pge_window->olc_OnKeyPress(olc_key, false); + // Intentional fallthrough + case WL_KEYBOARD_KEY_STATE_PRESSED: + pge_window->olc_OnKeyPress(olc_key, true); + break; + } + } + else + { + // Ubuntu still parties like its 1999 apparently + pge_window->olc_OnKeyPress(olc_key, state == WL_KEYBOARD_KEY_STATE_PRESSED); } } } diff --git a/dev/src/host_lin_wayland.h b/dev/src/host_lin_wayland.h index 2075dce7..0fc6970d 100644 --- a/dev/src/host_lin_wayland.h +++ b/dev/src/host_lin_wayland.h @@ -82,6 +82,7 @@ namespace olc::host wl_seat* seat{nullptr}; wl_pointer* pointer{nullptr}; wl_keyboard* keyboard{nullptr}; + uint32_t keyboard_version{0}; xkb_context* kb_context{nullptr}; xkb_state* kb_state{nullptr}; xkb_keymap* kb_keymap; diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 3c78617a..a033a8a9 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -4666,6 +4666,7 @@ namespace olc::host wl_seat* seat{nullptr}; wl_pointer* pointer{nullptr}; wl_keyboard* keyboard{nullptr}; + uint32_t keyboard_version{0}; xkb_context* kb_context{nullptr}; xkb_state* kb_state{nullptr}; xkb_keymap* kb_keymap; @@ -9607,6 +9608,7 @@ namespace olc::host if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD && keyboard == nullptr) { keyboard = wl_seat_get_keyboard(seat); + keyboard_version = wl_keyboard_get_version(keyboard); wl_keyboard_add_listener(keyboard, &wayland::keyboard_listener, this); } } @@ -9916,16 +9918,25 @@ namespace olc::host auto olc_key = itr->second; auto* pge_window = mapUID2OlcWindow[active_window_id]; - switch (state) { - case WL_KEYBOARD_KEY_STATE_RELEASED: - pge_window->olc_OnKeyPress(olc_key, false); - break; - case WL_KEYBOARD_KEY_STATE_REPEATED: - pge_window->olc_OnKeyPress(olc_key, false); - // Intentional fallthrough - case WL_KEYBOARD_KEY_STATE_PRESSED: - pge_window->olc_OnKeyPress(olc_key, true); - break; + // Wayland keyboard version 10 and above support key repeat and release states + if(keyboard_version >= 10) + { + switch (state) { + case WL_KEYBOARD_KEY_STATE_RELEASED: + pge_window->olc_OnKeyPress(olc_key, false); + break; + case WL_KEYBOARD_KEY_STATE_REPEATED: + pge_window->olc_OnKeyPress(olc_key, false); + // Intentional fallthrough + case WL_KEYBOARD_KEY_STATE_PRESSED: + pge_window->olc_OnKeyPress(olc_key, true); + break; + } + } + else + { + // Ubuntu still parties like its 1999 apparently + pge_window->olc_OnKeyPress(olc_key, state == WL_KEYBOARD_KEY_STATE_PRESSED); } } } From e758ede365c8ce87bec3aa432554c9a21fadda11 Mon Sep 17 00:00:00 2001 From: dandistine <34634876+dandistine@users.noreply.github.com> Date: Mon, 2 Feb 2026 20:22:57 -0600 Subject: [PATCH 10/58] Fix the fix. :shipit: --- dev/src/host_lin_wayland.cpp | 2 ++ olcPixelGameEngine3.h | 2 ++ 2 files changed, 4 insertions(+) diff --git a/dev/src/host_lin_wayland.cpp b/dev/src/host_lin_wayland.cpp index 795e9b0e..f2352d78 100644 --- a/dev/src/host_lin_wayland.cpp +++ b/dev/src/host_lin_wayland.cpp @@ -666,6 +666,7 @@ namespace olc::host auto* pge_window = mapUID2OlcWindow[active_window_id]; // Wayland keyboard version 10 and above support key repeat and release states + #ifdef WL_KEYBOARD_KEY_STATE_REPEATED_SINCE_VERSION if(keyboard_version >= 10) { switch (state) { @@ -681,6 +682,7 @@ namespace olc::host } } else + #endif { // Ubuntu still parties like its 1999 apparently pge_window->olc_OnKeyPress(olc_key, state == WL_KEYBOARD_KEY_STATE_PRESSED); diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index a033a8a9..d81a2806 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -9919,6 +9919,7 @@ namespace olc::host auto* pge_window = mapUID2OlcWindow[active_window_id]; // Wayland keyboard version 10 and above support key repeat and release states + #ifdef WL_KEYBOARD_KEY_STATE_REPEATED_SINCE_VERSION if(keyboard_version >= 10) { switch (state) { @@ -9934,6 +9935,7 @@ namespace olc::host } } else + #endif { // Ubuntu still parties like its 1999 apparently pge_window->olc_OnKeyPress(olc_key, state == WL_KEYBOARD_KEY_STATE_PRESSED); From bf84df17fd52dd11d50df1289b1156609bc8858a Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Tue, 3 Feb 2026 10:50:30 -0800 Subject: [PATCH 11/58] [macos][opengl] undefine GL_CLAMP to silence redefinition warnings --- dev/src/api_opengl.h | 3 ++- olcPixelGameEngine3.h | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/dev/src/api_opengl.h b/dev/src/api_opengl.h index b46fe1cc..dda1edb0 100644 --- a/dev/src/api_opengl.h +++ b/dev/src/api_opengl.h @@ -45,7 +45,8 @@ #define CALLSTYLE #define OGL_LOAD(t) &::t #define GL_GLEXT_PROTOTYPES - #define GL_CLAMP GL_CLAMP_TO_EDGE + #undef GL_CLAMP + #define GL_CLAMP GL_CLAMP_TO_EDGE #include // Correct issue with Unknown type name 'ptrdiff_t' #include #include diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 37eb99bd..8f06dc5b 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -5160,7 +5160,8 @@ namespace olc::host #define CALLSTYLE #define OGL_LOAD(t) &::t #define GL_GLEXT_PROTOTYPES - #define GL_CLAMP GL_CLAMP_TO_EDGE + #undef GL_CLAMP + #define GL_CLAMP GL_CLAMP_TO_EDGE #include // Correct issue with Unknown type name 'ptrdiff_t' #include #include From 49aa97dc2bc7708dfd152f3db014bf71c6070ff5 Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Sat, 7 Feb 2026 11:08:17 +0000 Subject: [PATCH 12/58] Diego found a bug with Draw2D::ImageRotated. Used the opportunity to create graph paper asset which will be very useful. Also fixed stb_image bug, and updated msvc --- dev/msvc/olcPGE3.sln | 8 +- dev/msvc/olcPGE3.vcxproj | 19 ++++ dev/msvc/olcPGE3.vcxproj.filters | 6 ++ .../olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj | 8 +- .../olcPGE3_BuildSH.vcxproj.filters | 3 + dev/src/draw2d.cpp | 8 +- dev/src/imload_stb_image.h | 4 +- dev/src/sh_template.h | 2 +- examples/assets/graph_paper.png | Bin 0 -> 22988 bytes examples/olcPGE3_ImageScaleRotate.cpp | 98 ++++++++++++++++++ olcPixelGameEngine3.h | 42 ++++++-- 11 files changed, 179 insertions(+), 19 deletions(-) create mode 100644 examples/assets/graph_paper.png create mode 100644 examples/olcPGE3_ImageScaleRotate.cpp diff --git a/dev/msvc/olcPGE3.sln b/dev/msvc/olcPGE3.sln index d1e9d390..5490fb76 100644 --- a/dev/msvc/olcPGE3.sln +++ b/dev/msvc/olcPGE3.sln @@ -1,12 +1,18 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 18 -VisualStudioVersion = 18.3.11408.92 d18.3 +VisualStudioVersion = 18.3.11408.92 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "olcPGE3", "olcPGE3.vcxproj", "{E7ACE477-3385-4FDE-95AD-26E40DF508CC}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "olcPGE3_BuildSH", "olcPGE3_BuildSH\olcPGE3_BuildSH.vcxproj", "{227300C2-9BC8-4E66-95FC-334A009D449B}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{9462016E-9662-468F-98F6-CBA5E15D0D82}" + ProjectSection(SolutionItems) = preProject + ..\src\imload_stb_image.cpp = ..\src\imload_stb_image.cpp + ..\src\imload_stb_image.h = ..\src\imload_stb_image.h + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 diff --git a/dev/msvc/olcPGE3.vcxproj b/dev/msvc/olcPGE3.vcxproj index 2f7fcc46..8a5cee2b 100644 --- a/dev/msvc/olcPGE3.vcxproj +++ b/dev/msvc/olcPGE3.vcxproj @@ -202,6 +202,9 @@ true + true + true + true true @@ -236,6 +239,9 @@ true + true + true + true @@ -250,6 +256,7 @@ true true + @@ -276,6 +283,9 @@ true + true + true + true true @@ -307,6 +317,9 @@ true + true + true + true true @@ -320,6 +333,12 @@ true true + + true + true + true + true + diff --git a/dev/msvc/olcPGE3.vcxproj.filters b/dev/msvc/olcPGE3.vcxproj.filters index df812651..0f8f7d7c 100644 --- a/dev/msvc/olcPGE3.vcxproj.filters +++ b/dev/msvc/olcPGE3.vcxproj.filters @@ -159,6 +159,9 @@ Hosts\Android Specific + + Header Files + @@ -242,5 +245,8 @@ Hosts\Android Specific + + Source Files + \ No newline at end of file diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj index 09326a02..460ee7a1 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj @@ -49,7 +49,13 @@ true true - + + + true + true + true + true + true true diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters index 5907981e..774183f3 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters @@ -69,6 +69,9 @@ Source Files + + Source Files + diff --git a/dev/src/draw2d.cpp b/dev/src/draw2d.cpp index 9842c1bd..e115a0f9 100644 --- a/dev/src/draw2d.cpp +++ b/dev/src/draw2d.cpp @@ -1148,10 +1148,10 @@ const GPUTask& olc::Draw2D::ImageRotated(olc::ImageRegion image, const olc::vf2d olc::vf2d size = image.regionsize * scale; std::vector vPoints(4); - vPoints[0] = (olc::vf2d(0.0f, 0.0f) - center) * scale; - vPoints[1] = (olc::vf2d(size.x, 0.0f) - center) * scale; - vPoints[2] = (size - center) * scale; - vPoints[3] = (olc::vf2d(0.0f, size.y) - center) * scale; + vPoints[0] = olc::vf2d(0.0f, 0.0f) - (center * scale); + vPoints[1] = olc::vf2d(size.x, 0.0f) - (center * scale); + vPoints[2] = size - (center * scale); + vPoints[3] = olc::vf2d(0.0f, size.y) - (center * scale); float c = cos(theta), s = sin(theta); for (size_t i = 0; i < 4; i++) diff --git a/dev/src/imload_stb_image.h b/dev/src/imload_stb_image.h index a2002808..7f073d01 100644 --- a/dev/src/imload_stb_image.h +++ b/dev/src/imload_stb_image.h @@ -9,7 +9,7 @@ //! END STDHEADER //! START DECLARATION -#if !defined(PGE_IMAGELOADER_LIB_PNG_DECLARED) +#if !defined(PGE_IMAGELOADER_STB_DECLARED) namespace olc::imload { class ImageLoader_STB_Image : public ImageLoader @@ -32,6 +32,6 @@ namespace olc::imload }; } -#define PGE_IMAGELOADER_LIB_PNG_DECLARED 1 +#define PGE_IMAGELOADER_STB_DECLARED 1 #endif //! END DECLARATION diff --git a/dev/src/sh_template.h b/dev/src/sh_template.h index c6e06c00..1850c15a 100644 --- a/dev/src/sh_template.h +++ b/dev/src/sh_template.h @@ -94,7 +94,7 @@ Primary Contributors ~~~~~~~~~~~~~~~~~~~~ @javidx9 (aka David Barr, OneLoneCoder) - @Moros1198, @dandistine, @johnnyg63, @iCiaran + @Moros1198, @dandistine, @johnnyg63, @iCiaran, @DCubix With assistance from all of the developers of olc::PixelGameEngine 2 over the years, and the many community contributors that have provided bug fixes, suggestions, diff --git a/examples/assets/graph_paper.png b/examples/assets/graph_paper.png new file mode 100644 index 0000000000000000000000000000000000000000..d63dfdf6bd54859e610bf282fb73298390b49b5f GIT binary patch literal 22988 zcmeG^30PBCx(85k)G2mRTnWY3+Bbd>mI@_6?kHO8v@X>;*y<1wLSzI&NoWE>F4nfp zYo&ReX-m;+I<>8xQ5)?BR?VfuDC4-Kj?aPxxv>#qWl3nv=3UM^H$k=liXZ)^)5h;Z zxc8iU?tlL6{OkRnmCKj>a>9%W0DxaEeQePx0LGv{$AIzU&@X)UylnuCJ(%^>TKU># zPYBYbIT2|Y(v9K>WlkO%2Otqs=B1@?7Ry67iX~aONnz$AL|AB6Mp9VH{AH2L@{+}y zvK}kS7q2c_{#1I==JfcCFibchQ7J$RT$w&@tCY%tOn4gg;SheWU?uXG;QrITBJWs&qD-?Y za`Ji>&y{uW9^rwnOv~d(MMU!ZPR`pTl}lxtq(eN?Gwk(XMtaXn@(S{^-SB6m^TpZX z9C5B(hUQ20dy!a~)w}0@U~wZ}%geJehnUWdcuhyNS+=YPpnl8W+;vV)KX_#F#m^zc zbrWIGCuC0*=85^M#4>3?e!6(^b7)_4+_*z;6C~%0)8yhs=qD*GIx;#oJTf{wGVZC! zctLcW;Nkdrk?3E~vNBhU0sM4Xh7CQrM?)E=@3xQQN65Cabj zNAEMq(rjry0trDL<9>7R(9-1OmHE=ltZXzUTeW0i=+ec>u~G4{vEk7XQSP}MwFE1( zl;Z5Qi?R@@Wr!1#!lELhA09CL^pL~u_pe4mh+g7Zl%uI&X-=9%oRXCx-_&Ql-jOH7 zxf1Uojyl-|oH(q>%|i5yc0=KQYHjaWQdnFxzt5|l_8Q^@EHp7AT_DerXN%oe9)Vks zE%#h`F0->ZDHCL;|RktO+Qd7D+zPhpm(9J?FWHR3Flz{2nEWE_&3kI2W;QK2ox~%im80!0-7i#DreV2f#zG6}+aO zx9G|H>afgB^tAG@|DO7%gFEit^I+`Mn5g@v#_X9C8>6RJT8*~m$39;3`RdmjoSzx^ z8}{vgBW%^jZ$5A+t?2B|C+Aek5*)>yj%L{xj`as$IRBeRwX;i~h}Dh_5SrF!G-qx3 z^7-;{f-Lo~z7Wiw5w!Y(u=B$xV|nBLHB1xn^{4mMPx`Z$C8)lPpMq<^;c^j zT>5fi#FHuS6|LX6@Ot(3hc{)TRv|z2=-0{_r1XJYx@T0tS-87u0F0)Yb~eRijAy*tR~L(xx`N}1-fVP zUD76zX{Mvz)$ekzi%(AUD?L(NFin}L(VL82%3`{!&e%2n(Ez|c4p#>OK!0|WEroWQ z+O-V>bkorQ03z%MwC%X_P+b)p4-BR&XrIBtCT3Ab=dWQ8ojxzXwu(K$X-9&fE%a(M$%4nO?p4ZPAXseSLG20uFST1>J*CBW!#xqw?g$15t?P{;x(ns*WkfIGbY#<7HF><~s9ryuY@V^Ve z1M>U=xnI?#XiEUBuR$YdZwBsv5Ue~Jvh85ktZta5Q_{rb@3u2>yZw%Ck z^jAIf+&jU})UOn9pk8faXDy-{F>iuM2BW}Dg6yEibFUo$gxlmQo%jehR5KPKm4jVf zpFTNu_a1rgJ8z<==@_I=AH9(L!r3O~hNc%~9`x>N)NI>?Q*~baz;5@#WiKPGknuRY z3AHJ$QB7z<=_d%(Ql1=WA!v2iQQB5-x<8o-r>YT$NWLPS8BQP;l zy`Vw)>c9HDvm1Rf2WyV8{x;}tfy~i{4@9Xv7r*OWrh_+7Zt7hz-t_H}KkQ5iXHxgD z(x6oIAaHA4Th#cO`X}A2bCZl7iRf&GC7I3F?q4YBR5AL2QgXIKzu;4X8L7M=pxyGHG0zF}|NO`(mL+U1yAn zFj26bLN%TK!g}#!f1c!dTyKOu0_bU$SlLK>8{LYV=)qRhEUj4@%5Dz=J9sQ^Cz_5s`EZkQ% ze%HtjkKs8*h$xB#guGZyE0;==r849ALf!t)GGOGdR#x*QbpEhVw3}ObT}_^8@tZJ3OjzSL%4s z)UvO(#%OdP-Dh+ab1E=Bvsu#GwKr%yptY_9&YDY1)vURir>_j_YhwSThqt z-Vg$d3<*Z*D@=0=g_|QS2WBCQkF!|_m!cUdLHccvIp~}KNudhPY-aaYf>JnhctfU% zDRYiYQgBBOgKu@QS9`6O0aoz}nc5E>%|2tMzYXX!X8wJ~jPD?GThb#r$mpw4<3Y_{ zt0RbH=kN3fPT8e9^SR*n$0md7Qz>Oz>*~x{h`O%crHPvkEadl`i9{40rJEfD-JrqT z_s}_C-4LYv8?05M;5inAY{BXVb4dYgtF6%_1p^DNN6OB5rb_oMY^l=~Af=}YK@ZhP zh4lSU$|K;Gcwn)PzGG?Dl|z0c$s63q_qa+?rD#V!S7`*Hk)MRCpr@(h>lgH3-$*J*R@EC)5)M{pHl#>52AqII=8k$dN93J6XYjS zwHqDy^(o_kL0ji`akptL@H}QMHPa)NF`EHD^8F|!4;pc!>(ef|13lv zYq#U;c!N=2R12e#XKO2TM+CdROgYzBkG1{|AYa&ul%HW;Gr_(KD-?c@_#)R%#bF9+$nyhG-aHE^97)K1`Kh-`eFQTjOkW<6TH(agjwRkTKD`wt zNSpgog#&^EKZ8`EulB`Vi}Ifx$DsA6>0=>bnefO(m}@5*J1DZ#UQe=$PvE6?{7{`b zB!CNS75ncF$mR;EI!Jr{VWyE4RN;*xlU+)+HL*GPu12CmYu)F7;!G8@3zqTlji{=S zGyd4`W(T0;Z`U!X$L}GIz|Dli^?RtrLo!<>Y@xGC1WBFsvMDVU!*I)X{5@!6c~5b5xr~`4XbK)6A=cIgFA-xbpIt>BwY^g1&(74iOqbVbt&(QPW|0|h z)Yq9iD4YIdv5l&gT;C%K)HF3NBRa7!TI%Cq-esZ@e)~B*TXHIeVo#ZI%{fJ?qY7oJ zH7~+rih3QKsp&EqEBK`I>lcl+>Li^_U-BT<07Y!2v;<1*+a?+a8l|;aefts$|KN8- z3s%Sw4ysmvZA);o`l5s>u5#mjX%7)O(xWYp{~ z$ee$|UPqnPr5p`tU!rwLpu(_@FrjB*1vyct=y0B{sDXc%935ZsES2KV)@n=f-|z_k zye@4S$xLvD6Y!F~jeTXE{R;1BGt6rv%rUSTcVPR}Tbk`81+UDWtT~sXb-)u6+U2}N z1Q=?^Ua8Uo72Tz0cU}&J-z!3kU4+K9(0QO9BE-*g5R8pTgY|Kw?lS}rqi0Fh#$0sh zF66)x>Kg^isHk0n1>ydLQO)<&{c8~uH;VhBZJmS zr3RLoTX5=1|FYMcN1{7nmVyOdZ1XNY#Lf`Zv|n=vCRf~Wi<$vn)lWBqePyiPoa8HG z{dbnJI?ts~2Bj-ZW}c4O96J_ROkdo2fogSwbsL|=7n?7y1vNZFMG*k+*igX4HVIwo zk$r8*=aGJv7OEu?-KgdAV~(!bKwZIwLDZL#8mX{O!9n#l0+pOc*mr5|1oM||D27r! zd>wEno6L@^a+;t%hqp+E9p-@WRVfc>iWs#jWuG@cVsHM(n|lAbgo0&TJh?EZaxzkD z>dHd+eX`aZ0I+x*CAuQj6cC5qsT#Te>)G2P(9lK>M)ErLj`D&qly0uUi z_|1_$>*Kb+pRi|fO~Aw@--bL>_+PVTSQn0cS+#D$oEIw>liGMbDj<9m@f6v-xZok7H|{Y5LF&20JPx z?ow9GV0}5W2k%Q$c(>~Go{2js?n42$*O#V`8M>8eevIDT{e=zI{gp6y!;pFShM_4O zV7J*Y#1&4p(Ch^azQz^HAIZgs=pmdve8W%)Dj!nim>m>@)T4+uhO3pzOck+Lt!9+b zKkLzgI9rNz6QWPYLdbqnW@~YVUA` z0zQuP>X;ohj?_qoZsFXVx!c_&gb&svq!_$WlI)j39MJHa)j5w7mD@V;Mw`=!;k%bo zIKXbJd#QN$&>fuG%EGVRO|Py&nqJ8v`Xs&kV0%*odk%^RAE+VP?03XIY$I1CP;a;0Z|G7c z>f$FQ>dwFmmq97<2J#@+x1YlQ51zc=fR|9v52$k8xiF0SRZR+R$7cu5@<%P=tw>o{ zHyMqMI4h8HrKZbOsG}~zVIwWjFxMZ|47}xDO3Qu@Pnm(G3Ia=;awVZ3 z`T|5AwF{NV*BocBQLISy%0V+k)}wJ)g12^}1f0w<5_%uOzJ|QF%$nhv!ymux4`htC zcE*4DC$dj~(YIMp4YJR*_~Bd&tY~zdT@lY8dAY`&s6m`j5d`f*9|;;W#~b5%Mu)#L_cRgQQ_2 zZmWVMJ2eeMJb@9T9^5dqZ$W&64j=BDK>XY93HJ%GPk?{uXw~Ss6+ahOg1$N(eGH?* zN~J)0376wFf5F^wuoL;S_Zu4FuFCof9EQ!f8#s4z-HYVSJTJtM!B^c>g&%=?P*Oy- z-0kny)rlCY0;3#rkxj_Cz%>fa=yT4ee;WkAb@qzXrNO&QC;)hEc&pCJb5)>kQH{1) zcl))UKC_NXjI5B2AkgAF6?~_{4}2=HaS1OTc+>K3>tB&B0!tS!U-Z$!4bT5Sz{ndi literal 0 HcmV?d00001 diff --git a/examples/olcPGE3_ImageScaleRotate.cpp b/examples/olcPGE3_ImageScaleRotate.cpp new file mode 100644 index 00000000..27337ab5 --- /dev/null +++ b/examples/olcPGE3_ImageScaleRotate.cpp @@ -0,0 +1,98 @@ +/* + olc::PixelGameEngine3 Example - ImageRotated (and scaled) + + Example of using ImageRotated to draw images with different + rotation and scaling. Note that the image is rotated around + a pivot point, which is specified in the function call. + + Licenced under the OLC-3 License +*/ + + +// Define OLC_PGE3_APPLICATION to include the implementation of +// the Pixel Game Engine as part of this translation unit +#define OLC_PGE3_APPLICATION +#include "../olcPixelGameEngine3.h" + +// Example application demonstrating image rotation. This class +// overrides the olc::PixelGameEngine base class by implementing +// the OnUserCreate() and OnUserUpdate() functions +class Example_ImageRotated : public olc::PixelGameEngine +{ +public: + Example_ImageRotated() + { + + } + +protected: + + // An Image object + olc::Image imgTest; + + // Handy graph paper for pixel testing + olc::Image imgGraph; + +public: + // Called once at the start, so create things here + bool OnUserCreate() override + { + // Load asset + CreateImageFromFile(imgTest, "./assets/minsanity_texture.png"); + + // Load Graph Paper + CreateImageFromFile(imgGraph, "./assets/graph_paper.png"); + return true; + } + + // Called every frame, so update things here + bool OnUserUpdate(float fElapsedTime) override + { + // Clear whole screen + draw.Clear(olc::Colour::VERY_DARK_BLUE); + + // Draw graph paper background + draw.Image(imgGraph, { -512,-512 }); + + // Note! Our imgTest is 64x64 pixels + + // Draw Image + draw.Image(imgTest, { 64, 64 }); + + // Draw Scaled Image + draw.Image(imgTest, { 192, 64 }, { 2.0f, 1.0f }); + + // Draw Scaled Image (but also horizontally flipped) + draw.Image(imgTest.flipH(), {64, 192}, {1.0f, 3.0f}); + + // Draw Rotated Image, Scaled evenly + draw.ImageRotated(imgTest, { 256, 256 }, TotalTimeElapsed() * 0.1f, { 32.0f, 32.0f }, {2.0f, 2.0f}); + + // Draw Rotated Image, Scaled unevenly + draw.ImageRotated(imgTest, { 448, 256 }, TotalTimeElapsed() * -0.1f, { 32.0f, 32.0f }, { 1.0f, 0.5f }); + + // Draw Rotated Image not around center, Scaled unevenly and flipped vertically + draw.ImageRotated(imgTest.flipV(), { 384, 384 }, TotalTimeElapsed() * 0.2f, { 16.0f, 32.0f }, { 2.0f, 1.0f }); + + // Successful frame + return true; + } +}; + + +// Main entry point for the application +int main() +{ + // Construct demo application + Example_ImageRotated demo; + + // Create "screen" of 512x480 "pixels" + // with a pixel size of 2x2 actual screen pixels + if (demo.Construct({ 512, 480 }, { 2, 2 })) + { + // Start the application + demo.Start(); + } + + return 0; +} \ No newline at end of file diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index fe5f6c9c..ad3a9198 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -94,7 +94,7 @@ Primary Contributors ~~~~~~~~~~~~~~~~~~~~ @javidx9 (aka David Barr, OneLoneCoder) - @Moros1198, @dandistine, @johnnyg63, @iCiaran + @Moros1198, @dandistine, @johnnyg63, @iCiaran, @DCubix With assistance from all of the developers of olc::PixelGameEngine 2 over the years, and the many community contributors that have provided bug fixes, suggestions, @@ -5747,7 +5747,7 @@ namespace olc::imload #endif #if OLC_IMAGELOADER == OLC_IMAGELOADER_STB_IMAGE -#if !defined(PGE_IMAGELOADER_LIB_PNG_DECLARED) +#if !defined(PGE_IMAGELOADER_STB_DECLARED) namespace olc::imload { class ImageLoader_STB_Image : public ImageLoader @@ -5770,7 +5770,7 @@ namespace olc::imload }; } -#define PGE_IMAGELOADER_LIB_PNG_DECLARED 1 +#define PGE_IMAGELOADER_STB_DECLARED 1 #endif #endif @@ -14371,10 +14371,10 @@ const GPUTask& olc::Draw2D::ImageRotated(olc::ImageRegion image, const olc::vf2d olc::vf2d size = image.regionsize * scale; std::vector vPoints(4); - vPoints[0] = (olc::vf2d(0.0f, 0.0f) - center) * scale; - vPoints[1] = (olc::vf2d(size.x, 0.0f) - center) * scale; - vPoints[2] = (size - center) * scale; - vPoints[3] = (olc::vf2d(0.0f, size.y) - center) * scale; + vPoints[0] = olc::vf2d(0.0f, 0.0f) - (center * scale); + vPoints[1] = olc::vf2d(size.x, 0.0f) - (center * scale); + vPoints[2] = size - (center * scale); + vPoints[3] = olc::vf2d(0.0f, size.y) - (center * scale); float c = cos(theta), s = sin(theta); for (size_t i = 0; i < 4; i++) @@ -16690,15 +16690,37 @@ namespace olc::imload // Create an image resource based on an image file asset in memory bool ImageLoader_STB_Image::CreateImageFromMemory(olc::Image& image, const uint8_t* data, const size_t bytes) { - return false; + stbi_uc* pixelData = nullptr; + int width = 0, height = 0, cmp = 0; + pixelData = stbi_load_from_memory(data, bytes, &width, &height, &cmp, 4); + if(!pixelData) + return false; + + image.Create({width, height}); + std::memcpy(reinterpret_cast(image.Data()), pixelData, width * height * 4); + + delete[] pixelData; + + return true; } // Create an image resource based on an image file asset in memory bool ImageLoader_STB_Image::CreateImageFromMemory(olc::Image& image, const std::vector& data) { - return false; + stbi_uc* pixelData = nullptr; + int width = 0, height = 0, cmp = 0; + pixelData = stbi_load_from_memory(data.data(), data.size(), &width, &height, &cmp, 4); + if(!pixelData) + return false; + + image.Create({width, height}); + std::memcpy(reinterpret_cast(image.Data()), pixelData, width * height * 4); + + delete[] pixelData; + + return true; } - + // Store an image as a file asset on disk bool ImageLoader_STB_Image::WriteImageToFile(const olc::Image& image, const std::string& sFileName) { From 7da5943db0b769e64fe2406f1294a55c4475c406 Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Sat, 7 Feb 2026 12:27:03 +0000 Subject: [PATCH 13/58] Added "world transforms" example fixed a bug in olc::t_2d --- .../olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj | 8 +- .../olcPGE3_BuildSH.vcxproj.filters | 3 + dev/src/transform2d.h | 2 +- examples/olcPGE3_WorldTransform.cpp | 165 ++++++++++++++++++ olcPixelGameEngine3.h | 2 +- 5 files changed, 177 insertions(+), 3 deletions(-) create mode 100644 examples/olcPGE3_WorldTransform.cpp diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj index 460ee7a1..9a113d36 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj @@ -49,7 +49,12 @@ true true - + + true + true + true + true + true true @@ -122,6 +127,7 @@ true true + diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters index 774183f3..521cf969 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters @@ -72,6 +72,9 @@ Source Files + + Source Files + diff --git a/dev/src/transform2d.h b/dev/src/transform2d.h index 6dfeec74..3b350f81 100644 --- a/dev/src/transform2d.h +++ b/dev/src/transform2d.h @@ -233,7 +233,7 @@ namespace olc // Constructs resultant matrices when transformation changes inline constexpr void update() { - m_mForward = m_mRotate * m_mShear * m_mScale * m_mTranslate; + m_mForward = m_mScale * m_mRotate * m_mShear * m_mTranslate; m_mInverse = m_mForward.invert(); } diff --git a/examples/olcPGE3_WorldTransform.cpp b/examples/olcPGE3_WorldTransform.cpp new file mode 100644 index 00000000..375ff547 --- /dev/null +++ b/examples/olcPGE3_WorldTransform.cpp @@ -0,0 +1,165 @@ +/* + olc::PixelGameEngine3 Example - World Transformations + + Demonstrates how PGE Drawing functions obey a global + world transform. + + In this example, the world transform is manipulated + using the arrow keys and Q/A for rotation. + + Holding SHIFT allows you to scale the world instead + of translating it. + + SPACE resets the world transform to the default state. + + Licenced under the OLC-3 License +*/ + + +// Define OLC_PGE3_APPLICATION to include the implementation of +// the Pixel Game Engine as part of this translation unit +#define OLC_PGE3_APPLICATION +#include "../olcPixelGameEngine3.h" + +// Example application demonstrating world transforms. This class +// overrides the olc::PixelGameEngine base class by implementing +// the OnUserCreate() and OnUserUpdate() functions +class Example_WorldTransform: public olc::PixelGameEngine +{ +public: + Example_WorldTransform() + { + + } + +protected: + + // An Image object + olc::Image imgTest; + + // Handy graph paper for pixel testing + olc::Image imgGraph; + + olc::vf2d vWorldOffset = { 0.0f, 0.0f }; + olc::vf2d vWorldScale = { 1.0f, 1.0f }; + float fWorldRotation = 0.0f; + +public: + // Called once at the start, so create things here + bool OnUserCreate() override + { + // Load asset + CreateImageFromFile(imgTest, "./assets/minsanity_texture.png"); + + // Load Graph Paper + CreateImageFromFile(imgGraph, "./assets/graph_paper.png"); + return true; + } + + void DrawScene(olc::Pixel col) + { + // Draw graph paper background + draw.Image(imgGraph, { -512,-512 }, { 1, 1 }, col); + // Draw Triangle and Circle + draw.Triangle({ 64.0f, 64.0f }, { 128.0f, 96.0f }, { 32.0f, 192.0f }, col); + draw.Circle({ 256.0f, 256.0f }, 64.0f, col); + // Draw Sprite + draw.Image(imgTest, { 192.0f, 64.0f }, { 1.0f, 1.0f }, col); + } + + + + // Called every frame, so update things here + bool OnUserUpdate(float fElapsedTime) override + { + // Clear whole screen + draw.Clear(olc::Colour::VERY_DARK_BLUE); + + // Get Mouse in screen space + olc::vf2d vMouseScreen = mouse.GetPosition(); + + + // Keyboard handling for world transform + if (keyboard.GetKey(olc::Key::SHIFT).bHeld) + { + if (keyboard.GetKey(olc::Key::LEFT).bPressed) + vWorldScale.x -= 0.125; + if (keyboard.GetKey(olc::Key::RIGHT).bPressed) + vWorldScale.x += 0.125; + if (keyboard.GetKey(olc::Key::UP).bPressed) + vWorldScale.y -= 0.125; + if (keyboard.GetKey(olc::Key::DOWN).bPressed) + vWorldScale.y += 0.125; + } + else + { + if (keyboard.GetKey(olc::Key::LEFT).bHeld) + vWorldOffset.x -= 64.0f * fElapsedTime; + if (keyboard.GetKey(olc::Key::RIGHT).bHeld) + vWorldOffset.x += 64.0f * fElapsedTime; + if (keyboard.GetKey(olc::Key::UP).bHeld) + vWorldOffset.y -= 64.0f * fElapsedTime; + if (keyboard.GetKey(olc::Key::DOWN).bHeld) + vWorldOffset.y += 64.0f * fElapsedTime; + } + + if (keyboard.GetKey(olc::Key::Q).bHeld) + fWorldRotation -= 0.5f * fElapsedTime; + if (keyboard.GetKey(olc::Key::A).bHeld) + fWorldRotation += 0.5f * fElapsedTime; + + if (keyboard.GetKey(olc::Key::SPACE).bPressed) + { + vWorldOffset = { 0.0f, 0.0f }; + vWorldScale = { 1.0f, 1.0f }; + fWorldRotation = 0.0f; + } + + // Clamp world transform values to reasonable limits + vWorldOffset = vWorldOffset.clamp({ -512.0f, -512.0f }, { 512.0f, 512.0f }); + vWorldScale = vWorldScale.clamp({ 0.125f, 0.125f }, { 4.0f, 4.0f }); + + // Draw in white the default transform (world offset 0,0, scale 1,1, rotation 0) + draw.WorldReset(); + DrawScene(olc::Colour::WHITE); + + // Draw in yellow the transformed world + draw.WorldOffset(vWorldOffset); + draw.WorldScale(vWorldScale); + draw.WorldRotate(fWorldRotation); + DrawScene(olc::Colour::YELLOW); + + // Get Mouse in "transformed world" space + olc::vf2d vMouseWorld = draw.ScreenToWorld(vMouseScreen); + + // Draw Status + draw.WorldReset(); + draw.StringProp({ 8, 300 }, "Offset: Arrow Keys, Scale: Shift & Arrow Keys, Rotate: Q/A, SPACE: Reset"); + draw.StringProp({ 8, 330 }, "World Offset: " + vWorldOffset.str()); + draw.StringProp({ 8, 340 }, "World Scale: " + vWorldScale.str()); + draw.StringProp({ 8, 350 }, "World Rotation: " + std::to_string(fWorldRotation)); + draw.StringProp({ 8, 360 }, "Mouse in screen space: " + vMouseScreen.str()); + draw.StringProp({ 8, 370 }, "Mouse in world space: " + vMouseWorld.str()); + + // Successful frame + return true; + } +}; + + +// Main entry point for the application +int main() +{ + // Construct demo application + Example_WorldTransform demo; + + // Create "screen" of 512x480 "pixels" + // with a pixel size of 2x2 actual screen pixels + if (demo.Construct({ 512, 480 }, { 2, 2 })) + { + // Start the application + demo.Start(); + } + + return 0; +} \ No newline at end of file diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index ad3a9198..78991046 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -1393,7 +1393,7 @@ namespace olc // Constructs resultant matrices when transformation changes inline constexpr void update() { - m_mForward = m_mRotate * m_mShear * m_mScale * m_mTranslate; + m_mForward = m_mScale * m_mRotate * m_mShear * m_mTranslate; m_mInverse = m_mForward.invert(); } From d273031ccd030a2679ad41af3f541db7ea39c35d Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Sat, 7 Feb 2026 15:51:51 +0000 Subject: [PATCH 14/58] Quick refactor, updated all demos too --- .../olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj | 15 ++++++---- dev/src/core.cpp | 26 ++++++++--------- dev/src/core.h | 2 +- dev/tests/test_mh.cpp | 6 ++-- examples/olcPGE3_AntiAliasing.cpp | 6 ++-- examples/olcPGE3_BatchesOfFills.cpp | 12 ++++---- examples/olcPGE3_CirclesEllipses.cpp | 12 ++++---- examples/olcPGE3_PixelShaders.cpp | 4 +-- examples/olcPGE3_Shockwave.cpp | 4 +-- examples/olcPGE3_TextBasics.cpp | 2 +- olcPixelGameEngine3.h | 28 +++++++++---------- 11 files changed, 61 insertions(+), 56 deletions(-) diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj index 9a113d36..71ccc542 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj @@ -26,10 +26,10 @@ true - true - true - true - true + false + false + false + false true @@ -127,7 +127,12 @@ true true - + + true + true + true + true + diff --git a/dev/src/core.cpp b/dev/src/core.cpp index b9061120..aa4d03e9 100644 --- a/dev/src/core.cpp +++ b/dev/src/core.cpp @@ -54,7 +54,7 @@ namespace olc { //pRenderer->RetargetDevice(pHost->GetHostWindowDescriptor(this)); pRenderer->PrepareWindowTarget(pHost->GetHostWindowDescriptor(this)); - CreateImage(GetDefaultImage(), vScreenSize); + CreateImage(GetScreen(), vScreenSize); SetWindowSize(vScreenSize * vPixelSize); // Assume 1:1 Relationship for now @@ -86,7 +86,7 @@ namespace olc keyboard.UpdateState(); draw.SetGPU(pRenderer); - draw.SetTarget(GetDefaultImage()); + draw.SetTarget(GetScreen()); pRenderer->DisplayPrepare(fElapsedTime, fTotalElapsedTime); @@ -114,9 +114,9 @@ namespace olc // Finialise any outstanding tasks draw.ProcessGPUTasks(); - if (GetDefaultImage().GetConfig().MSAA) + if (GetScreen().GetConfig().MSAA) { - pRenderer->ResolveMSAA(uint32_t(GetDefaultImage().GetGPUID())); + pRenderer->ResolveMSAA(uint32_t(GetScreen().GetGPUID())); } draw.ResetShader(); @@ -131,7 +131,7 @@ namespace olc { // Set the viewport to maintain the aspect ratio of GetDefaultImage() and maximise to // fit within the window client area - float fAspectScreen = float(GetDefaultImage().Size().x) / float(GetDefaultImage().Size().y); + float fAspectScreen = float(GetScreen().Size().x) / float(GetScreen().Size().y); vViewSize.x = (int32_t)vWindowSize.x; vViewSize.y = (int32_t)((float)vViewSize.x / fAspectScreen); @@ -157,7 +157,7 @@ namespace olc // Present final composite pRenderer->SetViewport(vViewPos, vViewSize); pRenderer->ClearViewport(config.colClear, true, true); - draw.ImageRect(GetDefaultImage().flipV(), { 0.0,0.0 }, vViewSize); + draw.ImageRect(GetScreen().flipV(), { 0.0,0.0 }, vViewSize); draw.ProcessGPUTasks(); // Update Window's primary surface @@ -243,7 +243,7 @@ namespace olc pImageLoader = imload; } - olc::Image& PGEWindow::GetDefaultImage() + olc::Image& PGEWindow::GetScreen() { return imgPrimary; } @@ -265,7 +265,7 @@ namespace olc const olc::vi2d& PGEWindow::ScreenSize() { - return GetDefaultImage().Size(); + return GetScreen().Size(); } bool PGEWindow::olc_OnMouseMove(const olc::vi2d& vMousePos) @@ -279,8 +279,8 @@ namespace olc // Scale mouse into view coordinates mouse.SetPosition( - (olc::vf2d(pos) / olc::vf2d(vWindowSize - (vViewPos * 2)) * GetDefaultImage().Size()) - .clamp({ 0.0f, 0.0f }, olc::vf2d(GetDefaultImage().Size()-1))); + (olc::vf2d(pos) / olc::vf2d(vWindowSize - (vViewPos * 2)) * GetScreen().Size()) + .clamp({ 0.0f, 0.0f }, olc::vf2d(GetScreen().Size()-1))); return true; } @@ -387,7 +387,7 @@ namespace olc // Create Primary olc::Image - aka "The Screen" olc::ImageConfig cfg; cfg.MSAA = config.bAntiAliasMainScreen; - CreateImage(GetDefaultImage(), config.vScreenSize, cfg); + CreateImage(GetScreen(), config.vScreenSize, cfg); // Initialise "Classic" Font System olc::pgeguts::CreateClassicFont(this); @@ -395,7 +395,7 @@ namespace olc // Prepare Draw2D system draw.SetGPU(gpu.get()); gpu->ApplyDefaultShader(); - draw.SetTarget(GetDefaultImage()); + draw.SetTarget(GetScreen()); // User Create GOOOOOOOOO!!!! if (!OnUserCreate()) @@ -408,7 +408,7 @@ namespace olc draw.ProcessGPUTasks(); // Set to known default state - draw.SetTarget(GetDefaultImage()); + draw.SetTarget(GetScreen()); draw.WorldReset(); gpu->ApplyDefaultShader(); diff --git a/dev/src/core.h b/dev/src/core.h index ea46277c..0dbec83f 100644 --- a/dev/src/core.h +++ b/dev/src/core.h @@ -94,7 +94,7 @@ namespace olc public: // Returns the image that represents the primary drawing surface - olc::Image& GetDefaultImage(); + olc::Image& GetScreen(); olc::Draw2D& GetDraw(); // Input devices are handled by a regular olc::Window, but for convenience... diff --git a/dev/tests/test_mh.cpp b/dev/tests/test_mh.cpp index b1b4c299..3926cfe8 100644 --- a/dev/tests/test_mh.cpp +++ b/dev/tests/test_mh.cpp @@ -19,7 +19,7 @@ class SecondWindow : public olc::PGEWindow fTotalTime += fElapsedTime; draw.Clear(olc::Colour::RED); - draw.WorldRotate(fTotalTime, GetDefaultImage().Size() / 2); + draw.WorldRotate(fTotalTime, GetScreen().Size() / 2); draw.FilledRect({ 10,10 }, { 20,20 }, olc::Colour::BLUE); return true; } @@ -125,7 +125,7 @@ class Example : public olc::PixelGameEngine vecLogos.resize(x); for (auto& a : vecLogos) { - a.pos = olc::vf2d(float(rand() % GetDefaultImage().Size().x), float(rand() % GetDefaultImage().Size().y)); + a.pos = olc::vf2d(float(rand() % GetScreen().Size().x), float(rand() % GetScreen().Size().y)); //a.vel = olc::vf2d(rand() % 100 - 50, rand() % 100 - 50); a.angvel = 1.1f; } @@ -247,7 +247,7 @@ class Example : public olc::PixelGameEngine imLogo);*/ - draw.SetTarget(GetDefaultImage()); + draw.SetTarget(GetScreen()); draw.Clear(olc::Colour::CYAN); /*draw.Triangle(vecTestPoints[0], vecTestPoints[1], vecTestPoints[2], diff --git a/examples/olcPGE3_AntiAliasing.cpp b/examples/olcPGE3_AntiAliasing.cpp index 9202a595..1e361de3 100644 --- a/examples/olcPGE3_AntiAliasing.cpp +++ b/examples/olcPGE3_AntiAliasing.cpp @@ -34,7 +34,7 @@ class Example_AntiAliasing : public olc::PixelGameEngine { // Create an image that uses anti-aliasing and is half // teh width of the screen - CreateImage(imgAntiAliased, GetDefaultImage().Size(), + CreateImage(imgAntiAliased, GetScreen().Size(), olc::ImageConfig{ .MSAA=true, .MSAASamples=16 }); // There is a global config that sets the default number @@ -57,7 +57,7 @@ class Example_AntiAliasing : public olc::PixelGameEngine auto DrawFan = [&]() { // Rotating gradient lines - olc::vf2d p1 = GetDefaultImage().Size() / 2.0f; + olc::vf2d p1 = GetScreen().Size() / 2.0f; olc::vf2d p2 = olc::vf2d{ std::cos(fTotalTime), std::sin(fTotalTime) } * 300.0f; draw.Line(p1, olc::Colour::CYAN, p1 + p2, olc::Colour::MAGENTA); draw.Line(p1, olc::Colour::CYAN, p1 - p2, olc::Colour::MAGENTA); @@ -104,7 +104,7 @@ class Example_AntiAliasing : public olc::PixelGameEngine draw.WorldReset(); // Draw to normal buffer - draw.SetTarget(GetDefaultImage()); + draw.SetTarget(GetScreen()); draw.Clear(olc::Colour::BLACK); DrawFan(); draw.WorldReset(); diff --git a/examples/olcPGE3_BatchesOfFills.cpp b/examples/olcPGE3_BatchesOfFills.cpp index 4762d00b..9c7eed2b 100644 --- a/examples/olcPGE3_BatchesOfFills.cpp +++ b/examples/olcPGE3_BatchesOfFills.cpp @@ -39,8 +39,8 @@ class Example_BatchesOfFills : public olc::PixelGameEngine for (int i = 0; i < 20; i++) { vecBubblePos.push_back({ - float((rand() % (GetDefaultImage().Size().x - 64)) + 32), - float((rand() % (GetDefaultImage().Size().y - 64)) + 32) }); + float((rand() % (GetScreen().Size().x - 64)) + 32), + float((rand() % (GetScreen().Size().y - 64)) + 32) }); vecBubbleVel.push_back({ (float(rand() % 2000) - 1000.0f) / 100.0f, @@ -118,22 +118,22 @@ class Example_BatchesOfFills : public olc::PixelGameEngine vecBubblePos[i] += vecBubbleVel[i] * fElapsedTime * 10.0f; // Bounce off walls - if (vecBubblePos[i].x < 32.0f || vecBubblePos[i].x > float(GetDefaultImage().Size().x - 32)) + if (vecBubblePos[i].x < 32.0f || vecBubblePos[i].x > float(GetScreen().Size().x - 32)) { if (vecBubbleVel[i].x < 0) vecBubblePos[i].x = 32.0f; else - vecBubblePos[i].x = float(GetDefaultImage().Size().x - 32); + vecBubblePos[i].x = float(GetScreen().Size().x - 32); vecBubbleVel[i].x = -vecBubbleVel[i].x; } - if (vecBubblePos[i].y < 32.0f || vecBubblePos[i].y > float(GetDefaultImage().Size().y - 32)) + if (vecBubblePos[i].y < 32.0f || vecBubblePos[i].y > float(GetScreen().Size().y - 32)) { if (vecBubbleVel[i].y < 0) vecBubblePos[i].y = 32.0f; else - vecBubblePos[i].y = float(GetDefaultImage().Size().y - 32); + vecBubblePos[i].y = float(GetScreen().Size().y - 32); vecBubbleVel[i].y = -vecBubbleVel[i].y; } diff --git a/examples/olcPGE3_CirclesEllipses.cpp b/examples/olcPGE3_CirclesEllipses.cpp index eada4595..6992475b 100644 --- a/examples/olcPGE3_CirclesEllipses.cpp +++ b/examples/olcPGE3_CirclesEllipses.cpp @@ -39,8 +39,8 @@ class Example_RoundThings : public olc::PixelGameEngine for (int i = 0; i < 20; i++) { vecBubblePos.push_back({ - float((rand() % (GetDefaultImage().Size().x - 64)) + 32), - float((rand() % (GetDefaultImage().Size().y - 64)) + 32)}); + float((rand() % (GetScreen().Size().x - 64)) + 32), + float((rand() % (GetScreen().Size().y - 64)) + 32)}); vecBubbleVel.push_back({ (float(rand() % 2000) - 1000.0f) / 100.0f, @@ -112,22 +112,22 @@ class Example_RoundThings : public olc::PixelGameEngine vecBubblePos[i] += vecBubbleVel[i] * fElapsedTime * 10.0f; // Bounce off walls - if (vecBubblePos[i].x < 32.0f || vecBubblePos[i].x > float(GetDefaultImage().Size().x - 32)) + if (vecBubblePos[i].x < 32.0f || vecBubblePos[i].x > float(GetScreen().Size().x - 32)) { if(vecBubbleVel[i].x < 0) vecBubblePos[i].x = 32.0f; else - vecBubblePos[i].x = float(GetDefaultImage().Size().x - 32); + vecBubblePos[i].x = float(GetScreen().Size().x - 32); vecBubbleVel[i].x = -vecBubbleVel[i].x; } - if (vecBubblePos[i].y < 32.0f || vecBubblePos[i].y > float(GetDefaultImage().Size().y - 32)) + if (vecBubblePos[i].y < 32.0f || vecBubblePos[i].y > float(GetScreen().Size().y - 32)) { if (vecBubbleVel[i].y < 0) vecBubblePos[i].y = 32.0f; else - vecBubblePos[i].y = float(GetDefaultImage().Size().y - 32); + vecBubblePos[i].y = float(GetScreen().Size().y - 32); vecBubbleVel[i].y = -vecBubbleVel[i].y; } diff --git a/examples/olcPGE3_PixelShaders.cpp b/examples/olcPGE3_PixelShaders.cpp index 8d5a66d2..1ceff755 100644 --- a/examples/olcPGE3_PixelShaders.cpp +++ b/examples/olcPGE3_PixelShaders.cpp @@ -133,7 +133,7 @@ class Example_CustomPixelShader : public olc::PixelGameEngine shaderExample.CreateUniform("amplitude"); // Create off-screen image to draw to - CreateImage(imgWithoutFX, GetDefaultImage().Size()); + CreateImage(imgWithoutFX, GetScreen().Size()); // Load a small image to draw for fun CreateImageFromFile(imgMini, "./assets/minsanity_texture.png"); @@ -184,7 +184,7 @@ class Example_CustomPixelShader : public olc::PixelGameEngine draw.Image(imgMini, mouse.GetPosition()); // Copy image to screen with new shader - draw.SetTarget(GetDefaultImage()); + draw.SetTarget(GetScreen()); // Set the custom shader draw.SetShader(shaderExample); diff --git a/examples/olcPGE3_Shockwave.cpp b/examples/olcPGE3_Shockwave.cpp index 255dfc58..45e66304 100644 --- a/examples/olcPGE3_Shockwave.cpp +++ b/examples/olcPGE3_Shockwave.cpp @@ -121,7 +121,7 @@ class Example_Shockwave : public olc::PixelGameEngine shaderExample.CreateUniform("sw_radius"); // Create off-screen image to draw to - CreateImage(imgWithoutFX, GetDefaultImage().Size()); + CreateImage(imgWithoutFX, GetScreen().Size()); // Load a fake game scene to demonstrate effect on CreateImageFromFile(imgGameScene, "./assets/gamescene.png"); @@ -156,7 +156,7 @@ class Example_Shockwave : public olc::PixelGameEngine // Copy image to screen with new shader - draw.SetTarget(GetDefaultImage()); + draw.SetTarget(GetScreen()); // Set the custom shader draw.SetShader(shaderExample); diff --git a/examples/olcPGE3_TextBasics.cpp b/examples/olcPGE3_TextBasics.cpp index 9ede7bca..4602047e 100644 --- a/examples/olcPGE3_TextBasics.cpp +++ b/examples/olcPGE3_TextBasics.cpp @@ -58,7 +58,7 @@ class Example_Text : public olc::PixelGameEngine // Determine how many characters fit on the screen olc::vf2d vSizeOfChar = draw.GetTextSize("A"); - olc::vf2d nVisibleChars = GetDefaultImage().Size() / vSizeOfChar; + olc::vf2d nVisibleChars = GetScreen().Size() / vSizeOfChar; // Update ticker fTickerTime += fElapsedTime; diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 78991046..a41bd41a 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -3231,7 +3231,7 @@ namespace olc public: // Returns the image that represents the primary drawing surface - olc::Image& GetDefaultImage(); + olc::Image& GetScreen(); olc::Draw2D& GetDraw(); // Input devices are handled by a regular olc::Window, but for convenience... @@ -15097,7 +15097,7 @@ namespace olc { //pRenderer->RetargetDevice(pHost->GetHostWindowDescriptor(this)); pRenderer->PrepareWindowTarget(pHost->GetHostWindowDescriptor(this)); - CreateImage(GetDefaultImage(), vScreenSize); + CreateImage(GetScreen(), vScreenSize); SetWindowSize(vScreenSize * vPixelSize); // Assume 1:1 Relationship for now @@ -15129,7 +15129,7 @@ namespace olc keyboard.UpdateState(); draw.SetGPU(pRenderer); - draw.SetTarget(GetDefaultImage()); + draw.SetTarget(GetScreen()); pRenderer->DisplayPrepare(fElapsedTime, fTotalElapsedTime); @@ -15157,9 +15157,9 @@ namespace olc // Finialise any outstanding tasks draw.ProcessGPUTasks(); - if (GetDefaultImage().GetConfig().MSAA) + if (GetScreen().GetConfig().MSAA) { - pRenderer->ResolveMSAA(uint32_t(GetDefaultImage().GetGPUID())); + pRenderer->ResolveMSAA(uint32_t(GetScreen().GetGPUID())); } draw.ResetShader(); @@ -15174,7 +15174,7 @@ namespace olc { // Set the viewport to maintain the aspect ratio of GetDefaultImage() and maximise to // fit within the window client area - float fAspectScreen = float(GetDefaultImage().Size().x) / float(GetDefaultImage().Size().y); + float fAspectScreen = float(GetScreen().Size().x) / float(GetScreen().Size().y); vViewSize.x = (int32_t)vWindowSize.x; vViewSize.y = (int32_t)((float)vViewSize.x / fAspectScreen); @@ -15200,7 +15200,7 @@ namespace olc // Present final composite pRenderer->SetViewport(vViewPos, vViewSize); pRenderer->ClearViewport(config.colClear, true, true); - draw.ImageRect(GetDefaultImage().flipV(), { 0.0,0.0 }, vViewSize); + draw.ImageRect(GetScreen().flipV(), { 0.0,0.0 }, vViewSize); draw.ProcessGPUTasks(); // Update Window's primary surface @@ -15286,7 +15286,7 @@ namespace olc pImageLoader = imload; } - olc::Image& PGEWindow::GetDefaultImage() + olc::Image& PGEWindow::GetScreen() { return imgPrimary; } @@ -15308,7 +15308,7 @@ namespace olc const olc::vi2d& PGEWindow::ScreenSize() { - return GetDefaultImage().Size(); + return GetScreen().Size(); } bool PGEWindow::olc_OnMouseMove(const olc::vi2d& vMousePos) @@ -15322,8 +15322,8 @@ namespace olc // Scale mouse into view coordinates mouse.SetPosition( - (olc::vf2d(pos) / olc::vf2d(vWindowSize - (vViewPos * 2)) * GetDefaultImage().Size()) - .clamp({ 0.0f, 0.0f }, olc::vf2d(GetDefaultImage().Size()-1))); + (olc::vf2d(pos) / olc::vf2d(vWindowSize - (vViewPos * 2)) * GetScreen().Size()) + .clamp({ 0.0f, 0.0f }, olc::vf2d(GetScreen().Size()-1))); return true; } @@ -15430,7 +15430,7 @@ namespace olc // Create Primary olc::Image - aka "The Screen" olc::ImageConfig cfg; cfg.MSAA = config.bAntiAliasMainScreen; - CreateImage(GetDefaultImage(), config.vScreenSize, cfg); + CreateImage(GetScreen(), config.vScreenSize, cfg); // Initialise "Classic" Font System olc::pgeguts::CreateClassicFont(this); @@ -15438,7 +15438,7 @@ namespace olc // Prepare Draw2D system draw.SetGPU(gpu.get()); gpu->ApplyDefaultShader(); - draw.SetTarget(GetDefaultImage()); + draw.SetTarget(GetScreen()); // User Create GOOOOOOOOO!!!! if (!OnUserCreate()) @@ -15451,7 +15451,7 @@ namespace olc draw.ProcessGPUTasks(); // Set to known default state - draw.SetTarget(GetDefaultImage()); + draw.SetTarget(GetScreen()); draw.WorldReset(); gpu->ApplyDefaultShader(); From 7be2f9419af27ee25da7fb8713ec62c6e85a2a63 Mon Sep 17 00:00:00 2001 From: DCubix Date: Sat, 7 Feb 2026 14:33:19 -0400 Subject: [PATCH 15/58] Android host no longer needs AssetManager passed to it --- dev/src/core.cpp | 6 ------ olcPixelGameEngine3.h | 6 ------ 2 files changed, 12 deletions(-) diff --git a/dev/src/core.cpp b/dev/src/core.cpp index aa4d03e9..a4d33831 100644 --- a/dev/src/core.cpp +++ b/dev/src/core.cpp @@ -319,13 +319,7 @@ namespace olc // DEVS!! Please don't merge these just yet // Initialise ImageLoader Interface -#if OLC_HOST == OLC_HOST_ANDROID - imageloader = std::make_unique( - olc::host::Host_Android::androidApp->activity->assetManager - ); -#else imageloader = std::make_unique(); -#endif // Allow host to prepare itself return host->OnApplicationStart(this); diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index a41bd41a..f0733f93 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -15362,13 +15362,7 @@ namespace olc // DEVS!! Please don't merge these just yet // Initialise ImageLoader Interface -#if OLC_HOST == OLC_HOST_ANDROID - imageloader = std::make_unique( - olc::host::Host_Android::androidApp->activity->assetManager - ); -#else imageloader = std::make_unique(); -#endif // Allow host to prepare itself return host->OnApplicationStart(this); From 5b63a4095b2af729207f76664972a0d15e3aae21 Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Sun, 8 Feb 2026 00:37:15 +0000 Subject: [PATCH 16/58] Updated Camera2D demo - it also has a Pan&Zoom Utility method --- .../olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj | 9 +- .../olcPGE3_BuildSH.vcxproj.filters | 3 + examples/olcPGE3_Camera2D_Tiles.cpp | 224 ++++++++++++++++++ utilities/olcUTIL3_Camera2D.h | 113 ++++++++- 4 files changed, 341 insertions(+), 8 deletions(-) create mode 100644 examples/olcPGE3_Camera2D_Tiles.cpp diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj index 71ccc542..31e1a4e8 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj @@ -26,10 +26,10 @@ true - false - false - false - false + true + true + true + true true @@ -127,6 +127,7 @@ true true + true true diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters index 521cf969..e13a0853 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters @@ -75,6 +75,9 @@ Source Files + + Source Files + diff --git a/examples/olcPGE3_Camera2D_Tiles.cpp b/examples/olcPGE3_Camera2D_Tiles.cpp new file mode 100644 index 00000000..8f53da8d --- /dev/null +++ b/examples/olcPGE3_Camera2D_Tiles.cpp @@ -0,0 +1,224 @@ +/* + olc::PixelGameEngine3 Example - Camera2D - Tiles & Panning & Zooming + + Demonstration of teh Camera2D Utility class, which provides various + modes of camera movement and tracking. This example uses a simple tile + map world to show how the camera can be used to track a point in the + world, and how the different camera modes affect the way the camera + moves in response to the tracked point. + + The user can control the tracked point with WASD keys, and switch between + "free roam" mode (where the camera does not track the point) and "play" mode + (where the camera tracks the point) with the TAB key. The user can also switch + between the different camera modes with the 1, 2, 3, 4, 5 keys. + + Licenced under the OLC-3 License +*/ + +// Define OLC_PGE3_APPLICATION to include the implementation of +// the Pixel Game Engine as part of this translation unit +#define OLC_PGE3_APPLICATION +#include "../olcPixelGameEngine3.h" + +// Include the Camera2D Utility - AFTER including the PixelGameEngine +// as it relies on some of its types! +#include "../utilities/olcUTIL3_Camera2D.h" + + +// Example application demonstrating a Camera in 2D. This class +// overrides the olc::PixelGameEngine base class by implementing +// the OnUserCreate() and OnUserUpdate() functions +class Example_Camera2D_Tiles : public olc::PixelGameEngine +{ +public: + Example_Camera2D_Tiles() + { + + } + +protected: + + // Camera utility class + olc::utils::Camera2D camera; + + // User can use mouse to pan & zoom around to show how + // the camera works without needing to "look through it" + bool bFreeRoam = false; + + // World parameters + olc::vi2d vWorldSize = { 80, 75 }; + std::vector vecWorldMap; + + // Size of each tile in the world, in pixels + olc::vf2d vTileSize = { 32, 32 }; + // The point in the world we want the camera to track + olc::vf2d vTrackedPoint; + + +public: + // Called once at the start, so create things here + bool OnUserCreate() override + { + vTrackedPoint = { 20.0f, 20.0f }; + // Create the camera, providing it with the screen size, tile size and tracked point + // Since our tile size is 32x32, the transform will be configured such that 1 unit in + // the world is 32 pixels on the screen. This is just useful for any tile based games, + // as it allows you to work in "tile space" rather than pixel space + camera = olc::utils::Camera2D(ScreenSize(), vTileSize, vTrackedPoint); + + // Configure Camera + + // Set the point in the world we want the camera to track. It will follow this point + // and adjust the world transform according to teh camera mode + camera.SetTarget(vTrackedPoint); + camera.SetMode(olc::utils::Camera2D::Mode::Simple); + + // Set the world boundary, which is used in some camera modes to prevent the camera from + // showing areas outside of the world. In this example, the world boundary is set to + // the size of our tile map, so that we dont see outside of it. Note that the world boundary + // is in world units, so we dont need to multiply by tile size, the camera will take care + // of that in the world transform + camera.SetWorldBoundary({ 0.0f, 0.0f }, vWorldSize); + camera.EnableWorldBoundary(true); + + // Create "tile map" world with just two tile types + vecWorldMap.resize(vWorldSize.area()); + for (int i = 0; i < vecWorldMap.size(); i++) + vecWorldMap[i] = ((rand() % 20) == 1) ? 1 : 0; + + return true; + } + + // Called every frame, so update things here + bool OnUserUpdate(float fElapsedTime) override + { + // Clear whole screen + draw.Clear(olc::Colour::VERY_DARK_BLUE); + + // Handle player "physics" in response to key presses + olc::vf2d vVel = { 0.0f, 0.0f }; + if (keyboard.GetKey(olc::Key::W).bHeld) vVel = vVel + olc::vf2d{0, -1}; + if (keyboard.GetKey(olc::Key::S).bHeld) vVel = vVel + olc::vf2d{0, +1}; + if (keyboard.GetKey(olc::Key::A).bHeld) vVel = vVel + olc::vf2d{-1, 0}; + if (keyboard.GetKey(olc::Key::D).bHeld) vVel = vVel + olc::vf2d{+1, 0}; + vTrackedPoint += vVel * 8.0f * fElapsedTime; + + // Switch between "free roam" and "play" mode with TAB key + if (keyboard.GetKey(olc::Key::TAB).bPressed) + { + bFreeRoam = !bFreeRoam; + } + + // Switch camera mode in operation + if (keyboard.GetKey(olc::Key::K1).bPressed) + camera.SetMode(olc::utils::Camera2D::Mode::Simple); + if (keyboard.GetKey(olc::Key::K2).bPressed) + camera.SetMode(olc::utils::Camera2D::Mode::EdgeMove); + if (keyboard.GetKey(olc::Key::K3).bPressed) + camera.SetMode(olc::utils::Camera2D::Mode::LazyFollow); + if (keyboard.GetKey(olc::Key::K4).bPressed) + camera.SetMode(olc::utils::Camera2D::Mode::FixedScreens); + if (keyboard.GetKey(olc::Key::K5).bPressed) + camera.SetMode(olc::utils::Camera2D::Mode::SlideScreens); + + // Update the camera, if teh tracked object remains visible, + // true is returned + bool bOnScreen = false; + + if (bFreeRoam) + { + // In free roam mode, we ignore the tracked point and instead + // allow the user to pan and zoom the camera with the mouse + camera.HandlePanAndZoom(mouse); + // Update camera, but dont actually change the world transform + bOnScreen = camera.Update(fElapsedTime, false); + } + else + // In play mode, we update the camera as normal, which will cause it to + // follow the tracked point according to the camera mode + bOnScreen = camera.Update(fElapsedTime); + + // Set the world transform for the camera, so that all drawing operations + draw.SetWorldTransform(camera.GetWorldTransform()); + + // Render "tile map", by getting visible tiles + + // If we never change scale we can just use the view parameters + // directly... + //olc::vi2d vTileCount = camera.GetViewSize().ceil() + 1; + //olc::vf2d vTileOffset = camera.GetViewPosition().floor(); + + // ... but if we allow free zooming, then we need to convert + // screen coordinates to world coordinates to get the correct + // tile offsets and counts + olc::vi2d vTileOffset = draw.ScreenToWorld({ 0,0 }).floor(); + olc::vi2d vTileCount = draw.ScreenToWorld(ScreenSize()).ceil() - vTileOffset; + + // Clamp to ensure we stay in bounds of our world map + olc::vi2d vTileTL = vTileOffset.max({ 0,0 }); + olc::vi2d vTileBR = (vTileOffset + vTileCount).min(vWorldSize); + olc::vi2d vTile; + + // Then looping through them and drawing them + auto batch = draw.CreateFilledBatch(); + + for (vTile.y = vTileTL.y; vTile.y < vTileBR.y; vTile.y++) + for (vTile.x = vTileTL.x; vTile.x < vTileBR.x; vTile.x++) + { + // 2D -> 1D index conversion for our world map + int idx = vTile.y * vWorldSize.x + vTile.x; + + if (vecWorldMap[idx] == 0) + draw.FilledRect(batch, vTile, { 1.0f, 1.0f }, olc::Colour::DARK_GREEN); + + if (vecWorldMap[idx] == 1) + draw.FilledRect(batch, vTile, { 1.0f, 1.0f }, olc::Colour::TANGERINE); + } + + // Draw the batch of tiles + draw.Batch(batch); + + // Draw the "player" as a 1x1 cell + draw.FilledRect(vTrackedPoint - olc::vf2d(0.5f, 0.5f), { 1.0f, 1.0f }, olc::Colour::BLUE); + + // Overlay with information + if (bFreeRoam) + { + draw.FilledRect(camera.GetViewPosition(), camera.GetViewSize(), olc::PixelF(1.0f, 0.0f, 0.0f, 0.5f)); + } + + + // Reset world transform to draw info in screen space + draw.WorldReset(); + + if (bFreeRoam) + draw.StringProp({ 2, 2 }, "TAB: Free Mode, M-Btn to Pan & Zoom", olc::Colour::YELLOW); + else + draw.StringProp({ 2,2 }, "TAB: Play Mode", olc::Colour::YELLOW); + + draw.StringProp({ 2,12 }, "WASD : Move", olc::Colour::YELLOW); + draw.StringProp({ 2,22 }, "CAMERA: 1) Simple 2) EdgeMove 3) LazyFollow 4) Screens 5) Slides", olc::Colour::YELLOW); + draw.StringProp({ 2,42 }, vTileOffset.str(), olc::Colour::YELLOW); + + // Successful frame + return true; + } +}; + + +// Main entry point for the application +int main() +{ + // Construct demo application + Example_Camera2D_Tiles demo; + + // Create "screen" of 512x480 "pixels" + // with a pixel size of 2x2 actual screen pixels + if (demo.Construct({ 512, 480 }, { 2, 2 })) + { + // Start the application + demo.Start(); + } + + return 0; +} \ No newline at end of file diff --git a/utilities/olcUTIL3_Camera2D.h b/utilities/olcUTIL3_Camera2D.h index 1ff9ca9e..50916a85 100644 --- a/utilities/olcUTIL3_Camera2D.h +++ b/utilities/olcUTIL3_Camera2D.h @@ -59,7 +59,7 @@ #pragma once -#include +//#include namespace olc::utils { @@ -79,10 +79,14 @@ namespace olc::utils inline Camera2D() : m_pTarget(&m_vLocalTarget) {} // Construct a camera with a viewable area size, and an optional starting position - inline Camera2D(const olc::vf2d& vViewSize, const olc::vf2d& vViewPos = { 0.0f, 0.0f }) : m_pTarget(&m_vLocalTarget) + inline Camera2D(const olc::vf2d& vScreenSize, const olc::vf2d& vViewScale = { 1.0f, 1.0f }, const olc::vf2d & vViewPos = { 0.0f, 0.0f }) : m_pTarget(&m_vLocalTarget) { - m_vViewSize = vViewSize; + m_vViewSize = vScreenSize / vViewScale; m_vViewPos = vViewPos; + m_vViewScale = vViewScale; + + transform.scale(vViewScale); + transform.translate(vViewPos); } // Set the operational mode of this camera @@ -191,7 +195,7 @@ namespace olc::utils // Update camera, animating if necessary, obeying world boundary rules // returns true if target is visible - inline virtual bool Update(const float fElapsedTime) + inline virtual bool Update(const float fElapsedTime, const bool bUpdateTransform = true) { switch (m_nMode) { @@ -240,10 +244,102 @@ namespace olc::utils m_vViewPos = m_vViewPos.max(m_vWorldBoundaryPos).min(m_vWorldBoundaryPos + m_vWorldBoundarySize - m_vViewSize); } + if (bUpdateTransform) + { + transform.scale(m_vViewScale); + transform.translate(m_vViewPos * -m_vViewScale); + } + + return GetTarget().x >= m_vViewPos.x && GetTarget().x < (m_vViewPos.x + m_vViewSize.x) && GetTarget().y >= m_vViewPos.y && GetTarget().y < (m_vViewPos.y + m_vViewSize.y); } + // Get the world transform for this camera + inline const olc::tf2d& GetWorldTransform() const + { + return transform; + } + + // Get the visible area of the camera in world space + inline const olc::vf2d GetVisibleArea() + { + return m_vViewSize; + } + + + // Handle mouse input for panning and zooming the camera + inline void HandlePanAndZoom(const olc::hw::Mouse& mouse, const int32_t nButtonPan = 2) + { + // Enter panning mode if middle mouse button pressed (and held) + if (mouse.GetButton(nButtonPan).bPressed) + { + bPanning = true; + // Cache mouse position at start of drag, so we can create a + // mouse position delta + vLastMouseScreenPos = mouse.GetPosition(); + } + + // Exit panning mode if middle mouse button released + if (mouse.GetButton(nButtonPan).bReleased) + { + bPanning = false; + } + + // Get current mouse position + olc::vf2d vMousePos = mouse.GetPosition(); + + // If we are panning, update translation component of transform + if (bPanning) + { + // Update translation by the mouse delta. Note that we round the mouse + // to screen coordinates to avoid sub-pixel jittering. This is optional. + transform.translate((transform.translate() + + olc::vf2d(vMousePos.x - vLastMouseScreenPos.x, vMousePos.y - vLastMouseScreenPos.y)).round()); + } + + + // Handle zooming and rotation. This is a bit clumsy because we are + // using the mouse wheel for both. In a real application you would + // probably want to use keyboard modifiers to distinguish the two. + if (mouse.GetWheel() != 0) + { + // Cache the mouse position before transformation + auto posWorldBeforeRotate = transform.inverse(vLastMouseScreenPos); + + // If right mouse button held, we are rotating + //if (mouse.GetButton(1).bHeld) + //{ + // // Adjust rotation depending on wheel direction + // if (mouse.GetWheel() > 0) + // transform.rotate(transform.rotate() + 0.1f, posWorldBeforeRotate); + // else + // transform.rotate(transform.rotate() - 0.1f, posWorldBeforeRotate); + //} + //else + //{ + // Adjust scale depending on wheel direction + if (mouse.GetWheel() > 0) + transform.scale(transform.scale() * 1.1f); + else + transform.scale(transform.scale() * 0.9f); + //} + + // Get the new screen position of the point under the mouse + auto posScreenAfterRotate = transform.forward(posWorldBeforeRotate); + + // Adjust translation to keep mouse position stable + auto posScreenDisplacement = vLastMouseScreenPos - posScreenAfterRotate; + + // Apply adjustment + transform.translate(transform.translate() + posScreenDisplacement); + } + + // Cache last mouse position + vLastMouseScreenPos = vMousePos; + } + + protected: // Position of camera focus point in the world olc::vf2d m_vPosition; @@ -251,6 +347,8 @@ namespace olc::utils olc::vf2d m_vViewSize; // Top left coordinate of camera viewing area olc::vf2d m_vViewPos; + // Scaling within the camera view + olc::vf2d m_vViewScale = { 1.0f, 1.0f }; // Camera movement mode Mode m_nMode = Mode::Simple; @@ -267,5 +365,12 @@ namespace olc::utils olc::vf2d m_vEdgeTriggerDistance = { 1.0f, 1.0f }; float m_fLazyFollowRate = 4.0f; olc::vi2d m_vScreenSize = { 16,15 }; + + // Pan and zoom support + bool bPanning = false; + olc::vf2d vLastMouseScreenPos = { 0.0f, 0.0f }; + + // The final "World" transform + olc::tf2d transform; }; } \ No newline at end of file From 67103fd7af238cc9c1d7eeefbc4b9f72366ddd16 Mon Sep 17 00:00:00 2001 From: dandistine <34634876+dandistine@users.noreply.github.com> Date: Sat, 7 Feb 2026 18:48:52 -0600 Subject: [PATCH 17/58] Add fish --- examples/olcPGE3_Fish.cpp | 447 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 447 insertions(+) create mode 100644 examples/olcPGE3_Fish.cpp diff --git a/examples/olcPGE3_Fish.cpp b/examples/olcPGE3_Fish.cpp new file mode 100644 index 00000000..7b86eb18 --- /dev/null +++ b/examples/olcPGE3_Fish.cpp @@ -0,0 +1,447 @@ +/* + olc::PixelGameEngine3 Example - Procedural Fish + + Original Author: dandistine + + A small pond of procedural fish. + + Based on: + https://www.youtube.com/watch?v=qlfh_rv6khY + + Licenced under the OLC-3 License +*/ + + +// Define OLC_PGE3_APPLICATION to include the implementation of +// the Pixel Game Engine as part of this translation unit +#define OLC_PGE3_APPLICATION +#include "../olcPixelGameEngine3.h" + +#include + +struct Segment { + olc::vf2d position; + float angle {0.0f}; + float size {10.0f}; + float distance {3.0f}; + olc::Pixel color {olc::Colour::WHITE}; +}; + +float rand_float(float min, float max) { + return min + static_cast(rand()) / (static_cast(RAND_MAX/(max - min))); +} + +float rand_int(int min, int max) { + return min + rand() % (max - min + 1); +} + +class Fish { + std::vector segments; + std::vector head_points; + float max_speed = 40.0f; + olc::Pixel outline_color = olc::Colour::BLACK; + olc::Pixel fin_color = olc::Pixel{143, 73, 31}; + olc::Pixel fin_tip_color = olc::Pixel{217, 127, 13, 170}; + + // An overly complicated function to determine the segment size + // This was found largely by playing with desmos until something looked right + // "form" lerps between a simple and more complex shape to allow some variation + float SegmentSize(int i, int total_segments, float scale, float form = 1.0f) { + float x = i; + const float sqrt_3 = std::sqrt(3.0f); + + // A basic shape + float val_1 = scale - (scale * i / (total_segments - 1)); + + // A shape with a slightly smaller head and tapering tail + float f = scale - (scale * (x - 2) / (total_segments - 1)); + float g = 1 + (sqrt_3 + 1) * (std::pow(((x + 1) / total_segments) - 1, 3)) +sqrt_3 * std::pow(((x + 1) / total_segments) - 1, 2); + float j = g * f * f / (0.5 * total_segments); + float val_2 = j + (total_segments - x - 1) / scale; + + return std::lerp(val_1, val_2, form); + } + + public: + Fish(olc::vf2d position, int num_segments, float total_length, float form = .50f, float scale = 1.0f) { + olc::vf2d next_position {10.0f, 0.0f}; + + segments.resize(num_segments); + // segment_angles.resize(num_segments, 0.0f); + float size = 11; + float distance = total_length / num_segments; + + for(int i = 0; i < num_segments; i++) { + float s = size - (size * i / (num_segments - 1)); + segments[i].size = SegmentSize(i, num_segments, size, form) * 1.2 * scale; + segments[i].position = position + i * next_position; + segments[i].angle = 0.0f; + segments[i].distance = distance; + } + + // Calculate the head points for drawing the head of the fish + const int head_points_count = 10; + // Angles for the top and bottom of the head + const float start_angle = -std::numbers::pi_v / 2.0f; + const float end_angle = std::numbers::pi_v / 2.0f; + const float head_size = segments[0].size; + + for(int i = 0; i < head_points_count; i++) { + float angle = start_angle + (end_angle - start_angle) * i / (head_points_count - 1); + head_points.emplace_back(std::cos(angle) * head_size * (1 + 0.3*(fabs(start_angle) - fabs(angle))), std::sin(angle) * head_size); + } + + } + + // Sum up the "total curve" of the fish. This is useful for the tail + float TotalCurve() { + float total_curve = 0.0f; + for(int i = 0; i < segments.size() - 1; i++) { + float a = segments[i].angle - segments[i + 1].angle; + if(a > std::numbers::pi_v) { + a -= 2 * std::numbers::pi_v; + } else if(a < -std::numbers::pi_v) { + a += 2 * std::numbers::pi_v; + } + total_curve += a; + } + return total_curve; + } + + void Draw(olc::Draw2D& pge) { + const auto world_transform = pge.GetWorldTransform(); + const auto origin = olc::vf2d{0.0f, 0.0f}; + const auto half_pi = std::numbers::pi_v / 2.0f; + + DrawFin(pge, segments.size() * 0.18, 1.0f); + DrawFin(pge, segments.size() * 0.7, 2.0f); + + { + // Draw a half circle for the head + const auto& head = segments[0]; + olc::tf2d transform; + transform.translate(head.position); + transform.rotate(head.angle); + pge.SetWorldTransform(transform); + + pge.FilledPolygon(olc::Structure::Fan, head_points, head.color); + + for(int i = 0; i < head_points.size() - 1; i++) { + pge.Line(head_points[i], head_points[i + 1], outline_color); + } + } + + pge.SetWorldTransform(world_transform); + + // Batch used for the outline of the fish + auto line_batch = pge.CreateLineBatch(); + + // Draw a bunch of quads from one segment to the next to form the body of the fish + for(int i = 0; i < segments.size() - 1; i++) { + const Segment& seg_a = segments[i]; + const Segment& seg_b = segments[i + 1]; + + float x1, y1, x2, y2; + x1 = seg_a.position.x + std::cos(seg_a.angle + half_pi) * seg_a.size; + y1 = seg_a.position.y + std::sin(seg_a.angle + half_pi) * seg_a.size; + x2 = seg_b.position.x + std::cos(seg_b.angle + half_pi) * seg_b.size; + y2 = seg_b.position.y + std::sin(seg_b.angle + half_pi) * seg_b.size; + + float x3, y3, x4, y4; + x3 = seg_a.position.x + std::cos(seg_a.angle - half_pi) * seg_a.size; + y3 = seg_a.position.y + std::sin(seg_a.angle - half_pi) * seg_a.size; + x4 = seg_b.position.x + std::cos(seg_b.angle - half_pi) * seg_b.size; + y4 = seg_b.position.y + std::sin(seg_b.angle - half_pi) * seg_b.size; + + std::vector quad_points = { + olc::vf2d(x1, y1), + olc::vf2d(x2, y2), + olc::vf2d(x4, y4), + olc::vf2d(x3, y3) + }; + + pge.FilledPolygon(olc::Structure::Fan, quad_points, seg_a.color); + + pge.Line(line_batch, olc::vf2d(x1, y1), olc::vf2d(x2, y2), outline_color); + pge.Line(line_batch, olc::vf2d(x3, y3), olc::vf2d(x4, y4), outline_color); + } + + pge.Batch(line_batch); + + DrawDorsalFin(pge, segments.size() * 0.07, segments.size() * 0.24, 2.0f); + DrawDorsalFin(pge, segments.size() * 0.6, segments.size() * 0.17, 1.0f); + DrawEyes(pge); + DrawTail(pge); + } + + // Draw a simple tail triangle + void DrawTail(olc::Draw2D& pge) { + const auto world_transform = pge.GetWorldTransform(); + const auto& tail_start = segments[segments.size() - 5]; + const auto& end = segments[segments.size() - 1]; + const auto half_pi = std::numbers::pi_v / 2.0f; + + float total_curve = TotalCurve(); + + std::vector tail_points; + + olc::tf2d transform; + transform.translate(end.position); + transform.rotate(end.angle); + + olc::vf2d p1 = tail_start.position; + p1.x += std::cos(half_pi) * tail_start.size; + p1.y += std::sin(half_pi) * tail_start.size; + olc::vf2d p2 = end.position; + olc::vf2d p3 = transform.forward(olc::vf2d{-10.0f, total_curve / 1.5f}); + + pge.SetWorldTransform(world_transform); + pge.FilledTriangle(p1, p2, p3, fin_color); + } + + // Draw some fins on the body of the fish + void DrawFin(olc::Draw2D& pge, int segment_index, float size) { + const auto world_transform = pge.GetWorldTransform(); + const auto& segment = segments[segment_index]; + const auto& prev_segment = segments[segment_index - 1]; + const auto& quarter_pi = std::numbers::pi_v / 4.0f; + const auto& third_pi = std::numbers::pi_v / 3.0f; + + olc::tf2d transform_right; + olc::tf2d transform_left; + + olc::vf2d offset = {std::cos(third_pi) * segment.size, std::sin(third_pi) * segment.size}; + olc::vf2d offset2 = {std::cos(-third_pi) * segment.size, std::sin(-third_pi) * segment.size}; + + transform_right.translate(segment.position); + transform_right.rotate(segment.angle + third_pi); + transform_left.translate(segment.position); + transform_left.rotate(segment.angle - third_pi); + + + float fin_length = segment.size; + float fin_width = segment.size * 0.5f; + olc::vf2d position = {0.0f, 0.0f}; + + olc::vf2d right_pos = transform_right.forwardRound(olc::vf2d{segment.size, 0.0f}); + olc::vf2d left_pos = transform_left.forwardRound(olc::vf2d{segment.size, 0.0f}); + + olc::tf2d transform {}; + transform.translate(right_pos); + transform.rotate(prev_segment.angle - quarter_pi); + pge.SetWorldTransform(transform); + pge.FilledEllipse({0.0f, 0.0f}, fin_length * size, fin_width * size, fin_color); + + + transform = {}; + transform.translate(left_pos); + transform.rotate(prev_segment.angle + quarter_pi); + pge.SetWorldTransform(transform); + pge.FilledEllipse({0.0f, 0.0f}, fin_length * size, fin_width * size, fin_color); + + + pge.SetWorldTransform(world_transform); + } + + // Draw the eyes on the head of the fish + void DrawEyes(olc::Draw2D& pge) { + const auto world_transform = pge.GetWorldTransform(); + const auto& head = segments[0]; + olc::tf2d transform; + transform.translate(head.position); + transform.rotate(head.angle); + pge.SetWorldTransform(transform); + + float eye_offset_x = 0; + float eye_offset_y = head.size * 0.3f; + float eye_size = head.size * 0.3f; + + // Left eye + pge.FilledCircle(head_points[2] * 0.9f, eye_size, olc::Colour::BLACK); + pge.FilledCircle(head_points[2] * 0.9f, eye_size * 0.5f, olc::Colour::WHITE); + + // Right eye + pge.FilledCircle(head_points[7] * 0.9f, eye_size, olc::Colour::BLACK); + pge.FilledCircle(head_points[7] * 0.9f, eye_size * 0.5f, olc::Colour::WHITE); + + pge.SetWorldTransform(world_transform); + } + + // Draw a fin on the back of the fish + void DrawDorsalFin(olc::Draw2D& pge, int fin_start, size_t length, float size) { + const int fin_end = fin_start + length; + const auto world_transform = pge.GetWorldTransform(); + std::vector fin_points {length * 2}; + std::vector point_colors; + point_colors.resize(fin_points.size()); + + auto line_batch = pge.CreateLineBatch(); + + for(int i = 0; i < length; i++) { + Segment& seg = segments[i + fin_start]; + olc::tf2d transform; + transform.translate(seg.position); + transform.rotate(seg.angle); + pge.SetWorldTransform(transform); + + float a = 3.0f + (i * 0.5f); + float b = 3.0f + (length - i) * 1; + float fin_scale = size * -1.0f * std::min(a, b); + + const auto& center_pos = pge.GetWorldTransform().translate(); + const auto& end_pos = pge.GetWorldTransform().forwardRound(olc::vf2d{1.0f * fin_scale, 0.0f}); + + fin_points[2 * i] = center_pos; + fin_points[2 * i + 1] = end_pos; + point_colors[2 * i] = fin_color; + point_colors[2 * i + 1] = fin_tip_color; + + pge.Line(line_batch,olc::vf2d{0.0f, 0.0f}, olc::vf2d{1.0f * fin_scale, 0.0f}, fin_color * 0.3f); + } + + pge.SetWorldTransform(world_transform); + + // Border on the fin + for(int i = 1; i < fin_points.size() - 3; i+=2) { + pge.Line(line_batch, fin_points[i], fin_points[i + 2], fin_color * 0.5f); + } + pge.Line(line_batch, fin_points[0], fin_points[1], fin_color * 0.5f); + pge.Line(line_batch, fin_points[fin_points.size() - 2], fin_points[fin_points.size() - 1], fin_color * 0.5f); + + + pge.Batch(line_batch); + pge.FilledPolygon(olc::Structure::Strip, fin_points, point_colors); + } + + void Update(float fElapsedTime, olc::vf2d target) { + auto& head = segments[0]; + olc::vf2d to_target = target - head.position; + // Rotate the head towards the target + float angle = std::atan2(to_target.y, to_target.x); + float delta = angle - head.angle; + + // Wrap the angle delta to the range [-pi, pi] + while (delta < -std::numbers::pi_v) delta += 2.0f * std::numbers::pi_v; + while (delta > std::numbers::pi_v) delta -= 2.0f * std::numbers::pi_v; + head.angle += fElapsedTime * delta; + + // Also need to constrain the overall head angle + if(head.angle > std::numbers::pi_v) { + head.angle -= 2.0f * std::numbers::pi_v; + } else if(head.angle < -std::numbers::pi_v) { + head.angle += 2.0f * std::numbers::pi_v; + } + + // Move the head forward a little bit + head.position += olc::vf2d(std::cos(head.angle), std::sin(head.angle)) * max_speed * fElapsedTime; + + // Move each segment towards the previous one + for(int i = 1; i < segments.size(); i++) { + olc::vf2d to_prev = segments[i - 1].position - segments[i].position; + segments[i].angle = std::atan2(to_prev.y, to_prev.x); + float distance = to_prev.mag(); + if(distance > 0.0f) { + olc::vf2d move = to_prev.norm() * (distance - segments[i].distance); + segments[i].position += move; + } + } + } + + bool IsPointInFish(olc::vf2d point) const { + for(const auto& seg : segments) { + if((point - seg.position).mag() < seg.size) { + return true; + } + } + return false; + } +}; + +class Example_Fish : public olc::PixelGameEngine +{ +public: + Example_Fish() + { + + } + +protected: + // We accumulate total time for some animation + float fTotalTime = 0.0f; + Fish fish{olc::vf2d(128.0f, 120.0f), 40, 80.0f, 1.0f}; + + std::vector others; + std::vector targets; +public: + // Called once at the start, so create things here + bool OnUserCreate() override + { + // Create some other fish to swim around + for(int i = 0; i < 10; i++) { + olc::vf2d pos {rand_float(0.0f, ScreenSize().x), rand_float(0.0f, ScreenSize().y)}; + others.emplace_back(pos, rand_int(25, 40), rand_float(60.0f, 100.0f), rand_float(0.5f, 1.0f), rand_float(0.5f, 1.5f)); + targets.emplace_back(rand_float(0.0f, ScreenSize().x), rand_float(0.0f, ScreenSize().y)); + } + // Nothing to do here, so return true + return true; + } + + // Called every frame, so update things here + bool OnUserUpdate(float fElapsedTime) override + { + olc::Pixel background_color {73, 220, 222}; + fTotalTime += fElapsedTime; + // Clear screen to a background color + draw.Clear(background_color); + + // Draw the targets for the other fish + for(const auto& target : targets) { + draw.FilledCircle(target, 2, olc::Colour::DARK_GREEN); + } + + // Update and draw the other fish + for(int i = 0; i < others.size(); i++) { + others[i].Update(fElapsedTime, targets[i]); + others[i].Draw(draw); + } + + // If a fish overlaps with a target, move teh target somewhere else + for(auto& target : targets) { + if(fish.IsPointInFish(target)) { + target = olc::vf2d(rand_float(0.0f, ScreenSize().x), rand_float(0.0f, ScreenSize().y)); + } else { + for(const auto& other : others) { + if(other.IsPointInFish(target)) { + target = olc::vf2d(rand_float(0.0f, ScreenSize().x), rand_float(0.0f, ScreenSize().y)); + break; + } + } + } + } + + // Update and draw the main fish last so it's on top + fish.Update(fElapsedTime, GetMouse().GetPosition()); + fish.Draw(draw); + + // Successful frame + return true; + } +}; + + +// Main entry point for the application +int main() +{ + // Construct demo application + Example_Fish demo; + + // Create "screen" of 256x240 "pixels" + // with a pixel size of 4x4 actual screen pixels + if (demo.Construct({ 256, 240 }, { 4, 4 })) + { + // Start the application + demo.Start(); + } + + return 0; +} \ No newline at end of file From 7c8b20e4f6632ea4ea4eef6323b329e6b0df2e24 Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Sun, 1 Feb 2026 22:31:15 +0000 Subject: [PATCH 18/58] Added "3D Cube" example - rudimentary 3D, mostly untested Rudimentary Cube matrix ordering seems legit its a workingcube, but a total hack how it got here leaving it there for today stupid near and far keywords added v_f2d accessors added 3d cross product construct with w = 1 default w to 1 Added v_4d and m_4d, tidied up a bit --- dev/msvc/olcPGE3.sln | 6 - dev/msvc/olcPGE3.vcxproj | 27 +- dev/msvc/olcPGE3.vcxproj.filters | 23 +- .../olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj | 14 +- .../olcPGE3_BuildSH.vcxproj.filters | 6 + dev/src/api_opengl.cpp | 20 + dev/src/api_opengl.h | 18 +- dev/src/core.cpp | 4 + dev/src/core.h | 4 + dev/src/draw2d.h | 4 + dev/src/draw3d.cpp | 267 ++++ dev/src/draw3d.h | 201 +++ dev/src/gpu_opengl33.cpp | 160 +- dev/src/gpu_opengl33.h | 4 + dev/src/gputask.h | 3 + dev/src/matrix3d.h | 2 +- dev/src/matrix4d.h | 367 +++++ dev/src/olcpge3.h | 2 + dev/src/sh_template.h | 14 + dev/src/vector4d.h | 348 +++++ dev/tests/blend.png | Bin 3493 -> 0 bytes dev/tests/olc.png | Bin 2417 -> 0 bytes dev/tests/test_main.cpp | 20 - dev/tests/test_matrices.cpp | 24 - dev/tests/test_mh.cpp | 150 +- dev/tests/test_pixels.cpp | 46 - dev/tests/test_vector2d.cpp | 18 - examples/olcPGE3_3DCube.cpp | 231 +++ olcPixelGameEngine3.h | 1350 ++++++++++++++++- 29 files changed, 3079 insertions(+), 254 deletions(-) create mode 100644 dev/src/draw3d.cpp create mode 100644 dev/src/draw3d.h create mode 100644 dev/src/matrix4d.h create mode 100644 dev/src/vector4d.h delete mode 100644 dev/tests/blend.png delete mode 100644 dev/tests/olc.png delete mode 100644 dev/tests/test_main.cpp delete mode 100644 dev/tests/test_matrices.cpp delete mode 100644 dev/tests/test_pixels.cpp delete mode 100644 dev/tests/test_vector2d.cpp create mode 100644 examples/olcPGE3_3DCube.cpp diff --git a/dev/msvc/olcPGE3.sln b/dev/msvc/olcPGE3.sln index 5490fb76..33b66c2e 100644 --- a/dev/msvc/olcPGE3.sln +++ b/dev/msvc/olcPGE3.sln @@ -7,12 +7,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "olcPGE3", "olcPGE3.vcxproj" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "olcPGE3_BuildSH", "olcPGE3_BuildSH\olcPGE3_BuildSH.vcxproj", "{227300C2-9BC8-4E66-95FC-334A009D449B}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{9462016E-9662-468F-98F6-CBA5E15D0D82}" - ProjectSection(SolutionItems) = preProject - ..\src\imload_stb_image.cpp = ..\src\imload_stb_image.cpp - ..\src\imload_stb_image.h = ..\src\imload_stb_image.h - EndProjectSection -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 diff --git a/dev/msvc/olcPGE3.vcxproj b/dev/msvc/olcPGE3.vcxproj index 8a5cee2b..44257486 100644 --- a/dev/msvc/olcPGE3.vcxproj +++ b/dev/msvc/olcPGE3.vcxproj @@ -196,6 +196,7 @@ + @@ -256,15 +257,16 @@ true true - + + @@ -278,6 +280,7 @@ + @@ -341,34 +344,12 @@ - - true - true - true - true - - - true - true - false false false false - - false - false - true - true - - - false - false - true - true - diff --git a/dev/msvc/olcPGE3.vcxproj.filters b/dev/msvc/olcPGE3.vcxproj.filters index 0f8f7d7c..56e01d4f 100644 --- a/dev/msvc/olcPGE3.vcxproj.filters +++ b/dev/msvc/olcPGE3.vcxproj.filters @@ -159,29 +159,23 @@ Hosts\Android Specific - + + Header Files + + + Header Files + + Header Files - - Source Files\tests - - - Source Files\tests - - - Source Files\tests - Source Files Source Files - - Source Files\tests - Source Files @@ -245,6 +239,9 @@ Hosts\Android Specific + + Source Files + Source Files diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj index 31e1a4e8..59440b1c 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj @@ -19,6 +19,7 @@ + true true @@ -43,6 +44,12 @@ true true + + true + true + true + true + true true @@ -127,7 +134,12 @@ true true - + + true + true + true + true + true true diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters index e13a0853..98b841bf 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters @@ -78,6 +78,12 @@ Source Files + + Source Files + + + Source Files + diff --git a/dev/src/api_opengl.cpp b/dev/src/api_opengl.cpp index 7d7e465d..9f1795bc 100644 --- a/dev/src/api_opengl.cpp +++ b/dev/src/api_opengl.cpp @@ -71,6 +71,8 @@ namespace olc::apis::opengl bLoaded &= (_glDeleteRenderbuffers = OGL_LOAD(glDeleteRenderbuffers)) != nullptr; bLoaded &= (_glGetInternalformativ = OGL_LOAD(glGetInternalformativ)) != nullptr; bLoaded &= (_glGetShaderiv = OGL_LOAD(glGetShaderiv)) != nullptr; + bLoaded &= (_glGetRenderbufferParameteriv = OGL_LOAD(glGetRenderbufferParameteriv)) != nullptr; + bLoaded &= (_glRenderbufferStorage = OGL_LOAD(glRenderbufferStorage)) != nullptr; // Do we really need to do this? - jx9 #if OLC_HOST != OLC_HOST_WINDOWS @@ -250,6 +252,12 @@ namespace olc::apis::opengl #endif } + void gl::glFrontFace(GLenum mode) + { + ::glFrontFace(mode); + CheckError(); + } + void gl::glSwapInterval(GLsizei n) { #if OLC_HOST == OLC_HOST_WINDOWS @@ -500,5 +508,17 @@ namespace olc::apis::opengl _glGetIntegerv(pname, data); CheckError(); } + + void gl::glGetRenderbufferParameteriv(GLenum target, GLenum pname, GLint* params) + { + _glGetRenderbufferParameteriv(target, pname, params); + CheckError(); + } + + void gl::glRenderbufferStorage(GLenum target, GLenum internalformat, GLsizei width, GLsizei height) + { + _glRenderbufferStorage(target, internalformat, width, height); + CheckError(); + } } //! END IMPLEMENTATION \ No newline at end of file diff --git a/dev/src/api_opengl.h b/dev/src/api_opengl.h index dda1edb0..d9c5602f 100644 --- a/dev/src/api_opengl.h +++ b/dev/src/api_opengl.h @@ -171,6 +171,8 @@ namespace olc typedef void CALLSTYLE glGetInternalformativ_t(GLenum target, GLenum internalformat, GLenum pname, GLsizei bufSize, GLint* params); typedef void CALLSTYLE glGetShaderiv_t(GLuint shader, GLenum pname, GLint* params); typedef void CALLSTYLE glGetIntegerv_t(GLenum pname, GLint *data); + typedef void CALLSTYLE glGetRenderbufferParameteriv_t(GLenum target, GLenum pname, GLint* params); + typedef void CALLSTYLE glRenderbufferStorage_t(GLenum target, GLenum internalformat, GLsizei width, GLsizei height); #if OLC_HOST == OLC_HOST_WINDOWS typedef void CALLSTYLE wglSwapIntervalEXT_t(GLsizei n); @@ -232,6 +234,8 @@ namespace olc glGetInternalformativ_t* _glGetInternalformativ = nullptr; glGetShaderiv_t* _glGetShaderiv = nullptr; glGetIntegerv_t *_glGetIntegerv = nullptr; + glGetRenderbufferParameteriv_t* _glGetRenderbufferParameteriv = nullptr; + glRenderbufferStorage_t* _glRenderbufferStorage = nullptr; #if OLC_HOST == OLC_HOST_WINDOWS wglSwapIntervalEXT_t* _wglSwapIntervalEXT = nullptr; #endif @@ -282,6 +286,8 @@ namespace olc void glDeleteRenderbuffers(GLsizei n, const GLuint* renderbuffers); void glGetInternalformativ(GLenum target, GLenum internalformat, GLenum pname, GLsizei bufSize, GLint* params); void glGetShaderiv(GLuint shader, GLenum pname, GLint* params); + void glGetRenderbufferParameteriv(GLenum target, GLenum pname, GLint* params); + void glRenderbufferStorage(GLenum target, GLenum internalformat, GLsizei width, GLsizei height); // OpenGL1.2 Proxies (just keeps things tidy imo) void glGenTextures(GLsizei n, GLuint* textures); @@ -303,7 +309,7 @@ namespace olc void glGetTexImage(GLenum target, GLint level, GLenum format, GLenum type, void* pixels); void glHint(GLenum target, GLenum mode); void glPolygonMode(GLenum face, GLenum mode); - + void glFrontFace(GLenum mode); void glGetIntegerv(GLenum pname, GLint *data); @@ -328,8 +334,9 @@ namespace olc static constexpr GLenum GL_SAMPLES_X = 0x80A9; static constexpr GLenum GL_COMPILE_STATUS_X = 0x8B81; static constexpr GLenum GL_INFO_LOG_LENGTH_X = 0x8B84; - - + static constexpr GLenum GL_DEPTH_COMPONENT24 = 0x81A6; + static constexpr GLenum GL_DEPTH_ATTACHMENT_X = 0x8D00; + static constexpr GLenum GL_RENDERBUFFER_SAMPLES_X = 0x8CAB; private: bool CheckError(const std::source_location loc = std::source_location::current()); @@ -338,8 +345,3 @@ namespace olc } // olc namespace //! END DECLARATION - - - - - diff --git a/dev/src/core.cpp b/dev/src/core.cpp index a4d33831..fc91d03e 100644 --- a/dev/src/core.cpp +++ b/dev/src/core.cpp @@ -50,6 +50,10 @@ //! START IMPLEMENTATION namespace olc { + PGEWindow::PGEWindow() : Window(), draw(), draw3d(draw) + { + } + bool PGEWindow::Create(const olc::vi2d& vScreenSize, const olc::vi2d& vPixelSize) { //pRenderer->RetargetDevice(pHost->GetHostWindowDescriptor(this)); diff --git a/dev/src/core.h b/dev/src/core.h index 0dbec83f..df39e7cd 100644 --- a/dev/src/core.h +++ b/dev/src/core.h @@ -21,6 +21,8 @@ #include "host_iface.h" #include "imload_iface.h" #include "font.h" +#include "draw2d.h" +#include "draw3d.h" //! END CUSTOMHEADER GLOBAL //! START DECLARATION @@ -63,6 +65,7 @@ namespace olc class PGEWindow : public Window { public: + PGEWindow(); bool Create(const olc::vi2d& vScreenSize, const olc::vi2d& vPixelSize); public: @@ -112,6 +115,7 @@ namespace olc protected: olc::Draw2D draw; + olc::Draw3D draw3d; private: diff --git a/dev/src/draw2d.h b/dev/src/draw2d.h index 285d747f..92c4184c 100644 --- a/dev/src/draw2d.h +++ b/dev/src/draw2d.h @@ -160,6 +160,8 @@ namespace olc class Renderer; class Shader; } + + class Draw3D; // These "opaque" structs are merely to help with // type differentiation of various GPUTask types @@ -172,6 +174,8 @@ namespace olc class Draw2D { + friend class olc::Draw3D; + public: Draw2D(); diff --git a/dev/src/draw3d.cpp b/dev/src/draw3d.cpp new file mode 100644 index 00000000..5c5deb2b --- /dev/null +++ b/dev/src/draw3d.cpp @@ -0,0 +1,267 @@ +#include "draw3d.h" +#include "gpu_iface.h" + +using namespace olc; + +//! START IMPLEMENTATION +thread_local Draw3D::buffer Draw3D::buffPoints; +thread_local Draw3D::buffer Draw3D::buffColours; +thread_local Draw3D::buffer Draw3D::vecGPUTasks; + +olc::Draw3D::Draw3D(olc::Draw2D& d2d) : draw2d(d2d) +{ + MatrixReset(); +} + +void olc::Draw3D::SetGPU(olc::gpu::Renderer* const renderer) +{ + draw2d.SetGPU(renderer); +} + +void olc::Draw3D::ProcessGPUTasks() +{ + draw2d.ProcessGPUTasks(); +} + +void olc::Draw3D::SetTarget(olc::Image& image) +{ + draw2d.SetTarget(image); +} + +olc::Image& olc::Draw3D::GetTarget() +{ + return draw2d.GetTarget(); +} + +olc::vi2d olc::Draw3D::GetTargetSize() +{ + return draw2d.GetTargetSize(); +} + +void olc::Draw3D::SetViewport(const olc::vi2d& pos, const olc::vi2d& size) +{ + draw2d.pRenderer->SetViewport(pos, size); +} + +bool olc::Draw3D::SetShader(const olc::gpu::Shader& shader) +{ + return draw2d.SetShader(shader); +} + +bool olc::Draw3D::ResetShader() +{ + return draw2d.ResetShader(); +} + +bool olc::Draw3D::SetShaderUniform(const std::string& name, const float value) +{ + return draw2d.SetShaderUniform(name, value); +} + +bool olc::Draw3D::SetShaderUniform(const std::string& name, const olc::vf2d& value) +{ + return draw2d.SetShaderUniform(name, value); +} + +bool olc::Draw3D::SetShaderUniform(const std::string& name, const olc::Pixel value) +{ + return draw2d.SetShaderUniform(name, value); +} + +bool olc::Draw3D::SetShaderTexture(const uint32_t nSlot, olc::Image& image) +{ + return draw2d.SetShaderTexture(nSlot, image); +} + +void olc::Draw3D::PrepareTargetForSW() +{ + draw2d.PrepareTargetForSW(); +} + +void olc::Draw3D::PrepareTargetForHW() +{ + draw2d.PrepareTargetForHW(); +} + +void olc::Draw3D::PrepareImageForSW(olc::Image& image) +{ + draw2d.PrepareImageForSW(image); +} + +void olc::Draw3D::PrepareImageForHW(olc::Image& image) +{ + draw2d.PrepareImageForHW(image); +} + + + + + +void olc::Draw3D::MatrixReset() +{ + matMVP.identity(); + matModel.identity(); + matView.identity(); + matProjection.identity(); +} + +void olc::Draw3D::SetModelMatrix(const olc::mf4d& mat) +{ + matModel = mat; + matMVP = matVP * matModel; +} + +const olc::mf4d& olc::Draw3D::GetModelMatrix() const +{ + return matModel; +} + +void olc::Draw3D::SetViewMatrix(const olc::mf4d& mat) +{ + matView = mat; + matVP = matProjection * matView; + matMVP = matVP * matModel; +} + +const olc::mf4d& olc::Draw3D::GetViewMatrix() const +{ + return matView; +} + +void olc::Draw3D::SetProjectionMatrix(const olc::mf4d& mat) +{ + matProjection = mat; + matVP = matProjection * matView; + matMVP = matVP * matModel; +} + +const olc::mf4d& olc::Draw3D::GetProjectionMatrix() const +{ + return matProjection; +} + +void olc::Draw3D::SetMVPMatrix(const olc::mf4d& mat) +{ + matMVP = mat; +} + +const olc::mf4d& olc::Draw3D::GetMVPMatrix() const +{ + return matMVP; +} + +void olc::Draw3D::SetCullMode(const olc::GPUTask::CullMode mode) +{ + cullMode = mode; +} + +void olc::Draw3D::EnableDepth(const bool bEnable) +{ + bDepth = bEnable; +} + + + +GPUTask olc::Draw3D::TaskWireMesh(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) +{ + GPUTask task; + task.structure = structure; + task.tint = tint; + task.vertexBuffer.resize(vPoints.size()); + task.bWireframe = true; + task.bDepth = bDepth; + task.cullmode = cullMode; + task.bIs3D = true; + task.mvpMatrix = matMVP.m; + + for (size_t i = 0; i < vPoints.size(); i++) + task.vertexBuffer[i] = { {vPoints[i].x, vPoints[i].y, vPoints[i].z, vPoints[i].w}, vColours[i], {0, 0}, {0, 0}, {0, 0}, {0, 0}}; + return task; +} + +GPUTask olc::Draw3D::TaskFillMesh(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) +{ + GPUTask task; + task.structure = structure; + task.tint = tint; + task.vertexBuffer.resize(vPoints.size()); + task.bDepth = bDepth; + task.cullmode = cullMode; + task.bIs3D = true; + task.mvpMatrix = matMVP.m; + + for (size_t i = 0; i < vPoints.size(); i++) + task.vertexBuffer[i] = { {vPoints[i].x, vPoints[i].y, vPoints[i].z, vPoints[i].w}, vColours[i], {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + return task; +} + +GPUTask olc::Draw3D::TaskTexturedMesh(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const std::vector& vTexCoords, olc::Image* const image, const olc::Pixel tint) +{ + GPUTask task; + task.structure = structure; + task.tint = tint; + task.vertexBuffer.resize(vPoints.size()); + task.pImage = image; + task.bIs3D = true; + task.bDepth = bDepth; + task.cullmode = cullMode; + task.mvpMatrix = matMVP.m; + for (size_t i = 0; i < vPoints.size(); i++) + task.vertexBuffer[i] = { {vPoints[i].x, vPoints[i].y, vPoints[i].z, vPoints[i].w}, vColours[i], {vTexCoords[i].x, vTexCoords[i].y}, {0, 0}, {0, 0}, {0, 0} }; + return task; +} + +void olc::Draw3D::Clear(const olc::Pixel& col) +{ + draw2d.Clear(col); +} + +GPUTask& olc::Draw3D::Line(const olc::vf4d& vStart, const olc::vf4d& vEnd, const olc::Pixel& col, const olc::Pixel tint) +{ + PrepareTargetForHW(); + + return draw2d.vecGPUTasks.data.emplace_back(std::move( + TaskWireMesh( + olc::Structure::Line, + { vStart, vEnd }, + { col, col }, + tint + ))); +} + +GPUTask& olc::Draw3D::Mesh(const olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) +{ + PrepareTargetForHW(); + + return draw2d.vecGPUTasks.data.emplace_back(std::move( + TaskWireMesh( + structure, + vPoints, + vColours, + tint + ))); + +} + +GPUTask& olc::Draw3D::Mesh(const olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const std::vector& vUVs, olc::Image& texture, const olc::Pixel tint) +{ + PrepareImageForHW(texture); + PrepareTargetForHW(); + return draw2d.vecGPUTasks.data.emplace_back(std::move( + TaskTexturedMesh( + structure, + vPoints, + vColours, + vUVs, + &texture, + tint + ))); +} + + + + + + +//! END IMPLEMENTATION + diff --git a/dev/src/draw3d.h b/dev/src/draw3d.h new file mode 100644 index 00000000..05ba068a --- /dev/null +++ b/dev/src/draw3d.h @@ -0,0 +1,201 @@ +#pragma once + +//! START STDHEADER +#include +#include +#include +#include +#include +#include +//! END STDHEADER + +//! START CUSTOMHEADER +#include "config.h" +#include "vector2d.h" +#include "vector4d.h" +#include "matrix4d.h" +#include "pixel.h" +#include "image.h" +#include "gputask.h" +#include "font.h" +#include "draw2d.h" +//! END CUSTOMHEADER + +//! START DECLARATION +#if !defined(PGE_DRAW3D_DECLARED) +namespace olc +{ + namespace gpu + { + class Renderer; + class Shader; + } + + class Draw3D + { + + + public: + Draw3D(olc::Draw2D& d2d); + + // Associate this drawing toolbox with a renderer + void SetGPU(olc::gpu::Renderer* const renderer); + void ProcessGPUTasks(); + + public: + // Sets the drawing target of this drawing toolbox + void SetTarget(olc::Image& image); + // Get the current drawing target + olc::Image& GetTarget(); + // Get Size of drawing target (aka GetTarget()->Size()) + olc::vi2d GetTargetSize(); + // Set the area in the target to 3d draw to + void SetViewport(const olc::vi2d& pos, const olc::vi2d& size); + + public: // Applied Matrices + void MatrixReset(); + void SetModelMatrix(const olc::mf4d& mat); + const olc::mf4d& GetModelMatrix() const; + void SetViewMatrix(const olc::mf4d& mat); + const olc::mf4d& GetViewMatrix() const; + void SetProjectionMatrix(const olc::mf4d& mat); + const olc::mf4d& GetProjectionMatrix() const; + void SetMVPMatrix(const olc::mf4d& mat); + const olc::mf4d& GetMVPMatrix() const; + + public: // Applied Rendering Modes + void SetCullMode(const olc::GPUTask::CullMode mode); + void EnableDepth(const bool bEnable); + + public: // Primitive Drawing Functions + // Clear entire draw target to specific colour + void Clear(const olc::Pixel& col); + + GPUTask& Line( + const olc::vf4d& vStart, + const olc::vf4d& vEnd, + const olc::Pixel& col = olc::Colour::WHITE, + const olc::Pixel tint = olc::Colour::WHITE); + + GPUTask& Mesh( + const olc::Structure structure, + const std::vector& vPoints, + const std::vector& vColours, + const olc::Pixel tint = olc::Colour::WHITE); + + GPUTask& Mesh( + const olc::Structure structure, + const std::vector& vPoints, + const std::vector& vColours, + const std::vector& vUVs, + olc::Image& texture, + const olc::Pixel tint = olc::Colour::WHITE); + + + + public: // GPU Task Creator Functions (not normally called by user) + GPUTask TaskWireMesh( + olc::Structure structure, + const std::vector& vPoints, + const std::vector& vColours, + const olc::Pixel tint = olc::Colour::WHITE); + + GPUTask TaskFillMesh( + olc::Structure structure, + const std::vector& vPoints, + const std::vector& vColours, + const olc::Pixel tint = olc::Colour::WHITE); + + GPUTask TaskTexturedMesh( + olc::Structure structure, + const std::vector& vPoints, + const std::vector& vColours, + const std::vector& vTexCoords, + olc::Image* const image, + const olc::Pixel tint = olc::Colour::WHITE); + + public: + // Change the shader used for subsequent GPU drawing tasks + bool SetShader(const olc::gpu::Shader& shader); + // Reset to default shader for subsequent GPU drawing tasks + bool ResetShader(); + // Set uniform variable for subsequent GPU drawing tasks + bool SetShaderUniform(const std::string& name, const float value); + // Set uniform variable for subsequent GPU drawing tasks + bool SetShaderUniform(const std::string& name, const olc::vf2d& value); + // Set uniform variable for subsequent GPU drawing tasks + bool SetShaderUniform(const std::string& name, const olc::Pixel value); + // Assign an image to a texture slot for subsequent GPU drawing tasks + bool SetShaderTexture(const uint32_t nSlot, olc::Image& image); + + public: + struct sDrawMetrics + { + uint32_t nGPUTasks = 0; + uint32_t nGPUtoCPUTransfers = 0; + uint32_t nCPUtoGPUTransfers = 0; + uint32_t nShaderChanges = 0; + }; + + void ResetDrawMetrics(); + sDrawMetrics GetDrawMetrics() const; + + private: + sDrawMetrics drawMetrics; + + + + + protected: + // Checks residency of image resource, and brings it to cpu RAM for r/w + void PrepareTargetForSW(); + // Checks residency of image resource, and brings it to gpu VRAM for r/w + void PrepareTargetForHW(); + + // Checks residency of image resource, and brings it to cpu RAM for r/w + void PrepareImageForSW(olc::Image& image); + // Checks residency of image resource, and brings it to gpu VRAM for r/w + void PrepareImageForHW(olc::Image& image); + + olc::Image* pTarget = nullptr; + olc::gpu::Renderer* pRenderer = nullptr; + + mf4d matModel; + mf4d matView; + mf4d matProjection; + mf4d matVP; + mf4d matMVP; + olc::vf2d vViewportPos = { 0, 0 }; + olc::vi2d vViewportSize = { 0, 0 }; + olc::GPUTask::CullMode cullMode = olc::GPUTask::CullMode::None; + bool bDepth = true; + + olc::Draw2D& draw2d; + + private: + // Simple dynamic buffer that only grows as needed + template + struct buffer + { + std::vector data; + + void reserve(size_t n) + { + if (n > data.capacity()) + data.reserve(n); + + // Ensure size matches requested so we + // can index into it directly + data.resize(n); + } + }; + + // Thread local buffers to avoid repeated allocations + static thread_local buffer buffPoints; + static thread_local buffer buffColours; + static thread_local buffer vecGPUTasks; + }; +} +#define PGE_DRAW3D_DECLARED +#endif +//! END DECLARATION \ No newline at end of file diff --git a/dev/src/gpu_opengl33.cpp b/dev/src/gpu_opengl33.cpp index 492a735a..b7544f01 100644 --- a/dev/src/gpu_opengl33.cpp +++ b/dev/src/gpu_opengl33.cpp @@ -438,7 +438,17 @@ void main() // Create a Frame Buffer Object for off-screen rendering things gl.glGenFramebuffers(1, (GLuint*)&nDefaultFBO); - gl.glBindFramebuffer(gl.GL_FRAMEBUFFER_X, nDefaultFBO); // GL_FRAMEBUFFER + gl.glBindFramebuffer(gl.GL_FRAMEBUFFER_X, nDefaultFBO); + + // Create a shared depth renderbuffer (will be resized dynamically) + gl.glGenRenderbuffers(1, &nDepthRBO); + gl.glBindRenderbuffer(gl.GL_RENDERBUFFER_X, nDepthRBO); + // Allocate with a default size (will be resized when needed) + gl.glRenderbufferStorage(gl.GL_RENDERBUFFER_X, gl.GL_DEPTH_COMPONENT24, 1024, 1024); + gl.glFramebufferRenderbuffer(gl.GL_FRAMEBUFFER_X, gl.GL_DEPTH_ATTACHMENT_X, gl.GL_RENDERBUFFER_X, nDepthRBO); + vCurrentDepthSize = {1024, 1024}; + nCurrentDepthSamples = 0; + // Attach 4 colour buffers std::array attachments = { { @@ -462,20 +472,34 @@ void main() gl.glGenFramebuffers(1, &nResolveFBO_Draw); gl.glGenFramebuffers(1, &nResolveFBO_Read); + // PGE Specific requirements + + // Texturing Enabled #if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID gl.glEnable(GL_TEXTURE_2D); // Turn on texturing gl.glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); #endif + // Alpha Blending Enabled gl.glEnable(GL_BLEND); + // Front Face is Counter-Clockwise + gl.glFrontFace(GL_CCW); + lastError = RendererError::NoError; return true; } bool Renderer_OGL33::DestroyDevice() { - //auto& gl = olc::apis::opengl::gl::Get(); - + auto& gl = olc::apis::opengl::gl::Get(); + + // Delete depth renderbuffer + if (nDepthRBO != 0) + { + gl.glDeleteRenderbuffers(1, &nDepthRBO); + nDepthRBO = 0; + } + #if OLC_HOST == OLC_HOST_WINDOWS wglDeleteContext(glRenderContext); #endif @@ -793,6 +817,60 @@ void main() // Bind FBO gl.glBindFramebuffer(gl.GL_FRAMEBUFFER_X, nDefaultFBO); + // Resize depth buffer to match target texture dimensions + olc::vi2d targetSize = mapTextureSizes[texid]; + int32_t targetSamples = 0; + + // Check if this is an MSAA texture + bool bIsMSAA = mapTextureToRenderbuffer.contains(texid); + if (bIsMSAA) + { + // Get the MSAA sample count from the color renderbuffer + uint32_t rboId = mapTextureToRenderbuffer[texid]; + gl.glBindRenderbuffer(gl.GL_RENDERBUFFER_X, rboId); + gl.glGetRenderbufferParameteriv(gl.GL_RENDERBUFFER_X, gl.GL_RENDERBUFFER_SAMPLES_X, &targetSamples); + } + + // Only resize if dimensions or sample count changed + if (targetSize != vCurrentDepthSize || targetSamples != nCurrentDepthSamples) + { + gl.glBindRenderbuffer(gl.GL_RENDERBUFFER_X, nDepthRBO); + + if (bIsMSAA && targetSamples > 0) + { + // Allocate MSAA depth buffer + gl.glRenderbufferStorageMultisample( + gl.GL_RENDERBUFFER_X, + targetSamples, + gl.GL_DEPTH_COMPONENT24, + targetSize.x, + targetSize.y + ); + } + else + { + // Allocate regular depth buffer + gl.glRenderbufferStorage( + gl.GL_RENDERBUFFER_X, + gl.GL_DEPTH_COMPONENT24, + targetSize.x, + targetSize.y + ); + } + + // Update tracked size and samples + vCurrentDepthSize = targetSize; + nCurrentDepthSamples = targetSamples; + + // Re-attach depth buffer to FBO + gl.glFramebufferRenderbuffer( + gl.GL_FRAMEBUFFER_X, + gl.GL_DEPTH_ATTACHMENT_X, + gl.GL_RENDERBUFFER_X, + nDepthRBO + ); + } + // Allocate target buffers - pick the single attachment corresponding to 'slot' std::array attachments = { { @@ -1004,42 +1082,36 @@ void main() // Copy data from CPU to GPU gl.glBufferData(gl.GL_ARRAY_BUFFER_X, sizeof(GPUTask::Vertex) * task.vertexBuffer.size(), task.vertexBuffer.data(), gl.GL_STREAM_DRAW_X); - - + // Configure shader with expected values - - - - // Shader: Apply MVP Matrix - //gl.glUniformMatrix4fv(shaderDefault.GetUniform("mvp"), 1, true, task.mvpMatrix.data()); - - // Shader: Apply Global Tint SetUniform("pgeGlobalTint", task.tint); - SetUniform("pgeTargetSizeInPixels", vTargetSize); SetUniform("pgeInverseTargetSizeInPixels", (1.0f / vTargetSize)); SetUniform("pgeTotalTimeElapsed", fTotalTime); + + // Apply Culling modes - //if (task.cullmode == GPUTask::CullMode::None) - //{ - // gl.glCullFace(GL_FRONT); - // gl.glDisable(GL_CULL_FACE); - //} - //else if (task.cullmode == GPUTask::CullMode::ClockWise) - //{ - // gl.glCullFace(GL_FRONT); - // gl.glEnable(GL_CULL_FACE); - //} - //else if (task.cullmode == GPUTask::CullMode::CounterClockWise) - //{ - // gl.glCullFace(GL_BACK); - // gl.glEnable(GL_CULL_FACE); - //} + if (task.cullmode == GPUTask::CullMode::None) + { + gl.glDisable(GL_CULL_FACE); + } + else if (task.cullmode == GPUTask::CullMode::ClockWise) + { + gl.glCullFace(GL_FRONT); + gl.glEnable(GL_CULL_FACE); + } + else if (task.cullmode == GPUTask::CullMode::CounterClockWise) + { + gl.glCullFace(GL_BACK); + gl.glEnable(GL_CULL_FACE); + } //// Apply Depth Testing (if required) - //if (task.bDepth) - // gl.glEnable(GL_DEPTH_TEST); + if (task.bDepth) + gl.glEnable(GL_DEPTH_TEST); + + glDepthFunc(GL_LESS); gl.glEnable(GL_BLEND); //gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); @@ -1048,16 +1120,24 @@ void main() if (task.bWireframe) gl.glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - if (task.structure == olc::Structure::Point) - gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); - else if (task.structure == olc::Structure::Line) - gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); - else if (task.structure == olc::Structure::LineLoop) - gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); - else if (task.structure == olc::Structure::LineList) - gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); + if (task.bIs3D) + { + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 2); + gl.glUniformMatrix4fv(pCurrentShader->GetUniform("pgeMVP"), 1, true, task.mvpMatrix.data()); + } else - gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 0); + { + if (task.structure == olc::Structure::Point) + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); + else if (task.structure == olc::Structure::Line) + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); + else if (task.structure == olc::Structure::LineLoop) + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); + else if (task.structure == olc::Structure::LineList) + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); + else + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 0); + } if (task.structure == olc::Structure::Fan) gl.glDrawArrays(GL_TRIANGLE_FAN, 0, (GLsizei)task.vertexBuffer.size()); @@ -1091,7 +1171,7 @@ void main() bool Renderer_OGL33::ClearViewport(const olc::Pixel col, bool bDepth, bool bStencil) { auto& gl = olc::apis::opengl::gl::Get(); - gl.glClearColor(float(col.r) / 255.0f, float(col.g) / 255.0f, float(col.b) / 255.0f, float(col.a) / 255.0f); + gl.glClearColor(float(col.r) / 255.0f, float(col.g) / 255.0f, float(col.b) / 255.0f, float(col.a) / 255.0f); gl.glClear(GL_COLOR_BUFFER_BIT | (bDepth ? GL_DEPTH_BUFFER_BIT : 0) | (bStencil ? GL_STENCIL_BUFFER_BIT : 0)); return true; } diff --git a/dev/src/gpu_opengl33.h b/dev/src/gpu_opengl33.h index 4cef8eea..6d1782bb 100644 --- a/dev/src/gpu_opengl33.h +++ b/dev/src/gpu_opengl33.h @@ -100,6 +100,10 @@ namespace olc const Shader* pCurrentShader = nullptr; + uint32_t nDepthRBO = 0; // Shared depth renderbuffer + olc::vi2d vCurrentDepthSize = {0, 0}; // Track current depth buffer size + int32_t nCurrentDepthSamples = 0; // Track current MSAA sample count + #if OLC_HOST == OLC_HOST_ANDROID EGLConfig FindBestConfig(EGLDisplay display, int desiredMultisamples = OLC_MSAA_SAMPLES); #endif diff --git a/dev/src/gputask.h b/dev/src/gputask.h index ef5adf9b..251230de 100644 --- a/dev/src/gputask.h +++ b/dev/src/gputask.h @@ -79,6 +79,9 @@ namespace olc // Use hardware wire drawing bool bWireframe = false; + // Define how to interpret vertex buffer + bool bIs3D = false; + // Overall biasing colour (great for blends) olc::Pixel tint = olc::Colour::WHITE; diff --git a/dev/src/matrix3d.h b/dev/src/matrix3d.h index 1d90418b..a950caba 100644 --- a/dev/src/matrix3d.h +++ b/dev/src/matrix3d.h @@ -5,7 +5,7 @@ #include #include #include -#include +#include //! END STDHEADER //! START CUSTOMHEADER diff --git a/dev/src/matrix4d.h b/dev/src/matrix4d.h new file mode 100644 index 00000000..2e65d998 --- /dev/null +++ b/dev/src/matrix4d.h @@ -0,0 +1,367 @@ +#pragma once + +//! START STDHEADER GLOBAL +#include +#include +#include +#include +#include +//! END STDHEADER + +//! START CUSTOMHEADER +#include "config.h" +#include "vector4d.h" +//! END CUSTOMHEADER + +//! START DECLARATION +#if !defined(PGE_MATRIX4D_DECLARED) +namespace olc +{ + + /* + A complete 4x4 Matrix structure, with a variety + of useful utility functions and operator overloads + specifically targeting 3D graphical transformations + + as per https://en.wikipedia.org/wiki/Transformation_matrix + + Access: column, row + */ + + /* + + + Because Matrices can be defined all sort sof ways, I have included this little + description to clarify how this particular implementation works. For the end + user's ease of use, the transformations are designed to mimic those found on + Wikipedias page on transformation matrices, which are in column-major order. + + Memory layout of the 4x4 matrix is as follows idx = R * 4 + C: + + 0x00 0x01 0x02 0x3 + 0x00 | 0,0 | 1,0 | 2,0 | 3,0 | + 0x04 | 0,1 | 1,1 | 2,1 | 3,1 | + 0x08 | 0,2 | 1,2 | 2,2 | 3,2 | + 0x0C | 0,3 | 1,3 | 2,3 | 3,3 | + + This is row-major order (in storage) but we really only access this + via the idx operator (col, row) so it is effectively column-major order + for the user. + + This is because in graphics we typically want to multiply a vector on + the right of the matrix, and we want the translation components to be in + the last column. + + Matrix * Vector multiplication is as follows: + + | m11 m12 m13 m14 | | v1 | | r1 | (m11*v1 + m12*v2 + m13*v3 + m14*v4) + | m21 m22 m23 m24 | * | v2 | = | r2 | (m21*v1 + m22*v2 + m23*v3 + m24*v4) + | m31 m32 m33 m34 | | v3 | | r3 | (m31*v1 + m32*v2 + m33*v3 + m34*v4) + | m41 m42 m43 m44 | | v4 | | r4 | (m41*v1 + m42*v2 + m43*v3 + m44*v4) + + Matrix * Matrix multiplication is as follows: + + | a11 a12 a13 a14 | | b11 b12 b13 b14 | | r11 r12 r13 r14 | (a11*b11 + a12*b21 + a13*b31 + a14*b41) ... + | a21 a22 a23 a24 | * | b21 b22 b23 b24 | = | r21 r22 r23 r24 | (a21*b11 + a22*b21 + a23*b31 + a24*b41) ... + | a31 a32 a33 a34 | | b31 b32 b33 b34 | | r31 r32 r33 r34 | (a31*b11 + a32*b21 + a33*b31 + a34*b41) ... + | a41 a42 a43 a44 | | b41 b42 b43 b44 | | r41 r42 r43 r44 | (a41*b11 + a42*b21 + a43*b31 + a44*b41) ... + + Example Translation: + + | 1 0 0 Tx | | x | | x' | (1*x + 0*y + 0*z + Tx*1) (x + tx) + | 0 1 0 Ty | * | y | = | y' | (0*x + 1*y + 0*z + Ty*1) (y + ty) + | 0 0 1 Tz | | z | | z' | (0*x + 0*y + 1*z + Tz*1) (z + tz) + | 0 0 0 1 | | 1 | | x' | (0*x + 0*y + 0*z + 1*1) (1) + + Example Rotation around Y axis: + | cθ 0 sθ 0 | | x | | x' | (cosθ*x + 0*y + sinθ*z + 0*1) (x*cosθ + z*sinθ) + | 0 1 0 0 | * | y | = | y' | (0*x + 1*y + 0*z + 0*1) (y) + | -sθ 0 cθ 0 | | z | | z' | (-sinθ*x + 0*y + cosθ*z + 0*1) (z*cosθ - x*sinθ) + | 0 0 0 1 | | 1 | | x' | (0*x + 0*y + 0*z + 1*1) (1) + + P' = Projection * View * World * P + + */ + + + + template + struct m_4d + { + static_assert(std::is_arithmetic::value, "olc::m_4d must be numeric"); + + // The 4x4 elements! + std::array m{ {0 } }; + + // Constructor created identity matrix + inline constexpr m_4d() + { + identity(); + } + + // Copy constructor + inline constexpr m_4d(const m_4d& mat) = default; + + // Assignment operator + inline constexpr m_4d& operator=(const m_4d& mat) = default; + + // Retrieve a specific element's 1D index + inline constexpr size_t idx(const size_t c, const size_t r) const + { + return r * 4 + c; // Column-major order (for user) but row-major order in storage + } + + // Retrieve non-const access to specific element + inline constexpr T& operator()(const size_t col, const size_t row) + { + return m[idx(col, row)]; + } + + // Retrieve const access to specific element + inline constexpr const T& operator()(const size_t col, const size_t row) const + { + return m[idx(col, row)]; + } + + // Set all elements to 0 + inline constexpr void clear() + { + std::fill(m.begin(), m.end(), T(0)); + } + + // Create identity matrix + inline constexpr void identity() + { + clear(); + auto& me = (*this); + me(0, 0) = T(1); + me(1, 1) = T(1); + me(2, 2) = T(1); + me(3, 3) = T(1); + } + + inline constexpr auto transpose() const + { + olc::m_4d out; + auto& me = (*this); + for (int i = 0; i < 4; i++) + for (int j = 0; j < 4; j++) + out(i, j) = me(j, i); + return out; + } + + // Create translation matrix via components + template + inline constexpr void translate(const Q x, const Q y, const Q z) + { + identity(); + auto& me = (*this); + me(3, 0) = T(x); + me(3, 1) = T(y); + me(3, 2) = T(z); + } + + // Create translation matrix via vector (x, y, z components) + template + inline constexpr void translate(const olc::v_4d& v) + { + translate(v.x, v.y, v.z); + } + + // Create scaling matrix via components + template + inline constexpr void scale(const Q x, const Q y, const Q z) + { + identity(); + auto& me = (*this); + me(0, 0) = T(x); + me(1, 1) = T(y); + me(2, 2) = T(z); + } + + // Create scaling matrix via vector (x, y, z components) + template + inline constexpr void scale(const olc::v_4d& v) + { + scale(v.x, v.y, v.z); + } + + // Create rotation matrix around X axis with radians + template + inline constexpr void rotateX(const Q rads) + { + identity(); + auto& me = (*this); + me(1, 1) = std::cos(T(rads)); + me(1, 2) = std::sin(T(rads)); + me(2, 1) = -me(1, 2); + me(2, 2) = me(1, 1); + } + + // Create rotation matrix around Y axis with radians + template + inline constexpr void rotateY(const Q rads) + { + identity(); + auto& me = (*this); + me(0, 0) = std::cos(T(rads)); + me(0, 2) = -std::sin(T(rads)); + me(2, 0) = -me(0, 2); + me(2, 2) = me(0, 0); + } + + // Create rotation matrix around Z axis with radians + template + inline constexpr void rotateZ(const Q rads) + { + identity(); + auto& me = (*this); + me(0, 0) = std::cos(T(rads)); + me(0, 1) = std::sin(T(rads)); + me(1, 0) = -me(0, 1); + me(1, 1) = me(0, 0); + } + + // Create perspective projection matrix + template + inline constexpr void perspective(const Q fov, const Q ratio, const Q nearplane, const Q farplane) + { + identity(); + auto& me = (*this); + T invFOV = T(1) / tan(fov * T(0.5)); + + me(0,0) = invFOV / ratio; // X scale + me(1,1) = invFOV; // Y scale + me(2,2) = (farplane + nearplane) / (nearplane - farplane); // Z mapping + me(3,2) = (2.0f * farplane * nearplane) / (nearplane - farplane); // Z offset + me(2,3) = -1.0f; // Perspective divide by -Z + me(3,3) = 0.0f; + } + + // Create orthographic projection matrix + template + inline constexpr void orthographic(const Q left, const Q right, const Q bottom, const Q top, const Q near1, const Q far1) + { + identity(); + auto& me = (*this); + me(0, 0) = T(2) / (right - left); + me(1, 1) = T(2) / (top - bottom); + me(2, 2) = T(-2) / (far1 - near1); + me(3, 0) = -(right + left) / (right - left); + me(3, 1) = -(top + bottom) / (top - bottom); + me(3, 2) = -(far1 + near1) / (far1 - near1); + } + + // Return inverted matrix + inline constexpr auto invert() const + { + // Using Gauss-Jordan elimination - AI special this :P + olc::m_4d out; + auto& me = (*this); + + T A2323 = me(2, 2) * me(3, 3) - me(2, 3) * me(3, 2); + T A1323 = me(2, 1) * me(3, 3) - me(2, 3) * me(3, 1); + T A1223 = me(2, 1) * me(3, 2) - me(2, 2) * me(3, 1); + T A0323 = me(2, 0) * me(3, 3) - me(2, 3) * me(3, 0); + T A0223 = me(2, 0) * me(3, 2) - me(2, 2) * me(3, 0); + T A0123 = me(2, 0) * me(3, 1) - me(2, 1) * me(3, 0); + T A2313 = me(1, 2) * me(3, 3) - me(1, 3) * me(3, 2); + T A1313 = me(1, 1) * me(3, 3) - me(1, 3) * me(3, 1); + T A1213 = me(1, 1) * me(3, 2) - me(1, 2) * me(3, 1); + T A2312 = me(1, 2) * me(2, 3) - me(1, 3) * me(2, 2); + T A1312 = me(1, 1) * me(2, 3) - me(1, 3) * me(2, 1); + T A1212 = me(1, 1) * me(2, 2) - me(1, 2) * me(2, 1); + T A0313 = me(1, 0) * me(3, 3) - me(1, 3) * me(3, 0); + T A0213 = me(1, 0) * me(3, 2) - me(1, 2) * me(3, 0); + T A0312 = me(1, 0) * me(2, 3) - me(1, 3) * me(2, 0); + T A0212 = me(1, 0) * me(2, 2) - me(1, 2) * me(2, 0); + T A0113 = me(1, 0) * me(3, 1) - me(1, 1) * me(3, 0); + T A0112 = me(1, 0) * me(2, 1) - me(1, 1) * me(2, 0); + + T det = me(0, 0) * (me(1, 1) * A2323 - me(1, 2) * A1323 + me(1, 3) * A1223) + - me(0, 1) * (me(1, 0) * A2323 - me(1, 2) * A0323 + me(1, 3) * A0223) + + me(0, 2) * (me(1, 0) * A1323 - me(1, 1) * A0323 + me(1, 3) * A0123) + - me(0, 3) * (me(1, 0) * A1223 - me(1, 1) * A0223 + me(1, 2) * A0123); + + T invdet = T(1) / det; + + out(0, 0) = invdet * (me(1, 1) * A2323 - me(1, 2) * A1323 + me(1, 3) * A1223); + out(0, 1) = invdet * -(me(0, 1) * A2323 - me(0, 2) * A1323 + me(0, 3) * A1223); + out(0, 2) = invdet * (me(0, 1) * A2313 - me(0, 2) * A1313 + me(0, 3) * A1213); + out(0, 3) = invdet * -(me(0, 1) * A2312 - me(0, 2) * A1312 + me(0, 3) * A1212); + out(1, 0) = invdet * -(me(1, 0) * A2323 - me(1, 2) * A0323 + me(1, 3) * A0223); + out(1, 1) = invdet * (me(0, 0) * A2323 - me(0, 2) * A0323 + me(0, 3) * A0223); + out(1, 2) = invdet * -(me(0, 0) * A2313 - me(0, 2) * A0313 + me(0, 3) * A0213); + out(1, 3) = invdet * (me(0, 0) * A2312 - me(0, 2) * A0312 + me(0, 3) * A0212); + out(2, 0) = invdet * (me(1, 0) * A1323 - me(1, 1) * A0323 + me(1, 3) * A0123); + out(2, 1) = invdet * -(me(0, 0) * A1323 - me(0, 1) * A0323 + me(0, 3) * A0123); + out(2, 2) = invdet * (me(0, 0) * A1313 - me(0, 1) * A0313 + me(0, 3) * A0113); + out(2, 3) = invdet * -(me(0, 0) * A1312 - me(0, 1) * A0312 + me(0, 3) * A0112); + out(3, 0) = invdet * -(me(1, 0) * A1223 - me(1, 1) * A0223 + me(1, 2) * A0123); + out(3, 1) = invdet * (me(0, 0) * A1223 - me(0, 1) * A0223 + me(0, 2) * A0123); + out(3, 2) = invdet * -(me(0, 0) * A1213 - me(0, 1) * A0213 + me(0, 2) * A0113); + out(3, 3) = invdet * (me(0, 0) * A1212 - me(0, 1) * A0212 + me(0, 2) * A0112); + + return out; + } + + // Transform a vector by this matrix + template + inline constexpr auto operator * (const olc::v_4d& v) const + { + auto& me = *this; + olc::v_4d vOut; + vOut.x = Q(me(0, 0) * v.x + me(1, 0) * v.y + me(2, 0) * v.z + me(3, 0) * v.w); + vOut.y = Q(me(0, 1) * v.x + me(1, 1) * v.y + me(2, 1) * v.z + me(3, 1) * v.w); + vOut.z = Q(me(0, 2) * v.x + me(1, 2) * v.y + me(2, 2) * v.z + me(3, 2) * v.w); + vOut.w = Q(me(0, 3) * v.x + me(1, 3) * v.y + me(2, 3) * v.z + me(3, 3) * v.w); + return vOut; + } + + // Multiply this matrix with another + template + inline constexpr auto operator * (const olc::m_4d& rhs) const + { + auto& me = *this; + olc::m_4d out; + for (size_t c = 0; c < 4; c++) + for (size_t r = 0; r < 4; r++) + out(c, r) = me(0, r) * rhs(c, 0) + me(1, r) * rhs(c, 1) + me(2, r) * rhs(c, 2) + me(3, r) * rhs(c, 3); + return out; + } + + // Transform a vector of v_4d by this matrix + template + inline constexpr auto transform(const std::vector>& v) + { + std::vector> o(v.size()); + std::transform(v.begin(), v.end(), o.begin(), [this](const olc::v_4d& i) {return (*this) * i; }); + return o; + } + + // Return this matrix as a std::string + inline std::string str() const + { + const auto& me = *this; + return std::string("[") + std::to_string(me(0, 0)) + "," + std::to_string(me(1, 0)) + "," + std::to_string(me(2, 0)) + "," + std::to_string(me(3, 0)) + "]\n" + + "[" + std::to_string(me(0, 1)) + "," + std::to_string(me(1, 1)) + "," + std::to_string(me(2, 1)) + "," + std::to_string(me(3, 1)) + "]\n" + + "[" + std::to_string(me(0, 2)) + "," + std::to_string(me(1, 2)) + "," + std::to_string(me(2, 2)) + "," + std::to_string(me(3, 2)) + "]\n" + + "[" + std::to_string(me(0, 3)) + "," + std::to_string(me(1, 3)) + "," + std::to_string(me(2, 3)) + "," + std::to_string(me(3, 3)) + "]\n"; + } + }; + + // Allow olc::m_4d to play nicely with std::cout + template + inline std::ostream& operator << (std::ostream& os, const m_4d& rhs) + { + os << rhs.str(); + return os; + } + + // Convenient types ready-to-go + typedef m_4d mf4d; + typedef m_4d md4d; +} +#define PGE_MATRIX4D_DECLARED 1 +#endif +//! END DECLARATION \ No newline at end of file diff --git a/dev/src/olcpge3.h b/dev/src/olcpge3.h index f1682777..56252716 100644 --- a/dev/src/olcpge3.h +++ b/dev/src/olcpge3.h @@ -3,7 +3,9 @@ #include "config.h" #include "pixel.h" #include "vector2d.h" +#include "vector4d.h" #include "matrix3d.h" +#include "matrix4d.h" #include "transform2d.h" #include "window.h" #include "core.h" diff --git a/dev/src/sh_template.h b/dev/src/sh_template.h index 1850c15a..0a77fada 100644 --- a/dev/src/sh_template.h +++ b/dev/src/sh_template.h @@ -100,6 +100,9 @@ and the many community contributors that have provided bug fixes, suggestions, criticisms, and encouragement from the OneLoneCoder Discord server, YouTube & GitHub. + Saladin, if you're out there, I know you would have been a proud contributer to this. + I miss you buddy. + Version History ~~~~~~~~~~~~~~~ v3.00: It begins... @@ -114,8 +117,12 @@ //! GRAB vector2d.h DECLARATION +//! GRAB vector4d.h DECLARATION + //! GRAB matrix3d.h DECLARATION +//! GRAB matrix4d.h DECLARATION + //! GRAB transform2d.h DECLARATION //! GRAB image.h DECLARATION @@ -130,6 +137,8 @@ //! GRAB draw2d.h DECLARATION +//! GRAB draw3d.h DECLARATION + //! GRAB hw_input.h DECLARATION //! GRAB hw_mouse.h DECLARATION @@ -267,6 +276,11 @@ #define PGE_DRAW2D_IMPLEMENTED 1 #endif +#if defined(OLC_PGE3_APPLICATION) && !defined(PGE_DRAW3D_IMPLEMENTED) +//! GRAB draw3d.cpp IMPLEMENTATION +#define PGE_DRAW3D_IMPLEMENTED 1 +#endif + #if defined(OLC_PGE3_APPLICATION) && !defined(PGE_CORE_IMPLEMENTED) //! GRAB core.cpp IMPLEMENTATION #define PGE_CORE_IMPLEMENTED 1 diff --git a/dev/src/vector4d.h b/dev/src/vector4d.h new file mode 100644 index 00000000..3661b505 --- /dev/null +++ b/dev/src/vector4d.h @@ -0,0 +1,348 @@ +#pragma once + +//! START STDHEADER GLOBAL +#include +#include +#include +#include +#include +//! END STDHEADER + +//! START CUSTOMHEADER +#include "config.h" +#include "vector2d.h" +//! END CUSTOMHEADER + +//! START DECLARATION +#if !defined(PGE_VECTOR4D_DECLARED) +namespace olc +{ + /* + A complete 4D geometric vector structure, with a variety + of useful utility functions and operator overloads. + */ + template + struct v_4d + { + static_assert(std::is_arithmetic::value, "olc::v_4d must be numeric"); + + union + { +#pragma warning(disable:4201) // Top MSVC whinging about anonymous structs + struct + { + // x-axis component + T x; + // y-axis component + T y; + // z-axis component + T z; + // w-axis component + T w; + }; +#pragma warning(default:4201) + + std::array xyzw = { {0,0,0,1} }; + }; + + // Default constructor + inline constexpr v_4d() = default; + + // Specific constructor + inline constexpr v_4d(T _x, T _y, T _z, T _w = 1) : x(_x), y(_y), z(_z), w(_w) + {} + + inline constexpr v_4d(const v_2d& v, T _z, T _w) : x(v.x), y(v.y), z(_z), w(_w) + {} + + inline constexpr v_4d(const v_2d& v1, const v_2d& v2) : x(v1.x), y(v1.y), z(v2.x), w(v2.y) + {} + + // Copy constructor + inline constexpr v_4d(const v_4d& v) = default; + + // Assignment operator + inline constexpr v_4d& operator=(const v_4d& v) = default; + + + inline constexpr std::array a() const + { + return xyzw; + } + + inline constexpr v_2d xy() const + { + return v_2d(x, y); + } + + inline constexpr v_2d zw() const + { + return v_2d(z, w); + } + + // Returns magnitude of vector + inline constexpr auto mag() const + { + return std::sqrt(x * x + y * y + z * z + w * w); + } + + // Returns magnitude squared of vector (useful for fast comparisons) + inline constexpr T mag2() const + { + return x * x + y * y + z * z + w * w; + } + + // Returns normalised version of vector + inline constexpr v_4d norm() const + { + auto r = 1 / mag(); + return v_4d(x * r, y * r, z * r, w * r); + } + + // Rounds all components down + inline constexpr v_4d floor() const + { + return v_4d(std::floor(x), std::floor(y), std::floor(z), std::floor(w)); + } + + // Rounds all components accurately + inline constexpr v_4d round() const + { + return v_4d(std::round(x), std::round(y), std::round(z), std::round(w)); + } + + // Rounds all components up + inline constexpr v_4d ceil() const + { + return v_4d(std::ceil(x), std::ceil(y), std::ceil(z), std::ceil(w)); + } + + // Returns 'element-wise' max of this and another vector + inline constexpr v_4d max(const v_4d& v) const + { + return v_4d(std::max(x, v.x), std::max(y, v.y), std::max(z, v.z), std::max(w, v.w)); + } + + // Returns 'element-wise' min of this and another vector + inline constexpr v_4d min(const v_4d& v) const + { + return v_4d(std::min(x, v.x), std::min(y, v.y), std::min(z, v.z), std::min(w, v.w)); + } + + // Returns 'element-wise' abs of this vector + inline constexpr v_4d abs() const + { + return v_4d(std::abs(x), std::abs(y), std::abs(z), std::abs(w)); + } + + // Calculates scalar dot product between this and another vector + inline constexpr auto dot(const v_4d& rhs) const + { + return this->x * rhs.x + this->y * rhs.y + this->z * rhs.z + this->w * rhs.w; + } + + // Calculates cross product between this and another vector + inline constexpr v_4d cross(const v_4d& rhs) const + { + return v_4d(this->y * rhs.z - this->z * rhs.y, this->z * rhs.x - this->x * rhs.z, this->x * rhs.y - this->y * rhs.x, 0); + } + + // Clamp the components of this vector in between the 'element-wise' minimum and maximum of 2 other vectors + inline constexpr v_4d clamp(const v_4d& v1, const v_4d& v2) const + { + return this->max(v1).min(v2); + } + + // Linearly interpolate between this vector, and another vector, given normalised parameter 't' + inline constexpr v_4d lerp(const v_4d& v1, const double t) const + { + return (*this) * (T(1.0 - t)) + (v1 * T(t)); + } + + // Compare if this vector is numerically equal to another + inline constexpr bool operator == (const v_4d& rhs) const + { + return (this->x == rhs.x && this->y == rhs.y && this->z == rhs.z && this->w == rhs.w); + } + + // Compare if this vector is not numerically equal to another + inline constexpr bool operator != (const v_4d& rhs) const + { + return (this->x != rhs.x || this->y != rhs.y || this->z != rhs.z || this->w != rhs.w); + } + + // Return this vector as a std::string, of the form "(x,y,z,w)" + inline std::string str() const + { + return std::string("(") + std::to_string(this->x) + "," + std::to_string(this->y) + "," + std::to_string(this->z) + "," + std::to_string(this->w) + ")"; + } + + // Allow 'casting' from other v_4d types + template + inline constexpr operator v_4d() const + { + return { static_cast(this->x), static_cast(this->y), static_cast(this->z), static_cast(this->w) }; + } + }; + + // Multiplication operator overloads between vectors and scalars, and vectors and vectors + template + inline constexpr auto operator * (const TL& lhs, const v_4d& rhs) + { + return v_4d(lhs * rhs.x, lhs * rhs.y, lhs * rhs.z, lhs * rhs.w); + } + + template + inline constexpr auto operator * (const v_4d& lhs, const TR& rhs) + { + return v_4d(lhs.x * rhs, lhs.y * rhs, lhs.z * rhs, lhs.w * rhs); + } + + template + inline constexpr auto operator * (const v_4d& lhs, const v_4d& rhs) + { + return v_4d(lhs.x * rhs.x, lhs.y * rhs.y, lhs.z * rhs.z, lhs.w * rhs.w); + } + + template + inline constexpr auto operator *= (v_4d& lhs, const TR& rhs) + { + lhs = lhs * rhs; + return lhs; + } + + // Division operator overloads between vectors and scalars, and vectors and vectors + template + inline constexpr auto operator / (const TL& lhs, const v_4d& rhs) + { + return v_4d(lhs / rhs.x, lhs / rhs.y, lhs / rhs.z, lhs / rhs.w); + } + + template + inline constexpr auto operator / (const v_4d& lhs, const TR& rhs) + { + return v_4d(lhs.x / rhs, lhs.y / rhs, lhs.z / rhs, lhs.w / rhs); + } + + template + inline constexpr auto operator / (const v_4d& lhs, const v_4d& rhs) + { + return v_4d(lhs.x / rhs.x, lhs.y / rhs.y, lhs.z / rhs.z, lhs.w / rhs.w); + } + + template + inline constexpr auto operator /= (v_4d& lhs, const TR& rhs) + { + lhs = lhs / rhs; + return lhs; + } + + // Unary Addition operator (pointless but i like the platinum trophies) + template + inline constexpr auto operator + (const v_4d& lhs) + { + return v_4d(+lhs.x, +lhs.y, +lhs.z, +lhs.w); + } + + // Addition operator overloads between vectors and scalars, and vectors and vectors + template + inline constexpr auto operator + (const TL& lhs, const v_4d& rhs) + { + return v_4d(lhs + rhs.x, lhs + rhs.y, lhs + rhs.z, lhs + rhs.w); + } + + template + inline constexpr auto operator + (const v_4d& lhs, const TR& rhs) + { + return v_4d(lhs.x + rhs, lhs.y + rhs, lhs.z + rhs, lhs.w + rhs); + } + + template + inline constexpr auto operator + (const v_4d& lhs, const v_4d& rhs) + { + return v_4d(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z, lhs.w + rhs.w); + } + + template + inline constexpr auto operator += (v_4d& lhs, const TR& rhs) + { + lhs = lhs + rhs; + return lhs; + } + + template + inline constexpr auto operator += (v_4d& lhs, const v_4d& rhs) + { + lhs = lhs + rhs; + return lhs; + } + + // Unary negation operator overoad for inverting a vector + template + inline constexpr auto operator - (const v_4d& lhs) + { + return v_4d(-lhs.x, -lhs.y, -lhs.z, -lhs.w); + } + + // Subtraction operator overloads between vectors and scalars, and vectors and vectors + template + inline constexpr auto operator - (const TL& lhs, const v_4d& rhs) + { + return v_4d(lhs - rhs.x, lhs - rhs.y, lhs - rhs.z, lhs - rhs.w); + } + + template + inline constexpr auto operator - (const v_4d& lhs, const TR& rhs) + { + return v_4d(lhs.x - rhs, lhs.y - rhs, lhs.z - rhs, lhs.w - rhs); + } + + template + inline constexpr auto operator - (const v_4d& lhs, const v_4d& rhs) + { + return v_4d(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z, lhs.w - rhs.w); + } + + template + inline constexpr auto operator -= (v_4d& lhs, const TR& rhs) + { + lhs = lhs - rhs; + return lhs; + } + + // Greater/Less-Than Operator overloads - mathematically useless, but handy for "sorted" container storage + template + inline constexpr bool operator < (const v_4d& lhs, const v_4d& rhs) + { + return (lhs.w < rhs.w) + || (lhs.w == rhs.w && lhs.z < rhs.z) + || (lhs.w == rhs.w && lhs.z == rhs.z && lhs.y < rhs.y) + || (lhs.w == rhs.w && lhs.z == rhs.z && lhs.y == rhs.y && lhs.x < rhs.x); + } + + template + inline constexpr bool operator > (const v_4d& lhs, const v_4d& rhs) + { + return (lhs.w > rhs.w) + || (lhs.w == rhs.w && lhs.z > rhs.z) + || (lhs.w == rhs.w && lhs.z == rhs.z && lhs.y > rhs.y) + || (lhs.w == rhs.w && lhs.z == rhs.z && lhs.y == rhs.y && lhs.x > rhs.x); + } + + // Allow olc::v_4d to play nicely with std::cout + template + inline std::ostream& operator << (std::ostream& os, const v_4d& rhs) + { + os << rhs.str(); + return os; + } + + // Convenient types ready-to-go + typedef v_4d vi4d; + typedef v_4d vu4d; + typedef v_4d vf4d; + typedef v_4d vd4d; +} +#define PGE_VECTOR4D_DECLARED 1 +#endif +//! END DECLARATION \ No newline at end of file diff --git a/dev/tests/blend.png b/dev/tests/blend.png deleted file mode 100644 index 912ff4319e5f5e7ae210426b5b32df4974da0837..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3493 zcmcgvdpwkR7k|dBTx;V+7d6dBD(03E!?+E}D;Xpc7GvhY7>s5HW0HoVwTX1GnuxBv ztwho)tPz)`<_A+WPub8Xb;^41Qw0O8Dp@<7?Zsi93G9qo0x1vL$3w0 z3zJ3*;a~(CQWgP$!J7RE-T!y! zoOchXr63pcCS?`HJB3q%K`%Ou6S8P+5!ekz1TR5kl?jWIN!v4m4%HGn7sA{o@*+k+ z?7*Ua=V<>DNY*TH%Q*?*chT7#CM$OC(R>GafCuJR^UE;$TK-*wQI|PJnP!Ju8G!{;hbrBp3$3p!+UxI}#F(Dvi?oXAR z7YXW9R47>^{}z@dYDwUhbA;b@KD2NC88lI#(S%x_Y(+QnNd4*;+P zI;4dq8UO&pjHrxiXFE47k#(rs8IVLI9yyAmXW6#4Q;-lRHU($!>*4}@`W6V8&WS%bDWe7+#D8bj0dvS$V$_k4s*Lj(iUYmL@ zic2A6oL6n^+*Z0QXQl0w*}1@MpjHjLHC}gxl@iS{Cyr@;IMef?XWchikI5Sy1eOtQ z?N@kqk}I#2SFctHnw=fu!n&R^@;0@ zr(MV5Q(}y!fsNaO>q9(5Jm^PAIda#2HUOw<%5E4S$kqh_rEi_=ZTE7s2XcJUx^}K~ zK~dwMC)eAduK{@{&@~lkT<$ zFUlwTM!H}I#;4O~6q`hWuv65(x}NwS{K>k19%GDFp+_RIqqI$)nCAtG zCDmu!M{XT9)0Q53i%~d~LWP<3W$T3w-F0a!(TJMLJu7Z%xYklBt(izrEYBf?O1%nf zdm8FD!U~l0UdRc62($w4i?OhY%I0;R*)Pcau7II-N>{+i1ZDVXVU|2lHd;D4*cf}r zY*5iPYl{p&p5C>hRd~~_V%E6+iExjpK>{H;VB}eM${-=&m9jv*%1Ag`NRqF-f527f zXTz4$6L;L|5a4=y&W=yAn*IDr`>bh#5LnR>V~-;)t1}B~UW(7DhLH{?nvD;lZ^ubA zvWDo4VBzMziVqV(WPX1@@#@yAQ*T@Ay@C9sPJu0N&C|EDcVzU~YKjcV{KgPZCOgpQVnbf7a1|A%OH;p; znVx`uE<0XX7Mf*v3vf+T&Q2S!XuZJyqH;{(05@Rh+^Yj?%)X}L@05-*hHxd%0>9T0 z1i+dvcUNa}&FdLo&zAZn5P-%Sqj7hY0N=G=64mv|-^Z9%jD|y`KHumzHNMhUtm4h& zKufi-K7q$X>c4CDJ#WMqq*Okv%dEEj+(;OEJF8Z5onX_Xo;5LuR>;+-hL;iQQ+`y# zZTUHje@hSDta*C}JyFrjwXm1rcC4md(lCw>o3g1w3Ag0k;oV_L&7PjwGxB9n%`zjQ z{!@pMQE|glNH6l%h&hU>)w3(1NC03^EWZT;|*`ZyU9N#RZ6@Qiq_*tEpRnN zVXR4kO-|7RpL@KDeL8Vtkd`YBKQj}jU#c6F56!Vj$1%#arPz<@H;l+OeISJw1CDhAqXjQknz-?mWi=c}5P7x^Fivb@e&|FNIV zv8>hg4=&0iEtT6N88B}i$x3VVXzlay?thsm{!g|@i2XkQlpR50>8CB_aSGcVzvI7a z30LyZRR_{^4!*fe!M_-pz7!@Hg=&H0y z(NM8x@?qXAOjGTKdp;F26lawB5TR=0t@c%VGW|qC&FrSZ4P*QQXdo!pyAH7Y#GaQ7mHxh32#JN6ijcGHY9(T>X4ry488tI$H4X>0jf^TZx+8?6suB~IU8vtoP15H z=s)rHZmPvhitY^sIT3D#Tk!#!5tEdk>ZPx*Q>5HB*SNRGcO1H~h8qa$ths`esB%(t zgO~Bdv#)Kv@U!U~@4gcVe3{EeMm`}QTR3KS!c_L}%E@7meUV*2;;)zkp#uN_ diff --git a/dev/tests/olc.png b/dev/tests/olc.png deleted file mode 100644 index fb4a6a3952b86d15f4d3b6e14a4942f961abe680..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2417 zcmai0eN-$%p12yW9Kzp7-}W@9+7z zdqb0v8t6aCA3=~nRhl9Te)HgGik~lhh6k+MfFQmL$((#9U!5-2Qf3~Zqly5}X|_Tf zK_n?oE1@j`3|a&Xq(zGTee??qC3R9PFJ8@8Tjjt=rj^qmyF4RDTVA3S>9CY!e~D8J z3Cw^YP^Z~sv5B2h%*`u?zu9dZL)|7!i4@CMYfw2w12mo&&*Njs{-}i3>BU)!x&4RX zN{Sg7#wx~fhr_{h#PTTGfD1$-5zZIlLLnDgaBXE4hH!E%wg{HO!=V5+ElpY(lCq#I zCs9P%87YQA9PKC9X}#Q5J8g0^>$Es90W+{LHeA3H-~;V}lYA15#nvwu6b@$*XwDyG zZZ%R2WiwJ%ua`k*yzsDt45@#H9~K#A4&{Ej2Pa{Dnz35_|K?`%pljF|WhoS{KM8}b z@YsPZr_2iQEMTMTv=%5!p?VQ+;aFY7a>_)}*|3y=6caqz){stM%2$w3Ya2|G6pKq3 z0u~Gag-LGEE1HK@P3a(M><~ynFhEWN_=L48{@uG^Hm^kkFQuItNuL?Q_FUXU&?Os5P~p@yOvnjo1%cQIsZ2@{auY`2=oepdJDF|7n2Z8I zvH@XagQbJRa5p{M_N+ajWwRm0tTd&wYk|(45cg1EQ5&Ua90Uzy23W;XOs3b9FmGk3 zhV5olhzjAP@)Vr+h{FgV-LOK7jqNWq{HejX`|6p&c+h7B1BV-FumE~!Jf2hDc%G5$ z{&+k}Kh{cu4^z`)Fkq0vdwBNXfQFME_l|nF@V+d71$G?`Uj>W)T;72oJ}XJsS;IOC zL1t{QZ#XhvE(@wk2|2ONZ-0}fn$yVn-cL5UraG)n$?2$9z08~1d8K{i=$2KDJJFw< zq4&d#(fI+ZYW<2D8*A6Cxz-jL6dZf{=Bp1bzE3#D$8_v|=WNL+bkn!R4^=y~#L}-a zAI*zMxXGLg&0_2c{NUXmFTa$nJ-PUBLH+$B6^$ZJcW50M6rP*Ijh0N4G;hvTUkbM5 zaduVTay89=H^9HgRo;3WpWZpY&wOC~zi(B3q`naR=)~p1hRA~(o7$!rX1cP!z9T3+ z%Ta$fD)M%;RN8#qz2@muOd z`@%O(2$9Z=P+hwDsOR(R`=>70S6(yj`Uinm+QY6N-qi#q6sE4pO!B=EvSd&6J(qmK z!=UB$wW4vyT70hMg>u^h!nSq&Q@7@zCiB4B#YF<=tkpj#n|fxSqxTv9Se_A%w(<(? zfy*9rMO`yTr&M+udXxJq=E7I{Xlq)54M74z*pCmgb^8REpm>!+mXp7_J5R>>lJB=< z`+BNZmVA2Dz3I49bAC^daZ=IISZQ43#;(}I&x1;r?k1A2yWGUcsmS9xv{l(3|G{dhn*voR^+LROTz zvLvqS?U%9^{<voK4oeeWHUV7j`Jmr+4;_VzA{sYMsHVps( diff --git a/dev/tests/test_main.cpp b/dev/tests/test_main.cpp deleted file mode 100644 index 31b680cd..00000000 --- a/dev/tests/test_main.cpp +++ /dev/null @@ -1,20 +0,0 @@ - -extern "C" void test_pixels(); -extern "C" void test_vector2d(); -extern "C" void test_matrices(); - -#include "api_opengl.h" - -int main() -{ - test_pixels(); - test_vector2d(); - test_matrices(); - - - auto& gl = olc::apis::opengl::gl::Get(); - - gl.glBindBuffer(0, 0); - - return 0; -} \ No newline at end of file diff --git a/dev/tests/test_matrices.cpp b/dev/tests/test_matrices.cpp deleted file mode 100644 index 7e393d4d..00000000 --- a/dev/tests/test_matrices.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include -#include -#include - -#include "../src/config.h" -#include "../src/matrix3d.h" -#include "../src/transform2d.h" - -extern "C" void test_matrices() -{ - olc::mf3d mat1; - - mat1.identity(); - - std::cout << mat1.str() << std::endl; - - olc::tf2d trans1; - trans1.translate(olc::vf2d{ 2, 3 }); - trans1.rotate(0.6f); - - std::cout << trans1.forward_matrix() << std::endl; - - -} \ No newline at end of file diff --git a/dev/tests/test_mh.cpp b/dev/tests/test_mh.cpp index 3926cfe8..03b98227 100644 --- a/dev/tests/test_mh.cpp +++ b/dev/tests/test_mh.cpp @@ -149,16 +149,101 @@ class Example : public olc::PixelGameEngine {64, 64 }*/ }; + cube = CreateSanityCube(); + + CreateImageFromFile(imSanityCube, "../../examples/assets/sanity_cube.png"); + return true; } + struct mesh + { + std::vector pos; + std::vector norm; + std::vector uv; + std::vector col; + olc::Structure layout = olc::Structure::List; + }; + + olc::Image imSanityCube; + + + inline mesh CreateSanityCube() + { + mesh m; + + + /* 5 6 + 1 2 + + 4 7 + 0 3 + + */ + + m.layout = olc::Structure::List; + + // South + m.pos.push_back({ 0,0,0 }); m.norm.push_back({ 0, 0, -1, 0 }); m.uv.push_back({ 0.25, 0.5 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 0,1,0 }); m.norm.push_back({ 0, 0, -1, 0 }); m.uv.push_back({ 0.25, 0.25 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 1,1,0 }); m.norm.push_back({ 0, 0, -1, 0 }); m.uv.push_back({ 0.5, 0.25 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 0,0,0 }); m.norm.push_back({ 0, 0, -1, 0 }); m.uv.push_back({ 0.25, 0.5 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 1,1,0 }); m.norm.push_back({ 0, 0, -1, 0 }); m.uv.push_back({ 0.5, 0.25 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 1,0,0 }); m.norm.push_back({ 0, 0, -1, 0 }); m.uv.push_back({ 0.5, 0.5 }); m.col.push_back(olc::Colour::WHITE); + + // East + m.pos.push_back({ 1,0,0 }); m.norm.push_back({ 1, 0, 0, 0 }); m.uv.push_back({ 0.5, 0.5 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 1,1,0 }); m.norm.push_back({ 1, 0, 0, 0 }); m.uv.push_back({ 0.5, 0.25 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 1,1,1 }); m.norm.push_back({ 1, 0, 0, 0 }); m.uv.push_back({ 0.75, 0.25 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 1,0,0 }); m.norm.push_back({ 1, 0, 0, 0 }); m.uv.push_back({ 0.5, 0.5 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 1,1,1 }); m.norm.push_back({ 1, 0, 0, 0 }); m.uv.push_back({ 0.75, 0.25 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 1,0,1 }); m.norm.push_back({ 1, 0, 0, 0 }); m.uv.push_back({ 0.75, 0.5 }); m.col.push_back(olc::Colour::WHITE); + + // North + m.pos.push_back({ 1,0,1 }); m.norm.push_back({ 0, 0, 1, 0 }); m.uv.push_back({ 0.75, 0.5 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 1,1,1 }); m.norm.push_back({ 0, 0, 1, 0 }); m.uv.push_back({ 0.75, 0.25 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 0,1,1 }); m.norm.push_back({ 0, 0, 1, 0 }); m.uv.push_back({ 1.0, 0.25 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 1,0,1 }); m.norm.push_back({ 0, 0, 1, 0 }); m.uv.push_back({ 0.75, 0.5 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 0,1,1 }); m.norm.push_back({ 0, 0, 1, 0 }); m.uv.push_back({ 1.0, 0.25 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 0,0,1 }); m.norm.push_back({ 0, 0, 1, 0 }); m.uv.push_back({ 1.0, 0.5 }); m.col.push_back(olc::Colour::WHITE); + + // West + m.pos.push_back({ 0,0,1 }); m.norm.push_back({ -1, 0, 0, 0 }); m.uv.push_back({ 0.0, 0.5 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 0,1,1 }); m.norm.push_back({ -1, 0, 0, 0 }); m.uv.push_back({ 0.0, 0.25 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 0,1,0 }); m.norm.push_back({ -1, 0, 0, 0 }); m.uv.push_back({ 0.25, 0.25 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 0,0,1 }); m.norm.push_back({ -1, 0, 0, 0 }); m.uv.push_back({ 0.0, 0.5 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 0,1,0 }); m.norm.push_back({ -1, 0, 0, 0 }); m.uv.push_back({ 0.25, 0.25 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 0,0,0 }); m.norm.push_back({ -1, 0, 0, 0 }); m.uv.push_back({ 0.25, 0.5 }); m.col.push_back(olc::Colour::WHITE); + + // Top + m.pos.push_back({ 0,1,0 }); m.norm.push_back({ 0, 1, 0, 0 }); m.uv.push_back({ 0.25, 0.25 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 0,1,1 }); m.norm.push_back({ 0, 1, 0, 0 }); m.uv.push_back({ 0.25, 0.0 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 1,1,1 }); m.norm.push_back({ 0, 1, 0, 0 }); m.uv.push_back({ 0.5, 0.0 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 0,1,0 }); m.norm.push_back({ 0, 1, 0, 0 }); m.uv.push_back({ 0.25, 0.25 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 1,1,1 }); m.norm.push_back({ 0, 1, 0, 0 }); m.uv.push_back({ 0.5, 0.0 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 1,1,0 }); m.norm.push_back({ 0, 1, 0, 0 }); m.uv.push_back({ 0.5, 0.25 }); m.col.push_back(olc::Colour::WHITE); + + // Bottom + m.pos.push_back({ 0,0,0 }); m.norm.push_back({ 0, -1, 0, 0 }); m.uv.push_back({ 0.25, 0.5 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 1,0,0 }); m.norm.push_back({ 0, -1, 0, 0 }); m.uv.push_back({ 0.5, 0.5 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 1,0,1 }); m.norm.push_back({ 0, -1, 0, 0 }); m.uv.push_back({ 0.5, 0.75 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 0,0,0 }); m.norm.push_back({ 0, -1, 0, 0 }); m.uv.push_back({ 0.25, 0.5 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 1,0,1 }); m.norm.push_back({ 0, -1, 0, 0 }); m.uv.push_back({ 0.5, 0.75 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 0,0,1 }); m.norm.push_back({ 0, -1, 0, 0 }); m.uv.push_back({ 0.25, 0.75 }); m.col.push_back(olc::Colour::WHITE); + + return m; + } + + mesh cube; + olc::vf4d vCubePos = { 0,0,-5 }; + bool OnUserUpdate(float fElapsedTime) override { //draw.Line({ 0,0 }, mouse.GetPosition(), olc::Colour::WHITE); //return true; - //if (mouse.GetButton(1).bHeld) + if (mouse.GetButton(1).bHeld) { fAngle += 0.5f * fElapsedTime; } @@ -266,7 +351,7 @@ class Example : public olc::PixelGameEngine vecColours); - draw.Image(imTempBuffer, { 64, 48 }); + //draw.Image(imTempBuffer, { 64, 48 }); draw.ImageQuad(imTempBuffer, vecTestPoints); //draw.Triangle(vecTestPoints[0], vecTestPoints[1], vecTestPoints[2], olc::Colour::BLACK); @@ -281,6 +366,62 @@ class Example : public olc::PixelGameEngine draw.Circle(vecTestPoints[i], 4, olc::Colour::BLACK, olc::Colour::WHITE, 16); } + + + // Testing matrices + olc::mf4d t1, t2, t3; + t1.translate(0.0f, 3.0f, 5.0f); + t2.translate(0.0f, 6.0f, 0.0f); + t3.translate(7.0f, 0.0f, 0.0f); + + olc::vf4d v1 = { 1,0,0,1 }; + olc::vf4d v2 = t3 * t2 * t1 * v1; + + + olc::mf4d matProj; + olc::mf4d matView; + olc::mf4d matWorld; + + draw3d.SetViewport({ 0,0 }, GetScreen().Size()); + draw3d.MatrixReset(); + matProj.perspective(90.0f * 3.14159f / 180.0f, float(ScreenSize().x) / float(ScreenSize().y), 0.1f, 1000.0f); + + draw3d.SetProjectionMatrix(matProj); + draw3d.SetViewMatrix(matView); + + if (keyboard.GetKey(olc::Key::LEFT).bHeld) + vCubePos.x -= 5.0f * fElapsedTime; + if (keyboard.GetKey(olc::Key::RIGHT).bHeld) + vCubePos.x += 5.0f * fElapsedTime; + if (keyboard.GetKey(olc::Key::UP).bHeld) + vCubePos.y += 5.0f * fElapsedTime; + if (keyboard.GetKey(olc::Key::DOWN).bHeld) + vCubePos.y -= 5.0f * fElapsedTime; + if (keyboard.GetKey(olc::Key::Q).bHeld) + vCubePos.z += 5.0f * fElapsedTime; + if (keyboard.GetKey(olc::Key::A).bHeld) + vCubePos.z -= 5.0f * fElapsedTime; + + matWorld.translate(vCubePos); + draw3d.SetModelMatrix(matWorld); + + for(int x = 0; x < 10; x++) + for (int y = 0; y < 10; y++) + for (int z = 0; z < 10; z++) + { + matWorld.translate(vCubePos + olc::vf4d(float(x) * 2.0f, float(y) * 2.0f, float(z) * 2.0f, 0)); + draw3d.SetModelMatrix(matWorld); + //draw3d.SetMVPMatrix(matProj * matView * matWorld); + draw3d.Mesh(cube.layout, cube.pos, cube.col, cube.uv, imSanityCube); + } + + + //draw3d.Mesh(cube.layout, cube.pos, cube.col); + + draw3d.Line({ 0.0f, 0.0f, 0.0f },{ 1.0f, 0.0f, 0.0f },olc::Colour::RED); + draw3d.Line({ 0.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }, olc::Colour::GREEN); + draw3d.Line({ 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 1.0f }, olc::Colour::BLUE); + // Handle mouse @@ -619,8 +760,9 @@ int main() cfg.vPixelSize = { 1,1 }; cfg.vScreenSize = { 1024, 960 }; - cfg.vPixelSize = { 4,4 }; - cfg.vScreenSize = { 256, 240 }; + //cfg.vPixelSize = { 4,4 }; + //cfg.vScreenSize = { 256, 240 }; + //cfg.bAntiAliasMainScreen = true; cfg.bVSync = false; //if (demo.Construct({ 1280, 960 }, { 1, 1 }, cfg)) diff --git a/dev/tests/test_pixels.cpp b/dev/tests/test_pixels.cpp deleted file mode 100644 index a68fcb5d..00000000 --- a/dev/tests/test_pixels.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include -#include -#include - -#include "../src/config.h" -#include "../src/pixel.h" - -extern "C" void test_pixels() -{ - olc::Pixel p1; - olc::Pixel p2(olc::Colour::TANGERINE); - olc::Pixel p3(0x22446688); - olc::Pixel p4(100, 200, 50, 255); - olc::Pixel p5 = p2; - olc::Pixel p6(p5); - - olc::Pixel p7 = p2 + p3; - olc::Pixel p8 = p3 - p2; - olc::Pixel p9 = p2 * 2.0f; - olc::Pixel p10 = p2 * 0.5f; - olc::Pixel p11 = p2 * 2; - olc::Pixel p12 = p2 / 2; - - p2 += p3; - p2 -= p3; - p3 *= 2.0f; - p3 /= 2.0f; - p4 /= 2; - p4 *= 2; - - assert(p6 == p5); - assert(p6 != p4); - - olc::Pixel p13 = olc::PixelF(0.5f, 1.0f, 0.2f, 1.0f); - olc::Pixel p14 = olc::PixelHSV(270.0f, 0.5f, 0.5f); - - - std::set s; - s.insert(p1); - s.insert(p2); - s.insert(p5); - s.insert(p6); - assert(s.size() == 3); - - std::cout << olc::Colour::TANGERINE << "\n"; -} \ No newline at end of file diff --git a/dev/tests/test_vector2d.cpp b/dev/tests/test_vector2d.cpp deleted file mode 100644 index c696c5ed..00000000 --- a/dev/tests/test_vector2d.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#include -#include -#include - -#include "../src/config.h" -#include "../src/vector2d.h" - -extern "C" void test_vector2d() -{ - olc::vf2d v1 = { 3, 4 }; - v1.mag(); - - v1.xy[0] = 11; - - auto s = sizeof(olc::vf2d); - - std::cout << v1 << "\n"; -} \ No newline at end of file diff --git a/examples/olcPGE3_3DCube.cpp b/examples/olcPGE3_3DCube.cpp new file mode 100644 index 00000000..19009e46 --- /dev/null +++ b/examples/olcPGE3_3DCube.cpp @@ -0,0 +1,231 @@ +/* + olc::PixelGameEngine3 Example - olc::SanityCube!!! + + Draws teh infamous olc::SanityCube using the hardware 3D rendering capabilities. + + Licenced under the OLC-3 License +*/ + + +// Define OLC_PGE3_APPLICATION to include the implementation of +// the Pixel Game Engine as part of this translation unit +#define OLC_PGE3_APPLICATION +#include "../olcPixelGameEngine3.h" + +// Example application demonstrating basic 3D rendering. This class +// overrides the olc::PixelGameEngine base class by implementing +// the OnUserCreate() and OnUserUpdate() functions +class Example_3DCube : public olc::PixelGameEngine +{ +public: + Example_3DCube() + { + + } + +protected: + + // Matrices for 3D projection, view and world transforms + olc::mf4d matProj; + olc::mf4d matView; + olc::mf4d matWorld; + + // Position in 3D space of "the camera" + olc::vf4d vViewTranslate = { 0.0f, 0.0f, 2.5f }; + +public: + // Called once at the start, so create things here + bool OnUserCreate() override + { + // Load the image for the sanity cube, and create the mesh for it + CreateImageFromFile(imSanityCube, "./assets/sanity_cube.png"); + meshSanityCube = CreateSanityCube(); + + // Only needs setting once, as the projection matrix doesn't change in this example + matProj.perspective(90.0f * 3.14159f / 180.0f, float(ScreenSize().x) / float(ScreenSize().y), 0.1f, 100.0f); + draw3d.SetProjectionMatrix(matProj); + + return true; + } + + // Called every frame, so update things here + bool OnUserUpdate(float fElapsedTime) override + { + // Handle some input to coarsely move the 3D view around. Note + // we have no notion of a camera without any additonal utilities + if (keyboard.GetKey(olc::Key::LEFT).bHeld) + vViewTranslate.x -= 5.0f * fElapsedTime; + if (keyboard.GetKey(olc::Key::RIGHT).bHeld) + vViewTranslate.x += 5.0f * fElapsedTime; + if (keyboard.GetKey(olc::Key::UP).bHeld) + vViewTranslate.y += 5.0f * fElapsedTime; + if (keyboard.GetKey(olc::Key::DOWN).bHeld) + vViewTranslate.y -= 5.0f * fElapsedTime; + if (keyboard.GetKey(olc::Key::Q).bHeld) + vViewTranslate.z += 5.0f * fElapsedTime; + if (keyboard.GetKey(olc::Key::A).bHeld) + vViewTranslate.z -= 5.0f * fElapsedTime; + + if (keyboard.GetKey(olc::Key::SPACE).bPressed) + bSpinning = !bSpinning; + + + // Clear whole screen (and depth buffer - very important!) + draw.Clear(olc::Colour::VERY_DARK_BLUE); + + // Draw Background Gradient using draw2d, to show that + // draw3d doesn't mess with draw2d's state + draw.FilledRect({ 0, 0 }, draw.GetTargetSize(), + olc::Colour::WHITE, olc::Colour::YELLOW, + olc::Colour::CYAN, olc::Colour::MAGENTA); + + // Construct the view matrix. Now... apologies... + // PGE3 has a left-handed coordinate system, with the camera looking down the + // positive Z axis. This is a bit unusual, but it means that the default view + // matrix needs to be rotated 180 degrees around the X axis. + olc::mf4d matViewRotateX, matViewTranslate; + matViewRotateX.rotateX(3.14159f); + matViewTranslate.translate(vViewTranslate); + matView = matViewRotateX * matViewTranslate; + draw3d.SetViewMatrix(matView); + + // Create a world matrix that rotates the cube over time. The cube is offset + // so it rotates around its centre. Its verts are defined in the range 0..1 + olc::mf4d matTrans, matRotX, matRotY; + matTrans.translate(-0.5f, -0.5f, -0.5f); + if (bSpinning) + { + matRotX.rotateX(TotalTimeElapsed() * 0.5f); + matRotY.rotateY(TotalTimeElapsed() * 0.25f); + } + + // Combine transformations & Apply: + // 1. Translate the cube so its centre is at the origin + // 2. Rotate the cube around the Y axis + // 3. Rotate the cube around the X axis + matWorld = matRotX * matRotY * matTrans; + draw3d.SetModelMatrix(matWorld); + + // The olc::SanityCube (TM) (c) is defined with vertices in clockwise order, + // so cull counter-clockwise faces to show it off in all its glory! This is + // counter to OpenGL's default culling mode, so it's a good test of the culling + // system as well. + draw3d.SetCullMode(olc::GPUTask::CullMode::CounterClockWise); + + // Draw the cube using the sanity cube's layout, and vectors of vertices, colours + // and texture coordinates. + draw3d.Mesh(meshSanityCube.layout, meshSanityCube.pos, meshSanityCube.col, meshSanityCube.uv, imSanityCube); + + // Draw a little RGB axis indicator + matWorld.translate(-1,-1,-1); + draw3d.SetModelMatrix(matWorld); + draw3d.Line({ 0,0,0 }, { 1, 0, 0 }, olc::Colour::RED); + draw3d.Line({ 0,0,0 }, { 0, 1, 0 }, olc::Colour::GREEN); + draw3d.Line({ 0,0,0 }, { 0, 0, 1 }, olc::Colour::BLUE); + + draw.StringProp({ 4, 4 }, "+X: Right\n-X: Left\n+Y: Up\n-Y: Down\n+Z: Q\n-Z: A\nSPIN: Space", olc::Colour::BLACK); + + // Successful frame + return true; + } + + // A simple mesh structure, containing vectors of vertex attributes + struct mesh + { + std::vector pos; + std::vector norm; + std::vector uv; + std::vector col; + olc::Structure layout = olc::Structure::List; + }; + + // The image and mesh for the sanity cube + olc::Image imSanityCube; + mesh meshSanityCube; + + bool bSpinning = false; + + + // Behold!! The Sanity Cube!! A cube with all the correct vertex attributes, to + // be used as a sanity check for the 3D rendering pipeline. + // If this doesn't render correctly, then nothing will. + inline mesh CreateSanityCube() + { + mesh m; + m.layout = olc::Structure::List; + + // South + m.pos.push_back({ 0,0,0 }); m.norm.push_back({ 0, 0, -1, 0 }); m.uv.push_back({ 0.25, 0.5 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 0,1,0 }); m.norm.push_back({ 0, 0, -1, 0 }); m.uv.push_back({ 0.25, 0.25 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 1,1,0 }); m.norm.push_back({ 0, 0, -1, 0 }); m.uv.push_back({ 0.5, 0.25 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 0,0,0 }); m.norm.push_back({ 0, 0, -1, 0 }); m.uv.push_back({ 0.25, 0.5 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 1,1,0 }); m.norm.push_back({ 0, 0, -1, 0 }); m.uv.push_back({ 0.5, 0.25 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 1,0,0 }); m.norm.push_back({ 0, 0, -1, 0 }); m.uv.push_back({ 0.5, 0.5 }); m.col.push_back(olc::Colour::WHITE); + + // East + m.pos.push_back({ 1,0,0 }); m.norm.push_back({ 1, 0, 0, 0 }); m.uv.push_back({ 0.5, 0.5 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 1,1,0 }); m.norm.push_back({ 1, 0, 0, 0 }); m.uv.push_back({ 0.5, 0.25 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 1,1,1 }); m.norm.push_back({ 1, 0, 0, 0 }); m.uv.push_back({ 0.75, 0.25 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 1,0,0 }); m.norm.push_back({ 1, 0, 0, 0 }); m.uv.push_back({ 0.5, 0.5 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 1,1,1 }); m.norm.push_back({ 1, 0, 0, 0 }); m.uv.push_back({ 0.75, 0.25 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 1,0,1 }); m.norm.push_back({ 1, 0, 0, 0 }); m.uv.push_back({ 0.75, 0.5 }); m.col.push_back(olc::Colour::WHITE); + + // North + m.pos.push_back({ 1,0,1 }); m.norm.push_back({ 0, 0, 1, 0 }); m.uv.push_back({ 0.75, 0.5 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 1,1,1 }); m.norm.push_back({ 0, 0, 1, 0 }); m.uv.push_back({ 0.75, 0.25 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 0,1,1 }); m.norm.push_back({ 0, 0, 1, 0 }); m.uv.push_back({ 1.0, 0.25 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 1,0,1 }); m.norm.push_back({ 0, 0, 1, 0 }); m.uv.push_back({ 0.75, 0.5 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 0,1,1 }); m.norm.push_back({ 0, 0, 1, 0 }); m.uv.push_back({ 1.0, 0.25 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 0,0,1 }); m.norm.push_back({ 0, 0, 1, 0 }); m.uv.push_back({ 1.0, 0.5 }); m.col.push_back(olc::Colour::WHITE); + + // West + m.pos.push_back({ 0,0,1 }); m.norm.push_back({ -1, 0, 0, 0 }); m.uv.push_back({ 0.0, 0.5 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 0,1,1 }); m.norm.push_back({ -1, 0, 0, 0 }); m.uv.push_back({ 0.0, 0.25 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 0,1,0 }); m.norm.push_back({ -1, 0, 0, 0 }); m.uv.push_back({ 0.25, 0.25 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 0,0,1 }); m.norm.push_back({ -1, 0, 0, 0 }); m.uv.push_back({ 0.0, 0.5 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 0,1,0 }); m.norm.push_back({ -1, 0, 0, 0 }); m.uv.push_back({ 0.25, 0.25 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 0,0,0 }); m.norm.push_back({ -1, 0, 0, 0 }); m.uv.push_back({ 0.25, 0.5 }); m.col.push_back(olc::Colour::WHITE); + + // Top + m.pos.push_back({ 0,1,0 }); m.norm.push_back({ 0, 1, 0, 0 }); m.uv.push_back({ 0.25, 0.25 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 0,1,1 }); m.norm.push_back({ 0, 1, 0, 0 }); m.uv.push_back({ 0.25, 0.0 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 1,1,1 }); m.norm.push_back({ 0, 1, 0, 0 }); m.uv.push_back({ 0.5, 0.0 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 0,1,0 }); m.norm.push_back({ 0, 1, 0, 0 }); m.uv.push_back({ 0.25, 0.25 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 1,1,1 }); m.norm.push_back({ 0, 1, 0, 0 }); m.uv.push_back({ 0.5, 0.0 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 1,1,0 }); m.norm.push_back({ 0, 1, 0, 0 }); m.uv.push_back({ 0.5, 0.25 }); m.col.push_back(olc::Colour::WHITE); + + // Bottom + m.pos.push_back({ 0,0,0 }); m.norm.push_back({ 0, -1, 0, 0 }); m.uv.push_back({ 0.25, 0.5 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 1,0,0 }); m.norm.push_back({ 0, -1, 0, 0 }); m.uv.push_back({ 0.5, 0.5 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 1,0,1 }); m.norm.push_back({ 0, -1, 0, 0 }); m.uv.push_back({ 0.5, 0.75 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 0,0,0 }); m.norm.push_back({ 0, -1, 0, 0 }); m.uv.push_back({ 0.25, 0.5 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 1,0,1 }); m.norm.push_back({ 0, -1, 0, 0 }); m.uv.push_back({ 0.5, 0.75 }); m.col.push_back(olc::Colour::WHITE); + m.pos.push_back({ 0,0,1 }); m.norm.push_back({ 0, -1, 0, 0 }); m.uv.push_back({ 0.25, 0.75 }); m.col.push_back(olc::Colour::WHITE); + + return m; + } + + + +}; + + +// Main entry point for the application +int main() +{ + // Construct demo application + Example_3DCube demo; + + olc::PGEConfig config; + config.vScreenSize = { 256, 240 }; + config.vPixelSize = { 4, 4 }; + config.bAntiAliasMainScreen = false; + + if (demo.Construct(config)) + { + // Start the application + demo.Start(); + } + + return 0; +} \ No newline at end of file diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index f0733f93..e5638503 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -100,6 +100,9 @@ and the many community contributors that have provided bug fixes, suggestions, criticisms, and encouragement from the OneLoneCoder Discord server, YouTube & GitHub. + Saladin, if you're out there, I know you would have been a proud contributer to this. + I miss you buddy. + Version History ~~~~~~~~~~~~~~~ v3.00: It begins... @@ -947,6 +950,338 @@ namespace olc #define PGE_VECTOR2D_DECLARED 1 #endif +#if !defined(PGE_VECTOR4D_DECLARED) +namespace olc +{ + /* + A complete 4D geometric vector structure, with a variety + of useful utility functions and operator overloads. + */ + template + struct v_4d + { + static_assert(std::is_arithmetic::value, "olc::v_4d must be numeric"); + + union + { +#pragma warning(disable:4201) // Top MSVC whinging about anonymous structs + struct + { + // x-axis component + T x; + // y-axis component + T y; + // z-axis component + T z; + // w-axis component + T w; + }; +#pragma warning(default:4201) + + std::array xyzw = { {0,0,0,1} }; + }; + + // Default constructor + inline constexpr v_4d() = default; + + // Specific constructor + inline constexpr v_4d(T _x, T _y, T _z, T _w = 1) : x(_x), y(_y), z(_z), w(_w) + {} + + inline constexpr v_4d(const v_2d& v, T _z, T _w) : x(v.x), y(v.y), z(_z), w(_w) + {} + + inline constexpr v_4d(const v_2d& v1, const v_2d& v2) : x(v1.x), y(v1.y), z(v2.x), w(v2.y) + {} + + // Copy constructor + inline constexpr v_4d(const v_4d& v) = default; + + // Assignment operator + inline constexpr v_4d& operator=(const v_4d& v) = default; + + + inline constexpr std::array a() const + { + return xyzw; + } + + inline constexpr v_2d xy() const + { + return v_2d(x, y); + } + + inline constexpr v_2d zw() const + { + return v_2d(z, w); + } + + // Returns magnitude of vector + inline constexpr auto mag() const + { + return std::sqrt(x * x + y * y + z * z + w * w); + } + + // Returns magnitude squared of vector (useful for fast comparisons) + inline constexpr T mag2() const + { + return x * x + y * y + z * z + w * w; + } + + // Returns normalised version of vector + inline constexpr v_4d norm() const + { + auto r = 1 / mag(); + return v_4d(x * r, y * r, z * r, w * r); + } + + // Rounds all components down + inline constexpr v_4d floor() const + { + return v_4d(std::floor(x), std::floor(y), std::floor(z), std::floor(w)); + } + + // Rounds all components accurately + inline constexpr v_4d round() const + { + return v_4d(std::round(x), std::round(y), std::round(z), std::round(w)); + } + + // Rounds all components up + inline constexpr v_4d ceil() const + { + return v_4d(std::ceil(x), std::ceil(y), std::ceil(z), std::ceil(w)); + } + + // Returns 'element-wise' max of this and another vector + inline constexpr v_4d max(const v_4d& v) const + { + return v_4d(std::max(x, v.x), std::max(y, v.y), std::max(z, v.z), std::max(w, v.w)); + } + + // Returns 'element-wise' min of this and another vector + inline constexpr v_4d min(const v_4d& v) const + { + return v_4d(std::min(x, v.x), std::min(y, v.y), std::min(z, v.z), std::min(w, v.w)); + } + + // Returns 'element-wise' abs of this vector + inline constexpr v_4d abs() const + { + return v_4d(std::abs(x), std::abs(y), std::abs(z), std::abs(w)); + } + + // Calculates scalar dot product between this and another vector + inline constexpr auto dot(const v_4d& rhs) const + { + return this->x * rhs.x + this->y * rhs.y + this->z * rhs.z + this->w * rhs.w; + } + + // Calculates cross product between this and another vector + inline constexpr v_4d cross(const v_4d& rhs) const + { + return v_4d(this->y * rhs.z - this->z * rhs.y, this->z * rhs.x - this->x * rhs.z, this->x * rhs.y - this->y * rhs.x, 0); + } + + // Clamp the components of this vector in between the 'element-wise' minimum and maximum of 2 other vectors + inline constexpr v_4d clamp(const v_4d& v1, const v_4d& v2) const + { + return this->max(v1).min(v2); + } + + // Linearly interpolate between this vector, and another vector, given normalised parameter 't' + inline constexpr v_4d lerp(const v_4d& v1, const double t) const + { + return (*this) * (T(1.0 - t)) + (v1 * T(t)); + } + + // Compare if this vector is numerically equal to another + inline constexpr bool operator == (const v_4d& rhs) const + { + return (this->x == rhs.x && this->y == rhs.y && this->z == rhs.z && this->w == rhs.w); + } + + // Compare if this vector is not numerically equal to another + inline constexpr bool operator != (const v_4d& rhs) const + { + return (this->x != rhs.x || this->y != rhs.y || this->z != rhs.z || this->w != rhs.w); + } + + // Return this vector as a std::string, of the form "(x,y,z,w)" + inline std::string str() const + { + return std::string("(") + std::to_string(this->x) + "," + std::to_string(this->y) + "," + std::to_string(this->z) + "," + std::to_string(this->w) + ")"; + } + + // Allow 'casting' from other v_4d types + template + inline constexpr operator v_4d() const + { + return { static_cast(this->x), static_cast(this->y), static_cast(this->z), static_cast(this->w) }; + } + }; + + // Multiplication operator overloads between vectors and scalars, and vectors and vectors + template + inline constexpr auto operator * (const TL& lhs, const v_4d& rhs) + { + return v_4d(lhs * rhs.x, lhs * rhs.y, lhs * rhs.z, lhs * rhs.w); + } + + template + inline constexpr auto operator * (const v_4d& lhs, const TR& rhs) + { + return v_4d(lhs.x * rhs, lhs.y * rhs, lhs.z * rhs, lhs.w * rhs); + } + + template + inline constexpr auto operator * (const v_4d& lhs, const v_4d& rhs) + { + return v_4d(lhs.x * rhs.x, lhs.y * rhs.y, lhs.z * rhs.z, lhs.w * rhs.w); + } + + template + inline constexpr auto operator *= (v_4d& lhs, const TR& rhs) + { + lhs = lhs * rhs; + return lhs; + } + + // Division operator overloads between vectors and scalars, and vectors and vectors + template + inline constexpr auto operator / (const TL& lhs, const v_4d& rhs) + { + return v_4d(lhs / rhs.x, lhs / rhs.y, lhs / rhs.z, lhs / rhs.w); + } + + template + inline constexpr auto operator / (const v_4d& lhs, const TR& rhs) + { + return v_4d(lhs.x / rhs, lhs.y / rhs, lhs.z / rhs, lhs.w / rhs); + } + + template + inline constexpr auto operator / (const v_4d& lhs, const v_4d& rhs) + { + return v_4d(lhs.x / rhs.x, lhs.y / rhs.y, lhs.z / rhs.z, lhs.w / rhs.w); + } + + template + inline constexpr auto operator /= (v_4d& lhs, const TR& rhs) + { + lhs = lhs / rhs; + return lhs; + } + + // Unary Addition operator (pointless but i like the platinum trophies) + template + inline constexpr auto operator + (const v_4d& lhs) + { + return v_4d(+lhs.x, +lhs.y, +lhs.z, +lhs.w); + } + + // Addition operator overloads between vectors and scalars, and vectors and vectors + template + inline constexpr auto operator + (const TL& lhs, const v_4d& rhs) + { + return v_4d(lhs + rhs.x, lhs + rhs.y, lhs + rhs.z, lhs + rhs.w); + } + + template + inline constexpr auto operator + (const v_4d& lhs, const TR& rhs) + { + return v_4d(lhs.x + rhs, lhs.y + rhs, lhs.z + rhs, lhs.w + rhs); + } + + template + inline constexpr auto operator + (const v_4d& lhs, const v_4d& rhs) + { + return v_4d(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z, lhs.w + rhs.w); + } + + template + inline constexpr auto operator += (v_4d& lhs, const TR& rhs) + { + lhs = lhs + rhs; + return lhs; + } + + template + inline constexpr auto operator += (v_4d& lhs, const v_4d& rhs) + { + lhs = lhs + rhs; + return lhs; + } + + // Unary negation operator overoad for inverting a vector + template + inline constexpr auto operator - (const v_4d& lhs) + { + return v_4d(-lhs.x, -lhs.y, -lhs.z, -lhs.w); + } + + // Subtraction operator overloads between vectors and scalars, and vectors and vectors + template + inline constexpr auto operator - (const TL& lhs, const v_4d& rhs) + { + return v_4d(lhs - rhs.x, lhs - rhs.y, lhs - rhs.z, lhs - rhs.w); + } + + template + inline constexpr auto operator - (const v_4d& lhs, const TR& rhs) + { + return v_4d(lhs.x - rhs, lhs.y - rhs, lhs.z - rhs, lhs.w - rhs); + } + + template + inline constexpr auto operator - (const v_4d& lhs, const v_4d& rhs) + { + return v_4d(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z, lhs.w - rhs.w); + } + + template + inline constexpr auto operator -= (v_4d& lhs, const TR& rhs) + { + lhs = lhs - rhs; + return lhs; + } + + // Greater/Less-Than Operator overloads - mathematically useless, but handy for "sorted" container storage + template + inline constexpr bool operator < (const v_4d& lhs, const v_4d& rhs) + { + return (lhs.w < rhs.w) + || (lhs.w == rhs.w && lhs.z < rhs.z) + || (lhs.w == rhs.w && lhs.z == rhs.z && lhs.y < rhs.y) + || (lhs.w == rhs.w && lhs.z == rhs.z && lhs.y == rhs.y && lhs.x < rhs.x); + } + + template + inline constexpr bool operator > (const v_4d& lhs, const v_4d& rhs) + { + return (lhs.w > rhs.w) + || (lhs.w == rhs.w && lhs.z > rhs.z) + || (lhs.w == rhs.w && lhs.z == rhs.z && lhs.y > rhs.y) + || (lhs.w == rhs.w && lhs.z == rhs.z && lhs.y == rhs.y && lhs.x > rhs.x); + } + + // Allow olc::v_4d to play nicely with std::cout + template + inline std::ostream& operator << (std::ostream& os, const v_4d& rhs) + { + os << rhs.str(); + return os; + } + + // Convenient types ready-to-go + typedef v_4d vi4d; + typedef v_4d vu4d; + typedef v_4d vf4d; + typedef v_4d vd4d; +} +#define PGE_VECTOR4D_DECLARED 1 +#endif + #if !defined(PGE_MATRIX3D_DECLARED) namespace olc { @@ -1175,6 +1510,357 @@ namespace olc #define PGE_MATRIX3D_DECLARED 1 #endif +#if !defined(PGE_MATRIX4D_DECLARED) +namespace olc +{ + + /* + A complete 4x4 Matrix structure, with a variety + of useful utility functions and operator overloads + specifically targeting 3D graphical transformations + + as per https://en.wikipedia.org/wiki/Transformation_matrix + + Access: column, row + */ + + /* + + + Because Matrices can be defined all sort sof ways, I have included this little + description to clarify how this particular implementation works. For the end + user's ease of use, the transformations are designed to mimic those found on + Wikipedias page on transformation matrices, which are in column-major order. + + Memory layout of the 4x4 matrix is as follows idx = R * 4 + C: + + 0x00 0x01 0x02 0x3 + 0x00 | 0,0 | 1,0 | 2,0 | 3,0 | + 0x04 | 0,1 | 1,1 | 2,1 | 3,1 | + 0x08 | 0,2 | 1,2 | 2,2 | 3,2 | + 0x0C | 0,3 | 1,3 | 2,3 | 3,3 | + + This is row-major order (in storage) but we really only access this + via the idx operator (col, row) so it is effectively column-major order + for the user. + + This is because in graphics we typically want to multiply a vector on + the right of the matrix, and we want the translation components to be in + the last column. + + Matrix * Vector multiplication is as follows: + + | m11 m12 m13 m14 | | v1 | | r1 | (m11*v1 + m12*v2 + m13*v3 + m14*v4) + | m21 m22 m23 m24 | * | v2 | = | r2 | (m21*v1 + m22*v2 + m23*v3 + m24*v4) + | m31 m32 m33 m34 | | v3 | | r3 | (m31*v1 + m32*v2 + m33*v3 + m34*v4) + | m41 m42 m43 m44 | | v4 | | r4 | (m41*v1 + m42*v2 + m43*v3 + m44*v4) + + Matrix * Matrix multiplication is as follows: + + | a11 a12 a13 a14 | | b11 b12 b13 b14 | | r11 r12 r13 r14 | (a11*b11 + a12*b21 + a13*b31 + a14*b41) ... + | a21 a22 a23 a24 | * | b21 b22 b23 b24 | = | r21 r22 r23 r24 | (a21*b11 + a22*b21 + a23*b31 + a24*b41) ... + | a31 a32 a33 a34 | | b31 b32 b33 b34 | | r31 r32 r33 r34 | (a31*b11 + a32*b21 + a33*b31 + a34*b41) ... + | a41 a42 a43 a44 | | b41 b42 b43 b44 | | r41 r42 r43 r44 | (a41*b11 + a42*b21 + a43*b31 + a44*b41) ... + + Example Translation: + + | 1 0 0 Tx | | x | | x' | (1*x + 0*y + 0*z + Tx*1) (x + tx) + | 0 1 0 Ty | * | y | = | y' | (0*x + 1*y + 0*z + Ty*1) (y + ty) + | 0 0 1 Tz | | z | | z' | (0*x + 0*y + 1*z + Tz*1) (z + tz) + | 0 0 0 1 | | 1 | | x' | (0*x + 0*y + 0*z + 1*1) (1) + + Example Rotation around Y axis: + | cθ 0 sθ 0 | | x | | x' | (cosθ*x + 0*y + sinθ*z + 0*1) (x*cosθ + z*sinθ) + | 0 1 0 0 | * | y | = | y' | (0*x + 1*y + 0*z + 0*1) (y) + | -sθ 0 cθ 0 | | z | | z' | (-sinθ*x + 0*y + cosθ*z + 0*1) (z*cosθ - x*sinθ) + | 0 0 0 1 | | 1 | | x' | (0*x + 0*y + 0*z + 1*1) (1) + + P' = Projection * View * World * P + + */ + + + + template + struct m_4d + { + static_assert(std::is_arithmetic::value, "olc::m_4d must be numeric"); + + // The 4x4 elements! + std::array m{ {0 } }; + + // Constructor created identity matrix + inline constexpr m_4d() + { + identity(); + } + + // Copy constructor + inline constexpr m_4d(const m_4d& mat) = default; + + // Assignment operator + inline constexpr m_4d& operator=(const m_4d& mat) = default; + + // Retrieve a specific element's 1D index + inline constexpr size_t idx(const size_t c, const size_t r) const + { + return r * 4 + c; // Column-major order (for user) but row-major order in storage + } + + // Retrieve non-const access to specific element + inline constexpr T& operator()(const size_t col, const size_t row) + { + return m[idx(col, row)]; + } + + // Retrieve const access to specific element + inline constexpr const T& operator()(const size_t col, const size_t row) const + { + return m[idx(col, row)]; + } + + // Set all elements to 0 + inline constexpr void clear() + { + std::fill(m.begin(), m.end(), T(0)); + } + + // Create identity matrix + inline constexpr void identity() + { + clear(); + auto& me = (*this); + me(0, 0) = T(1); + me(1, 1) = T(1); + me(2, 2) = T(1); + me(3, 3) = T(1); + } + + inline constexpr auto transpose() const + { + olc::m_4d out; + auto& me = (*this); + for (int i = 0; i < 4; i++) + for (int j = 0; j < 4; j++) + out(i, j) = me(j, i); + return out; + } + + // Create translation matrix via components + template + inline constexpr void translate(const Q x, const Q y, const Q z) + { + identity(); + auto& me = (*this); + me(3, 0) = T(x); + me(3, 1) = T(y); + me(3, 2) = T(z); + } + + // Create translation matrix via vector (x, y, z components) + template + inline constexpr void translate(const olc::v_4d& v) + { + translate(v.x, v.y, v.z); + } + + // Create scaling matrix via components + template + inline constexpr void scale(const Q x, const Q y, const Q z) + { + identity(); + auto& me = (*this); + me(0, 0) = T(x); + me(1, 1) = T(y); + me(2, 2) = T(z); + } + + // Create scaling matrix via vector (x, y, z components) + template + inline constexpr void scale(const olc::v_4d& v) + { + scale(v.x, v.y, v.z); + } + + // Create rotation matrix around X axis with radians + template + inline constexpr void rotateX(const Q rads) + { + identity(); + auto& me = (*this); + me(1, 1) = std::cos(T(rads)); + me(1, 2) = std::sin(T(rads)); + me(2, 1) = -me(1, 2); + me(2, 2) = me(1, 1); + } + + // Create rotation matrix around Y axis with radians + template + inline constexpr void rotateY(const Q rads) + { + identity(); + auto& me = (*this); + me(0, 0) = std::cos(T(rads)); + me(0, 2) = -std::sin(T(rads)); + me(2, 0) = -me(0, 2); + me(2, 2) = me(0, 0); + } + + // Create rotation matrix around Z axis with radians + template + inline constexpr void rotateZ(const Q rads) + { + identity(); + auto& me = (*this); + me(0, 0) = std::cos(T(rads)); + me(0, 1) = std::sin(T(rads)); + me(1, 0) = -me(0, 1); + me(1, 1) = me(0, 0); + } + + // Create perspective projection matrix + template + inline constexpr void perspective(const Q fov, const Q ratio, const Q nearplane, const Q farplane) + { + identity(); + auto& me = (*this); + T invFOV = T(1) / tan(fov * T(0.5)); + + me(0,0) = invFOV / ratio; // X scale + me(1,1) = invFOV; // Y scale + me(2,2) = (farplane + nearplane) / (nearplane - farplane); // Z mapping + me(3,2) = (2.0f * farplane * nearplane) / (nearplane - farplane); // Z offset + me(2,3) = -1.0f; // Perspective divide by -Z + me(3,3) = 0.0f; + } + + // Create orthographic projection matrix + template + inline constexpr void orthographic(const Q left, const Q right, const Q bottom, const Q top, const Q near1, const Q far1) + { + identity(); + auto& me = (*this); + me(0, 0) = T(2) / (right - left); + me(1, 1) = T(2) / (top - bottom); + me(2, 2) = T(-2) / (far1 - near1); + me(3, 0) = -(right + left) / (right - left); + me(3, 1) = -(top + bottom) / (top - bottom); + me(3, 2) = -(far1 + near1) / (far1 - near1); + } + + // Return inverted matrix + inline constexpr auto invert() const + { + // Using Gauss-Jordan elimination - AI special this :P + olc::m_4d out; + auto& me = (*this); + + T A2323 = me(2, 2) * me(3, 3) - me(2, 3) * me(3, 2); + T A1323 = me(2, 1) * me(3, 3) - me(2, 3) * me(3, 1); + T A1223 = me(2, 1) * me(3, 2) - me(2, 2) * me(3, 1); + T A0323 = me(2, 0) * me(3, 3) - me(2, 3) * me(3, 0); + T A0223 = me(2, 0) * me(3, 2) - me(2, 2) * me(3, 0); + T A0123 = me(2, 0) * me(3, 1) - me(2, 1) * me(3, 0); + T A2313 = me(1, 2) * me(3, 3) - me(1, 3) * me(3, 2); + T A1313 = me(1, 1) * me(3, 3) - me(1, 3) * me(3, 1); + T A1213 = me(1, 1) * me(3, 2) - me(1, 2) * me(3, 1); + T A2312 = me(1, 2) * me(2, 3) - me(1, 3) * me(2, 2); + T A1312 = me(1, 1) * me(2, 3) - me(1, 3) * me(2, 1); + T A1212 = me(1, 1) * me(2, 2) - me(1, 2) * me(2, 1); + T A0313 = me(1, 0) * me(3, 3) - me(1, 3) * me(3, 0); + T A0213 = me(1, 0) * me(3, 2) - me(1, 2) * me(3, 0); + T A0312 = me(1, 0) * me(2, 3) - me(1, 3) * me(2, 0); + T A0212 = me(1, 0) * me(2, 2) - me(1, 2) * me(2, 0); + T A0113 = me(1, 0) * me(3, 1) - me(1, 1) * me(3, 0); + T A0112 = me(1, 0) * me(2, 1) - me(1, 1) * me(2, 0); + + T det = me(0, 0) * (me(1, 1) * A2323 - me(1, 2) * A1323 + me(1, 3) * A1223) + - me(0, 1) * (me(1, 0) * A2323 - me(1, 2) * A0323 + me(1, 3) * A0223) + + me(0, 2) * (me(1, 0) * A1323 - me(1, 1) * A0323 + me(1, 3) * A0123) + - me(0, 3) * (me(1, 0) * A1223 - me(1, 1) * A0223 + me(1, 2) * A0123); + + T invdet = T(1) / det; + + out(0, 0) = invdet * (me(1, 1) * A2323 - me(1, 2) * A1323 + me(1, 3) * A1223); + out(0, 1) = invdet * -(me(0, 1) * A2323 - me(0, 2) * A1323 + me(0, 3) * A1223); + out(0, 2) = invdet * (me(0, 1) * A2313 - me(0, 2) * A1313 + me(0, 3) * A1213); + out(0, 3) = invdet * -(me(0, 1) * A2312 - me(0, 2) * A1312 + me(0, 3) * A1212); + out(1, 0) = invdet * -(me(1, 0) * A2323 - me(1, 2) * A0323 + me(1, 3) * A0223); + out(1, 1) = invdet * (me(0, 0) * A2323 - me(0, 2) * A0323 + me(0, 3) * A0223); + out(1, 2) = invdet * -(me(0, 0) * A2313 - me(0, 2) * A0313 + me(0, 3) * A0213); + out(1, 3) = invdet * (me(0, 0) * A2312 - me(0, 2) * A0312 + me(0, 3) * A0212); + out(2, 0) = invdet * (me(1, 0) * A1323 - me(1, 1) * A0323 + me(1, 3) * A0123); + out(2, 1) = invdet * -(me(0, 0) * A1323 - me(0, 1) * A0323 + me(0, 3) * A0123); + out(2, 2) = invdet * (me(0, 0) * A1313 - me(0, 1) * A0313 + me(0, 3) * A0113); + out(2, 3) = invdet * -(me(0, 0) * A1312 - me(0, 1) * A0312 + me(0, 3) * A0112); + out(3, 0) = invdet * -(me(1, 0) * A1223 - me(1, 1) * A0223 + me(1, 2) * A0123); + out(3, 1) = invdet * (me(0, 0) * A1223 - me(0, 1) * A0223 + me(0, 2) * A0123); + out(3, 2) = invdet * -(me(0, 0) * A1213 - me(0, 1) * A0213 + me(0, 2) * A0113); + out(3, 3) = invdet * (me(0, 0) * A1212 - me(0, 1) * A0212 + me(0, 2) * A0112); + + return out; + } + + // Transform a vector by this matrix + template + inline constexpr auto operator * (const olc::v_4d& v) const + { + auto& me = *this; + olc::v_4d vOut; + vOut.x = Q(me(0, 0) * v.x + me(1, 0) * v.y + me(2, 0) * v.z + me(3, 0) * v.w); + vOut.y = Q(me(0, 1) * v.x + me(1, 1) * v.y + me(2, 1) * v.z + me(3, 1) * v.w); + vOut.z = Q(me(0, 2) * v.x + me(1, 2) * v.y + me(2, 2) * v.z + me(3, 2) * v.w); + vOut.w = Q(me(0, 3) * v.x + me(1, 3) * v.y + me(2, 3) * v.z + me(3, 3) * v.w); + return vOut; + } + + // Multiply this matrix with another + template + inline constexpr auto operator * (const olc::m_4d& rhs) const + { + auto& me = *this; + olc::m_4d out; + for (size_t c = 0; c < 4; c++) + for (size_t r = 0; r < 4; r++) + out(c, r) = me(0, r) * rhs(c, 0) + me(1, r) * rhs(c, 1) + me(2, r) * rhs(c, 2) + me(3, r) * rhs(c, 3); + return out; + } + + // Transform a vector of v_4d by this matrix + template + inline constexpr auto transform(const std::vector>& v) + { + std::vector> o(v.size()); + std::transform(v.begin(), v.end(), o.begin(), [this](const olc::v_4d& i) {return (*this) * i; }); + return o; + } + + // Return this matrix as a std::string + inline std::string str() const + { + const auto& me = *this; + return std::string("[") + std::to_string(me(0, 0)) + "," + std::to_string(me(1, 0)) + "," + std::to_string(me(2, 0)) + "," + std::to_string(me(3, 0)) + "]\n" + + "[" + std::to_string(me(0, 1)) + "," + std::to_string(me(1, 1)) + "," + std::to_string(me(2, 1)) + "," + std::to_string(me(3, 1)) + "]\n" + + "[" + std::to_string(me(0, 2)) + "," + std::to_string(me(1, 2)) + "," + std::to_string(me(2, 2)) + "," + std::to_string(me(3, 2)) + "]\n" + + "[" + std::to_string(me(0, 3)) + "," + std::to_string(me(1, 3)) + "," + std::to_string(me(2, 3)) + "," + std::to_string(me(3, 3)) + "]\n"; + } + }; + + // Allow olc::m_4d to play nicely with std::cout + template + inline std::ostream& operator << (std::ostream& os, const m_4d& rhs) + { + os << rhs.str(); + return os; + } + + // Convenient types ready-to-go + typedef m_4d mf4d; + typedef m_4d md4d; +} +#define PGE_MATRIX4D_DECLARED 1 +#endif + #if !defined(PGE_TRANSFORM2D_DECLARED) namespace olc { @@ -1728,6 +2414,9 @@ namespace olc // Use hardware wire drawing bool bWireframe = false; + // Define how to interpret vertex buffer + bool bIs3D = false; + // Overall biasing colour (great for blends) olc::Pixel tint = olc::Colour::WHITE; @@ -1903,6 +2592,8 @@ namespace olc class Renderer; class Shader; } + + class Draw3D; // These "opaque" structs are merely to help with // type differentiation of various GPUTask types @@ -1915,6 +2606,8 @@ namespace olc class Draw2D { + friend class olc::Draw3D; + public: Draw2D(); @@ -2781,17 +3474,194 @@ namespace olc } }; - // Thread local buffers to avoid repeated allocations - static thread_local buffer buffPoints; - static thread_local buffer buffUnitCirclePoints; - static thread_local buffer buffColours; - static thread_local buffer vecGPUTasks; + // Thread local buffers to avoid repeated allocations + static thread_local buffer buffPoints; + static thread_local buffer buffUnitCirclePoints; + static thread_local buffer buffColours; + static thread_local buffer vecGPUTasks; + + void RedefineUnitCircleBuffer(const int32_t nFacets); + + }; +} +#define PGE_DRAW2D_DECLARED +#endif + +#if !defined(PGE_DRAW3D_DECLARED) +namespace olc +{ + namespace gpu + { + class Renderer; + class Shader; + } + + class Draw3D + { + + + public: + Draw3D(olc::Draw2D& d2d); + + // Associate this drawing toolbox with a renderer + void SetGPU(olc::gpu::Renderer* const renderer); + void ProcessGPUTasks(); + + public: + // Sets the drawing target of this drawing toolbox + void SetTarget(olc::Image& image); + // Get the current drawing target + olc::Image& GetTarget(); + // Get Size of drawing target (aka GetTarget()->Size()) + olc::vi2d GetTargetSize(); + // Set the area in the target to 3d draw to + void SetViewport(const olc::vi2d& pos, const olc::vi2d& size); + + public: // Applied Matrices + void MatrixReset(); + void SetModelMatrix(const olc::mf4d& mat); + const olc::mf4d& GetModelMatrix() const; + void SetViewMatrix(const olc::mf4d& mat); + const olc::mf4d& GetViewMatrix() const; + void SetProjectionMatrix(const olc::mf4d& mat); + const olc::mf4d& GetProjectionMatrix() const; + void SetMVPMatrix(const olc::mf4d& mat); + const olc::mf4d& GetMVPMatrix() const; + + public: // Applied Rendering Modes + void SetCullMode(const olc::GPUTask::CullMode mode); + void EnableDepth(const bool bEnable); + + public: // Primitive Drawing Functions + // Clear entire draw target to specific colour + void Clear(const olc::Pixel& col); + + GPUTask& Line( + const olc::vf4d& vStart, + const olc::vf4d& vEnd, + const olc::Pixel& col = olc::Colour::WHITE, + const olc::Pixel tint = olc::Colour::WHITE); + + GPUTask& Mesh( + const olc::Structure structure, + const std::vector& vPoints, + const std::vector& vColours, + const olc::Pixel tint = olc::Colour::WHITE); + + GPUTask& Mesh( + const olc::Structure structure, + const std::vector& vPoints, + const std::vector& vColours, + const std::vector& vUVs, + olc::Image& texture, + const olc::Pixel tint = olc::Colour::WHITE); + + + + public: // GPU Task Creator Functions (not normally called by user) + GPUTask TaskWireMesh( + olc::Structure structure, + const std::vector& vPoints, + const std::vector& vColours, + const olc::Pixel tint = olc::Colour::WHITE); + + GPUTask TaskFillMesh( + olc::Structure structure, + const std::vector& vPoints, + const std::vector& vColours, + const olc::Pixel tint = olc::Colour::WHITE); + + GPUTask TaskTexturedMesh( + olc::Structure structure, + const std::vector& vPoints, + const std::vector& vColours, + const std::vector& vTexCoords, + olc::Image* const image, + const olc::Pixel tint = olc::Colour::WHITE); + + public: + // Change the shader used for subsequent GPU drawing tasks + bool SetShader(const olc::gpu::Shader& shader); + // Reset to default shader for subsequent GPU drawing tasks + bool ResetShader(); + // Set uniform variable for subsequent GPU drawing tasks + bool SetShaderUniform(const std::string& name, const float value); + // Set uniform variable for subsequent GPU drawing tasks + bool SetShaderUniform(const std::string& name, const olc::vf2d& value); + // Set uniform variable for subsequent GPU drawing tasks + bool SetShaderUniform(const std::string& name, const olc::Pixel value); + // Assign an image to a texture slot for subsequent GPU drawing tasks + bool SetShaderTexture(const uint32_t nSlot, olc::Image& image); + + public: + struct sDrawMetrics + { + uint32_t nGPUTasks = 0; + uint32_t nGPUtoCPUTransfers = 0; + uint32_t nCPUtoGPUTransfers = 0; + uint32_t nShaderChanges = 0; + }; + + void ResetDrawMetrics(); + sDrawMetrics GetDrawMetrics() const; + + private: + sDrawMetrics drawMetrics; + + + - void RedefineUnitCircleBuffer(const int32_t nFacets); + protected: + // Checks residency of image resource, and brings it to cpu RAM for r/w + void PrepareTargetForSW(); + // Checks residency of image resource, and brings it to gpu VRAM for r/w + void PrepareTargetForHW(); + + // Checks residency of image resource, and brings it to cpu RAM for r/w + void PrepareImageForSW(olc::Image& image); + // Checks residency of image resource, and brings it to gpu VRAM for r/w + void PrepareImageForHW(olc::Image& image); + + olc::Image* pTarget = nullptr; + olc::gpu::Renderer* pRenderer = nullptr; + + mf4d matModel; + mf4d matView; + mf4d matProjection; + mf4d matVP; + mf4d matMVP; + olc::vf2d vViewportPos = { 0, 0 }; + olc::vi2d vViewportSize = { 0, 0 }; + olc::GPUTask::CullMode cullMode = olc::GPUTask::CullMode::None; + bool bDepth = true; + + olc::Draw2D& draw2d; + private: + // Simple dynamic buffer that only grows as needed + template + struct buffer + { + std::vector data; + + void reserve(size_t n) + { + if (n > data.capacity()) + data.reserve(n); + + // Ensure size matches requested so we + // can index into it directly + data.resize(n); + } + }; + + // Thread local buffers to avoid repeated allocations + static thread_local buffer buffPoints; + static thread_local buffer buffColours; + static thread_local buffer vecGPUTasks; }; } -#define PGE_DRAW2D_DECLARED +#define PGE_DRAW3D_DECLARED #endif #if !defined(PGE_HARDWAREINPUT_DECLARED) @@ -3200,6 +4070,7 @@ namespace olc class PGEWindow : public Window { public: + PGEWindow(); bool Create(const olc::vi2d& vScreenSize, const olc::vi2d& vPixelSize); public: @@ -3249,6 +4120,7 @@ namespace olc protected: olc::Draw2D draw; + olc::Draw3D draw3d; private: @@ -5312,6 +6184,8 @@ namespace olc typedef void CALLSTYLE glGetInternalformativ_t(GLenum target, GLenum internalformat, GLenum pname, GLsizei bufSize, GLint* params); typedef void CALLSTYLE glGetShaderiv_t(GLuint shader, GLenum pname, GLint* params); typedef void CALLSTYLE glGetIntegerv_t(GLenum pname, GLint *data); + typedef void CALLSTYLE glGetRenderbufferParameteriv_t(GLenum target, GLenum pname, GLint* params); + typedef void CALLSTYLE glRenderbufferStorage_t(GLenum target, GLenum internalformat, GLsizei width, GLsizei height); #if OLC_HOST == OLC_HOST_WINDOWS typedef void CALLSTYLE wglSwapIntervalEXT_t(GLsizei n); @@ -5373,6 +6247,8 @@ namespace olc glGetInternalformativ_t* _glGetInternalformativ = nullptr; glGetShaderiv_t* _glGetShaderiv = nullptr; glGetIntegerv_t *_glGetIntegerv = nullptr; + glGetRenderbufferParameteriv_t* _glGetRenderbufferParameteriv = nullptr; + glRenderbufferStorage_t* _glRenderbufferStorage = nullptr; #if OLC_HOST == OLC_HOST_WINDOWS wglSwapIntervalEXT_t* _wglSwapIntervalEXT = nullptr; #endif @@ -5423,6 +6299,8 @@ namespace olc void glDeleteRenderbuffers(GLsizei n, const GLuint* renderbuffers); void glGetInternalformativ(GLenum target, GLenum internalformat, GLenum pname, GLsizei bufSize, GLint* params); void glGetShaderiv(GLuint shader, GLenum pname, GLint* params); + void glGetRenderbufferParameteriv(GLenum target, GLenum pname, GLint* params); + void glRenderbufferStorage(GLenum target, GLenum internalformat, GLsizei width, GLsizei height); // OpenGL1.2 Proxies (just keeps things tidy imo) void glGenTextures(GLsizei n, GLuint* textures); @@ -5444,7 +6322,7 @@ namespace olc void glGetTexImage(GLenum target, GLint level, GLenum format, GLenum type, void* pixels); void glHint(GLenum target, GLenum mode); void glPolygonMode(GLenum face, GLenum mode); - + void glFrontFace(GLenum mode); void glGetIntegerv(GLenum pname, GLint *data); @@ -5469,8 +6347,9 @@ namespace olc static constexpr GLenum GL_SAMPLES_X = 0x80A9; static constexpr GLenum GL_COMPILE_STATUS_X = 0x8B81; static constexpr GLenum GL_INFO_LOG_LENGTH_X = 0x8B84; - - + static constexpr GLenum GL_DEPTH_COMPONENT24 = 0x81A6; + static constexpr GLenum GL_DEPTH_ATTACHMENT_X = 0x8D00; + static constexpr GLenum GL_RENDERBUFFER_SAMPLES_X = 0x8CAB; private: bool CheckError(const std::source_location loc = std::source_location::current()); @@ -5575,6 +6454,10 @@ namespace olc const Shader* pCurrentShader = nullptr; + uint32_t nDepthRBO = 0; // Shared depth renderbuffer + olc::vi2d vCurrentDepthSize = {0, 0}; // Track current depth buffer size + int32_t nCurrentDepthSamples = 0; // Track current MSAA sample count + #if OLC_HOST == OLC_HOST_ANDROID EGLConfig FindBestConfig(EGLDisplay display, int desiredMultisamples = OLC_MSAA_SAMPLES); #endif @@ -11589,6 +12472,8 @@ namespace olc::apis::opengl bLoaded &= (_glDeleteRenderbuffers = OGL_LOAD(glDeleteRenderbuffers)) != nullptr; bLoaded &= (_glGetInternalformativ = OGL_LOAD(glGetInternalformativ)) != nullptr; bLoaded &= (_glGetShaderiv = OGL_LOAD(glGetShaderiv)) != nullptr; + bLoaded &= (_glGetRenderbufferParameteriv = OGL_LOAD(glGetRenderbufferParameteriv)) != nullptr; + bLoaded &= (_glRenderbufferStorage = OGL_LOAD(glRenderbufferStorage)) != nullptr; // Do we really need to do this? - jx9 #if OLC_HOST != OLC_HOST_WINDOWS @@ -11768,6 +12653,12 @@ namespace olc::apis::opengl #endif } + void gl::glFrontFace(GLenum mode) + { + ::glFrontFace(mode); + CheckError(); + } + void gl::glSwapInterval(GLsizei n) { #if OLC_HOST == OLC_HOST_WINDOWS @@ -12018,6 +12909,18 @@ namespace olc::apis::opengl _glGetIntegerv(pname, data); CheckError(); } + + void gl::glGetRenderbufferParameteriv(GLenum target, GLenum pname, GLint* params) + { + _glGetRenderbufferParameteriv(target, pname, params); + CheckError(); + } + + void gl::glRenderbufferStorage(GLenum target, GLenum internalformat, GLsizei width, GLsizei height) + { + _glRenderbufferStorage(target, internalformat, width, height); + CheckError(); + } } namespace olc::gpu { @@ -12456,7 +13359,17 @@ void main() // Create a Frame Buffer Object for off-screen rendering things gl.glGenFramebuffers(1, (GLuint*)&nDefaultFBO); - gl.glBindFramebuffer(gl.GL_FRAMEBUFFER_X, nDefaultFBO); // GL_FRAMEBUFFER + gl.glBindFramebuffer(gl.GL_FRAMEBUFFER_X, nDefaultFBO); + + // Create a shared depth renderbuffer (will be resized dynamically) + gl.glGenRenderbuffers(1, &nDepthRBO); + gl.glBindRenderbuffer(gl.GL_RENDERBUFFER_X, nDepthRBO); + // Allocate with a default size (will be resized when needed) + gl.glRenderbufferStorage(gl.GL_RENDERBUFFER_X, gl.GL_DEPTH_COMPONENT24, 1024, 1024); + gl.glFramebufferRenderbuffer(gl.GL_FRAMEBUFFER_X, gl.GL_DEPTH_ATTACHMENT_X, gl.GL_RENDERBUFFER_X, nDepthRBO); + vCurrentDepthSize = {1024, 1024}; + nCurrentDepthSamples = 0; + // Attach 4 colour buffers std::array attachments = { { @@ -12480,20 +13393,34 @@ void main() gl.glGenFramebuffers(1, &nResolveFBO_Draw); gl.glGenFramebuffers(1, &nResolveFBO_Read); + // PGE Specific requirements + + // Texturing Enabled #if OLC_HOST != OLC_HOST_EMSCRIPTEN && OLC_HOST != OLC_HOST_ANDROID gl.glEnable(GL_TEXTURE_2D); // Turn on texturing gl.glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); #endif + // Alpha Blending Enabled gl.glEnable(GL_BLEND); + // Front Face is Counter-Clockwise + gl.glFrontFace(GL_CCW); + lastError = RendererError::NoError; return true; } bool Renderer_OGL33::DestroyDevice() { - //auto& gl = olc::apis::opengl::gl::Get(); - + auto& gl = olc::apis::opengl::gl::Get(); + + // Delete depth renderbuffer + if (nDepthRBO != 0) + { + gl.glDeleteRenderbuffers(1, &nDepthRBO); + nDepthRBO = 0; + } + #if OLC_HOST == OLC_HOST_WINDOWS wglDeleteContext(glRenderContext); #endif @@ -12811,6 +13738,60 @@ void main() // Bind FBO gl.glBindFramebuffer(gl.GL_FRAMEBUFFER_X, nDefaultFBO); + // Resize depth buffer to match target texture dimensions + olc::vi2d targetSize = mapTextureSizes[texid]; + int32_t targetSamples = 0; + + // Check if this is an MSAA texture + bool bIsMSAA = mapTextureToRenderbuffer.contains(texid); + if (bIsMSAA) + { + // Get the MSAA sample count from the color renderbuffer + uint32_t rboId = mapTextureToRenderbuffer[texid]; + gl.glBindRenderbuffer(gl.GL_RENDERBUFFER_X, rboId); + gl.glGetRenderbufferParameteriv(gl.GL_RENDERBUFFER_X, gl.GL_RENDERBUFFER_SAMPLES_X, &targetSamples); + } + + // Only resize if dimensions or sample count changed + if (targetSize != vCurrentDepthSize || targetSamples != nCurrentDepthSamples) + { + gl.glBindRenderbuffer(gl.GL_RENDERBUFFER_X, nDepthRBO); + + if (bIsMSAA && targetSamples > 0) + { + // Allocate MSAA depth buffer + gl.glRenderbufferStorageMultisample( + gl.GL_RENDERBUFFER_X, + targetSamples, + gl.GL_DEPTH_COMPONENT24, + targetSize.x, + targetSize.y + ); + } + else + { + // Allocate regular depth buffer + gl.glRenderbufferStorage( + gl.GL_RENDERBUFFER_X, + gl.GL_DEPTH_COMPONENT24, + targetSize.x, + targetSize.y + ); + } + + // Update tracked size and samples + vCurrentDepthSize = targetSize; + nCurrentDepthSamples = targetSamples; + + // Re-attach depth buffer to FBO + gl.glFramebufferRenderbuffer( + gl.GL_FRAMEBUFFER_X, + gl.GL_DEPTH_ATTACHMENT_X, + gl.GL_RENDERBUFFER_X, + nDepthRBO + ); + } + // Allocate target buffers - pick the single attachment corresponding to 'slot' std::array attachments = { { @@ -13022,42 +14003,36 @@ void main() // Copy data from CPU to GPU gl.glBufferData(gl.GL_ARRAY_BUFFER_X, sizeof(GPUTask::Vertex) * task.vertexBuffer.size(), task.vertexBuffer.data(), gl.GL_STREAM_DRAW_X); - - + // Configure shader with expected values - - - - // Shader: Apply MVP Matrix - //gl.glUniformMatrix4fv(shaderDefault.GetUniform("mvp"), 1, true, task.mvpMatrix.data()); - - // Shader: Apply Global Tint SetUniform("pgeGlobalTint", task.tint); - SetUniform("pgeTargetSizeInPixels", vTargetSize); SetUniform("pgeInverseTargetSizeInPixels", (1.0f / vTargetSize)); SetUniform("pgeTotalTimeElapsed", fTotalTime); + + // Apply Culling modes - //if (task.cullmode == GPUTask::CullMode::None) - //{ - // gl.glCullFace(GL_FRONT); - // gl.glDisable(GL_CULL_FACE); - //} - //else if (task.cullmode == GPUTask::CullMode::ClockWise) - //{ - // gl.glCullFace(GL_FRONT); - // gl.glEnable(GL_CULL_FACE); - //} - //else if (task.cullmode == GPUTask::CullMode::CounterClockWise) - //{ - // gl.glCullFace(GL_BACK); - // gl.glEnable(GL_CULL_FACE); - //} + if (task.cullmode == GPUTask::CullMode::None) + { + gl.glDisable(GL_CULL_FACE); + } + else if (task.cullmode == GPUTask::CullMode::ClockWise) + { + gl.glCullFace(GL_FRONT); + gl.glEnable(GL_CULL_FACE); + } + else if (task.cullmode == GPUTask::CullMode::CounterClockWise) + { + gl.glCullFace(GL_BACK); + gl.glEnable(GL_CULL_FACE); + } //// Apply Depth Testing (if required) - //if (task.bDepth) - // gl.glEnable(GL_DEPTH_TEST); + if (task.bDepth) + gl.glEnable(GL_DEPTH_TEST); + + glDepthFunc(GL_LESS); gl.glEnable(GL_BLEND); //gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); @@ -13066,16 +14041,24 @@ void main() if (task.bWireframe) gl.glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - if (task.structure == olc::Structure::Point) - gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); - else if (task.structure == olc::Structure::Line) - gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); - else if (task.structure == olc::Structure::LineLoop) - gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); - else if (task.structure == olc::Structure::LineList) - gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); + if (task.bIs3D) + { + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 2); + gl.glUniformMatrix4fv(pCurrentShader->GetUniform("pgeMVP"), 1, true, task.mvpMatrix.data()); + } else - gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 0); + { + if (task.structure == olc::Structure::Point) + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); + else if (task.structure == olc::Structure::Line) + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); + else if (task.structure == olc::Structure::LineLoop) + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); + else if (task.structure == olc::Structure::LineList) + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 1); + else + gl.glUniform1i(pCurrentShader->GetUniform("pgeDrawType"), 0); + } if (task.structure == olc::Structure::Fan) gl.glDrawArrays(GL_TRIANGLE_FAN, 0, (GLsizei)task.vertexBuffer.size()); @@ -13109,7 +14092,7 @@ void main() bool Renderer_OGL33::ClearViewport(const olc::Pixel col, bool bDepth, bool bStencil) { auto& gl = olc::apis::opengl::gl::Get(); - gl.glClearColor(float(col.r) / 255.0f, float(col.g) / 255.0f, float(col.b) / 255.0f, float(col.a) / 255.0f); + gl.glClearColor(float(col.r) / 255.0f, float(col.g) / 255.0f, float(col.b) / 255.0f, float(col.a) / 255.0f); gl.glClear(GL_COLOR_BUFFER_BIT | (bDepth ? GL_DEPTH_BUFFER_BIT : 0) | (bStencil ? GL_STENCIL_BUFFER_BIT : 0)); return true; } @@ -15090,9 +16073,276 @@ void olc::Draw2D::swRasterShadedLine(const olc::vi2d& v1, const olc::vi2d& v2, c #define PGE_DRAW2D_IMPLEMENTED 1 #endif +#if defined(OLC_PGE3_APPLICATION) && !defined(PGE_DRAW3D_IMPLEMENTED) +thread_local Draw3D::buffer Draw3D::buffPoints; +thread_local Draw3D::buffer Draw3D::buffColours; +thread_local Draw3D::buffer Draw3D::vecGPUTasks; + +olc::Draw3D::Draw3D(olc::Draw2D& d2d) : draw2d(d2d) +{ + MatrixReset(); +} + +void olc::Draw3D::SetGPU(olc::gpu::Renderer* const renderer) +{ + draw2d.SetGPU(renderer); +} + +void olc::Draw3D::ProcessGPUTasks() +{ + draw2d.ProcessGPUTasks(); +} + +void olc::Draw3D::SetTarget(olc::Image& image) +{ + draw2d.SetTarget(image); +} + +olc::Image& olc::Draw3D::GetTarget() +{ + return draw2d.GetTarget(); +} + +olc::vi2d olc::Draw3D::GetTargetSize() +{ + return draw2d.GetTargetSize(); +} + +void olc::Draw3D::SetViewport(const olc::vi2d& pos, const olc::vi2d& size) +{ + draw2d.pRenderer->SetViewport(pos, size); +} + +bool olc::Draw3D::SetShader(const olc::gpu::Shader& shader) +{ + return draw2d.SetShader(shader); +} + +bool olc::Draw3D::ResetShader() +{ + return draw2d.ResetShader(); +} + +bool olc::Draw3D::SetShaderUniform(const std::string& name, const float value) +{ + return draw2d.SetShaderUniform(name, value); +} + +bool olc::Draw3D::SetShaderUniform(const std::string& name, const olc::vf2d& value) +{ + return draw2d.SetShaderUniform(name, value); +} + +bool olc::Draw3D::SetShaderUniform(const std::string& name, const olc::Pixel value) +{ + return draw2d.SetShaderUniform(name, value); +} + +bool olc::Draw3D::SetShaderTexture(const uint32_t nSlot, olc::Image& image) +{ + return draw2d.SetShaderTexture(nSlot, image); +} + +void olc::Draw3D::PrepareTargetForSW() +{ + draw2d.PrepareTargetForSW(); +} + +void olc::Draw3D::PrepareTargetForHW() +{ + draw2d.PrepareTargetForHW(); +} + +void olc::Draw3D::PrepareImageForSW(olc::Image& image) +{ + draw2d.PrepareImageForSW(image); +} + +void olc::Draw3D::PrepareImageForHW(olc::Image& image) +{ + draw2d.PrepareImageForHW(image); +} + + + + + +void olc::Draw3D::MatrixReset() +{ + matMVP.identity(); + matModel.identity(); + matView.identity(); + matProjection.identity(); +} + +void olc::Draw3D::SetModelMatrix(const olc::mf4d& mat) +{ + matModel = mat; + matMVP = matVP * matModel; +} + +const olc::mf4d& olc::Draw3D::GetModelMatrix() const +{ + return matModel; +} + +void olc::Draw3D::SetViewMatrix(const olc::mf4d& mat) +{ + matView = mat; + matVP = matProjection * matView; + matMVP = matVP * matModel; +} + +const olc::mf4d& olc::Draw3D::GetViewMatrix() const +{ + return matView; +} + +void olc::Draw3D::SetProjectionMatrix(const olc::mf4d& mat) +{ + matProjection = mat; + matVP = matProjection * matView; + matMVP = matVP * matModel; +} + +const olc::mf4d& olc::Draw3D::GetProjectionMatrix() const +{ + return matProjection; +} + +void olc::Draw3D::SetMVPMatrix(const olc::mf4d& mat) +{ + matMVP = mat; +} + +const olc::mf4d& olc::Draw3D::GetMVPMatrix() const +{ + return matMVP; +} + +void olc::Draw3D::SetCullMode(const olc::GPUTask::CullMode mode) +{ + cullMode = mode; +} + +void olc::Draw3D::EnableDepth(const bool bEnable) +{ + bDepth = bEnable; +} + + + +GPUTask olc::Draw3D::TaskWireMesh(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) +{ + GPUTask task; + task.structure = structure; + task.tint = tint; + task.vertexBuffer.resize(vPoints.size()); + task.bWireframe = true; + task.bDepth = bDepth; + task.cullmode = cullMode; + task.bIs3D = true; + task.mvpMatrix = matMVP.m; + + for (size_t i = 0; i < vPoints.size(); i++) + task.vertexBuffer[i] = { {vPoints[i].x, vPoints[i].y, vPoints[i].z, vPoints[i].w}, vColours[i], {0, 0}, {0, 0}, {0, 0}, {0, 0}}; + return task; +} + +GPUTask olc::Draw3D::TaskFillMesh(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) +{ + GPUTask task; + task.structure = structure; + task.tint = tint; + task.vertexBuffer.resize(vPoints.size()); + task.bDepth = bDepth; + task.cullmode = cullMode; + task.bIs3D = true; + task.mvpMatrix = matMVP.m; + + for (size_t i = 0; i < vPoints.size(); i++) + task.vertexBuffer[i] = { {vPoints[i].x, vPoints[i].y, vPoints[i].z, vPoints[i].w}, vColours[i], {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + return task; +} + +GPUTask olc::Draw3D::TaskTexturedMesh(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const std::vector& vTexCoords, olc::Image* const image, const olc::Pixel tint) +{ + GPUTask task; + task.structure = structure; + task.tint = tint; + task.vertexBuffer.resize(vPoints.size()); + task.pImage = image; + task.bIs3D = true; + task.bDepth = bDepth; + task.cullmode = cullMode; + task.mvpMatrix = matMVP.m; + for (size_t i = 0; i < vPoints.size(); i++) + task.vertexBuffer[i] = { {vPoints[i].x, vPoints[i].y, vPoints[i].z, vPoints[i].w}, vColours[i], {vTexCoords[i].x, vTexCoords[i].y}, {0, 0}, {0, 0}, {0, 0} }; + return task; +} + +void olc::Draw3D::Clear(const olc::Pixel& col) +{ + draw2d.Clear(col); +} + +GPUTask& olc::Draw3D::Line(const olc::vf4d& vStart, const olc::vf4d& vEnd, const olc::Pixel& col, const olc::Pixel tint) +{ + PrepareTargetForHW(); + + return draw2d.vecGPUTasks.data.emplace_back(std::move( + TaskWireMesh( + olc::Structure::Line, + { vStart, vEnd }, + { col, col }, + tint + ))); +} + +GPUTask& olc::Draw3D::Mesh(const olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) +{ + PrepareTargetForHW(); + + return draw2d.vecGPUTasks.data.emplace_back(std::move( + TaskWireMesh( + structure, + vPoints, + vColours, + tint + ))); + +} + +GPUTask& olc::Draw3D::Mesh(const olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const std::vector& vUVs, olc::Image& texture, const olc::Pixel tint) +{ + PrepareImageForHW(texture); + PrepareTargetForHW(); + return draw2d.vecGPUTasks.data.emplace_back(std::move( + TaskTexturedMesh( + structure, + vPoints, + vColours, + vUVs, + &texture, + tint + ))); +} + + + + + + +#define PGE_DRAW3D_IMPLEMENTED 1 +#endif + #if defined(OLC_PGE3_APPLICATION) && !defined(PGE_CORE_IMPLEMENTED) namespace olc { + PGEWindow::PGEWindow() : Window(), draw(), draw3d(draw) + { + } + bool PGEWindow::Create(const olc::vi2d& vScreenSize, const olc::vi2d& vPixelSize) { //pRenderer->RetargetDevice(pHost->GetHostWindowDescriptor(this)); From 973a778c1aaaef674867846c5ca81730337a406c Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Sun, 8 Feb 2026 11:43:37 -0500 Subject: [PATCH 19/58] [api_opengl][gpu_opengl33] fix opengl macros being redefined as static constants --- dev/src/api_opengl.h | 2 +- dev/src/gpu_opengl33.cpp | 6 +++--- olcPixelGameEngine3.h | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/dev/src/api_opengl.h b/dev/src/api_opengl.h index d9c5602f..31da05f5 100644 --- a/dev/src/api_opengl.h +++ b/dev/src/api_opengl.h @@ -334,7 +334,7 @@ namespace olc static constexpr GLenum GL_SAMPLES_X = 0x80A9; static constexpr GLenum GL_COMPILE_STATUS_X = 0x8B81; static constexpr GLenum GL_INFO_LOG_LENGTH_X = 0x8B84; - static constexpr GLenum GL_DEPTH_COMPONENT24 = 0x81A6; + static constexpr GLenum GL_DEPTH_COMPONENT24_X = 0x81A6; static constexpr GLenum GL_DEPTH_ATTACHMENT_X = 0x8D00; static constexpr GLenum GL_RENDERBUFFER_SAMPLES_X = 0x8CAB; private: diff --git a/dev/src/gpu_opengl33.cpp b/dev/src/gpu_opengl33.cpp index b7544f01..02fa8542 100644 --- a/dev/src/gpu_opengl33.cpp +++ b/dev/src/gpu_opengl33.cpp @@ -444,7 +444,7 @@ void main() gl.glGenRenderbuffers(1, &nDepthRBO); gl.glBindRenderbuffer(gl.GL_RENDERBUFFER_X, nDepthRBO); // Allocate with a default size (will be resized when needed) - gl.glRenderbufferStorage(gl.GL_RENDERBUFFER_X, gl.GL_DEPTH_COMPONENT24, 1024, 1024); + gl.glRenderbufferStorage(gl.GL_RENDERBUFFER_X, gl.GL_DEPTH_COMPONENT24_X, 1024, 1024); gl.glFramebufferRenderbuffer(gl.GL_FRAMEBUFFER_X, gl.GL_DEPTH_ATTACHMENT_X, gl.GL_RENDERBUFFER_X, nDepthRBO); vCurrentDepthSize = {1024, 1024}; nCurrentDepthSamples = 0; @@ -842,7 +842,7 @@ void main() gl.glRenderbufferStorageMultisample( gl.GL_RENDERBUFFER_X, targetSamples, - gl.GL_DEPTH_COMPONENT24, + gl.GL_DEPTH_COMPONENT24_X, targetSize.x, targetSize.y ); @@ -852,7 +852,7 @@ void main() // Allocate regular depth buffer gl.glRenderbufferStorage( gl.GL_RENDERBUFFER_X, - gl.GL_DEPTH_COMPONENT24, + gl.GL_DEPTH_COMPONENT24_X, targetSize.x, targetSize.y ); diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index e5638503..d56f898d 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -6347,7 +6347,7 @@ namespace olc static constexpr GLenum GL_SAMPLES_X = 0x80A9; static constexpr GLenum GL_COMPILE_STATUS_X = 0x8B81; static constexpr GLenum GL_INFO_LOG_LENGTH_X = 0x8B84; - static constexpr GLenum GL_DEPTH_COMPONENT24 = 0x81A6; + static constexpr GLenum GL_DEPTH_COMPONENT24_X = 0x81A6; static constexpr GLenum GL_DEPTH_ATTACHMENT_X = 0x8D00; static constexpr GLenum GL_RENDERBUFFER_SAMPLES_X = 0x8CAB; private: @@ -13365,7 +13365,7 @@ void main() gl.glGenRenderbuffers(1, &nDepthRBO); gl.glBindRenderbuffer(gl.GL_RENDERBUFFER_X, nDepthRBO); // Allocate with a default size (will be resized when needed) - gl.glRenderbufferStorage(gl.GL_RENDERBUFFER_X, gl.GL_DEPTH_COMPONENT24, 1024, 1024); + gl.glRenderbufferStorage(gl.GL_RENDERBUFFER_X, gl.GL_DEPTH_COMPONENT24_X, 1024, 1024); gl.glFramebufferRenderbuffer(gl.GL_FRAMEBUFFER_X, gl.GL_DEPTH_ATTACHMENT_X, gl.GL_RENDERBUFFER_X, nDepthRBO); vCurrentDepthSize = {1024, 1024}; nCurrentDepthSamples = 0; @@ -13763,7 +13763,7 @@ void main() gl.glRenderbufferStorageMultisample( gl.GL_RENDERBUFFER_X, targetSamples, - gl.GL_DEPTH_COMPONENT24, + gl.GL_DEPTH_COMPONENT24_X, targetSize.x, targetSize.y ); @@ -13773,7 +13773,7 @@ void main() // Allocate regular depth buffer gl.glRenderbufferStorage( gl.GL_RENDERBUFFER_X, - gl.GL_DEPTH_COMPONENT24, + gl.GL_DEPTH_COMPONENT24_X, targetSize.x, targetSize.y ); From 51941aec7f9ec470865e8ee841571c3ff1689399 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Mon, 9 Feb 2026 02:59:20 -0500 Subject: [PATCH 20/58] [examples] add mouse example --- examples/olcPGE3_Mouse.cpp | 170 +++++++++++++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 examples/olcPGE3_Mouse.cpp diff --git a/examples/olcPGE3_Mouse.cpp b/examples/olcPGE3_Mouse.cpp new file mode 100644 index 00000000..bca347a2 --- /dev/null +++ b/examples/olcPGE3_Mouse.cpp @@ -0,0 +1,170 @@ +/* + olc::PixelGameEngine3 Example - Mouse + + Demonstrates using the mouse input system + + Licenced under the OLC-3 License +*/ + + +// Define OLC_PGE3_APPLICATION to include the implementation of +// the Pixel Game Engine as part of this translation unit +#include +#define OLC_PGE3_APPLICATION +#include "../olcPixelGameEngine3.h" +#include + +struct Particle +{ + olc::vf2d pos; + olc::vf2d vel; + olc::Pixel color; + float life; + float maxLife; +}; + +// Example application demonstrating mouse input. This class +// overrides the olc::PixelGameEngine base class by implementing +// the OnUserCreate() and OnUserUpdate() functions +class Example_Mouse : public olc::PixelGameEngine +{ +public: + Example_Mouse() + { + + } + +public: + // Called once at the start, so create things here + bool OnUserCreate() override + { + return true; + } + + // Called every frame, so update things here + bool OnUserUpdate(float fElapsedTime) override + { + // Clear whole screen + draw.Clear(olc::Colour::DARK_BLUE); + + // Get the rounded version of the mouse position + olc::vf2d mousePos = mouse.GetPosition().round(); + + // Update scroll accumulator + scrollAcculator += mouse.GetWheel() * fElapsedTime; + scrollAcculator *= 0.95f; // Friction/Decay + + olc::vi2d barSize{200, 20}; + olc::vi2d barPosition = olc::vi2d{(ScreenSize().x / 2) - (barSize.x / 2), ScreenSize().y - (barSize.y + 10)}; + DrawScrollIndicator(barPosition, barSize, scrollAcculator); + + // Create particles on mouse buttons + if(mouse.GetButton(0).bPressed) // Left click + SpawnParticles(mousePos, olc::Colour::RED, 20); + + if(mouse.GetButton(1).bPressed) // Right click + SpawnParticles(mousePos, olc::Colour::BLUE, 20); + + if(mouse.GetButton(2).bPressed) // Middle click + SpawnParticles(mousePos, olc::Colour::GREEN, 20); + + // Update and Draw Particles + for(auto &p : vecParticles) + { + p.life -= fElapsedTime; + p.pos += p.vel * fElapsedTime; + p.vel *= 0.98f; // Friction + p.color.a = (uint8_t)((p.life / p.maxLife) * 255); + + draw.FilledCircle(p.pos, 3, p.color); + } + + // Remove dead particles + vecParticles.erase( + std::remove_if(vecParticles.begin(), vecParticles.end(), + [](Particle p) -> bool { return p.life <= 0.0f; }), + vecParticles.end() + ); + + // Draw cursor + draw.Circle(mousePos, 8, olc::Colour::WHITE); + draw.Circle(mousePos, 4, olc::Colour::CYAN); + + if(mouse.GetButton(0).bHeld) + draw.Circle(mousePos, 14, olc::Colour::RED); + + if(mouse.GetButton(1).bHeld) + draw.Circle(mousePos, 18, olc::Colour::BLUE); + + if(mouse.GetButton(2).bHeld) + draw.Circle(mousePos, 22, olc::Colour::GREEN); + + // Instructions + draw.String({10, 10}, "Mouse Example\n\nClick all the buttons!\nScroll the wheel!", olc::Colour::YELLOW); + + // Successful frame + return true; + } + + void DrawScrollIndicator(olc::vf2d pos, olc::vf2d size, float& indicatorPosition) + { + indicatorPosition = std::clamp(indicatorPosition, -1.0f, 1.0f); + draw.FilledRect(pos, size, olc::Colour::DARK_GREY); + draw.Rect(pos, size, olc::Colour::WHITE); + + olc::vf2d halfTextSize = draw.GetTextSize("SCROLL WHEEL") / 2; + draw.String(pos + (size / 2) - halfTextSize, "SCROLL WHEEL"); + + draw.FilledRect( + {pos.x + (size.x / 2) + (indicatorPosition * (size.x * .5f)) - 2, pos.y}, + {4, size.y}, + olc::Colour::YELLOW + ); + } + + void SpawnParticles(olc::vf2d pos, olc::Pixel color, int count) + { + for(int i = 0; i < count; i++) + { + float angle = (rand() / (float)RAND_MAX) * 2.0f * std::numbers::pi; + float speed = 50.0f + (rand() / (float)RAND_MAX) * 100.0f; + + Particle p; + p.pos = pos; + p.vel = olc::vf2d{ + std::cos(angle) * speed, + std::sin(angle) * speed + }; + p.color = color; + p.life = 1.0f + (rand() / (float)RAND_MAX) * 1.0f; + p.maxLife = p.life; + + vecParticles.push_back(p); + } + } +private: + std::vector vecParticles; + float scrollAcculator = 0.0f; +}; + +// Main entry point for the application +int main() +{ + // Construct demo application + Example_Mouse demo; + + // Create "screen" of 256x240 "pixels" + // with a pixel size of 4x4 actual screen pixels + PGEConfig config; + config.bVSync = true; + config.vPixelSize = { 4,4 }; + config.vScreenSize = { 256,240 }; + + if (demo.Construct(config)) + { + // Start the application + demo.Start(); + } + + return 0; +} \ No newline at end of file From 4c025b160e3186e66152872b3405cad918228a23 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Mon, 9 Feb 2026 03:11:53 -0500 Subject: [PATCH 21/58] [examples][mouse] add all the buttons PGE currently support thanks Bixxy --- examples/olcPGE3_Mouse.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/examples/olcPGE3_Mouse.cpp b/examples/olcPGE3_Mouse.cpp index bca347a2..7804632a 100644 --- a/examples/olcPGE3_Mouse.cpp +++ b/examples/olcPGE3_Mouse.cpp @@ -68,6 +68,12 @@ class Example_Mouse : public olc::PixelGameEngine if(mouse.GetButton(2).bPressed) // Middle click SpawnParticles(mousePos, olc::Colour::GREEN, 20); + if(mouse.GetButton(3).bHeld) + SpawnParticles(mousePos, olc::Colour::MAGENTA, 20); + + if(mouse.GetButton(4).bHeld) + SpawnParticles(mousePos, olc::Colour::TANGERINE, 20); + // Update and Draw Particles for(auto &p : vecParticles) { @@ -98,7 +104,13 @@ class Example_Mouse : public olc::PixelGameEngine if(mouse.GetButton(2).bHeld) draw.Circle(mousePos, 22, olc::Colour::GREEN); + + if(mouse.GetButton(3).bHeld) + draw.Circle(mousePos, 26, olc::Colour::MAGENTA); + if(mouse.GetButton(4).bHeld) + draw.Circle(mousePos, 28, olc::Colour::TANGERINE); + // Instructions draw.String({10, 10}, "Mouse Example\n\nClick all the buttons!\nScroll the wheel!", olc::Colour::YELLOW); From e0850742cfe3c4c425fb9b658c4f7615e759b2c3 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Mon, 9 Feb 2026 03:23:33 -0500 Subject: [PATCH 22/58] [examples][mouse] switch buttons 3/4 to bPressed for spawning particles --- examples/olcPGE3_Mouse.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/olcPGE3_Mouse.cpp b/examples/olcPGE3_Mouse.cpp index 7804632a..bc3c36e2 100644 --- a/examples/olcPGE3_Mouse.cpp +++ b/examples/olcPGE3_Mouse.cpp @@ -68,10 +68,10 @@ class Example_Mouse : public olc::PixelGameEngine if(mouse.GetButton(2).bPressed) // Middle click SpawnParticles(mousePos, olc::Colour::GREEN, 20); - if(mouse.GetButton(3).bHeld) + if(mouse.GetButton(3).bPressed) SpawnParticles(mousePos, olc::Colour::MAGENTA, 20); - if(mouse.GetButton(4).bHeld) + if(mouse.GetButton(4).bPressed) SpawnParticles(mousePos, olc::Colour::TANGERINE, 20); // Update and Draw Particles From 3746c9485e27784b7be5bcd47067cff0c67e6314 Mon Sep 17 00:00:00 2001 From: John Galvin <96908304+Johnnyg63@users.noreply.github.com> Date: Mon, 9 Feb 2026 12:32:55 +0000 Subject: [PATCH 23/58] Added draw3d.h/cpp, matrix4d.h and vector4d.h to the XCode project --- dev/xcode_macos/olcPGE3.xcodeproj/project.pbxproj | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/dev/xcode_macos/olcPGE3.xcodeproj/project.pbxproj b/dev/xcode_macos/olcPGE3.xcodeproj/project.pbxproj index 3e092141..562ee0d4 100644 --- a/dev/xcode_macos/olcPGE3.xcodeproj/project.pbxproj +++ b/dev/xcode_macos/olcPGE3.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 462680A42E69D03800799C36 /* image.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 460495EC2E300E000047E223 /* image.cpp */; }; 462680A52E69D15C00799C36 /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46E85DF82E39135B00B74FA3 /* OpenGL.framework */; }; 462680D12E6C3C4500799C36 /* gpu_iface.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 462680D02E6C3C4500799C36 /* gpu_iface.cpp */; }; + 466C384B2F3A0B0E00F75AF4 /* draw3d.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 466C38482F3A0B0E00F75AF4 /* draw3d.cpp */; }; 467318BA2EEC213700714D4C /* font.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 467318B92EEC213700714D4C /* font.cpp */; }; 46964CE82EE5C23300B7BCF1 /* api_macos.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 46964CE62EE5C23300B7BCF1 /* api_macos.cpp */; }; 469A92132E79B0F1007BB460 /* hw_mouse.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 469A92122E79B0F1007BB460 /* hw_mouse.cpp */; }; @@ -70,6 +71,10 @@ 460495FB2E300E0F0047E223 /* test_mh.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = test_mh.cpp; sourceTree = ""; }; 462680A22E69CDED00799C36 /* draw2d.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = draw2d.cpp; sourceTree = ""; }; 462680D02E6C3C4500799C36 /* gpu_iface.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = gpu_iface.cpp; sourceTree = ""; }; + 466C38472F3A0B0E00F75AF4 /* draw3d.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = draw3d.h; sourceTree = ""; }; + 466C38482F3A0B0E00F75AF4 /* draw3d.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = draw3d.cpp; sourceTree = ""; }; + 466C38492F3A0B0E00F75AF4 /* matrix4d.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = matrix4d.h; sourceTree = ""; }; + 466C384A2F3A0B0E00F75AF4 /* vector4d.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = vector4d.h; sourceTree = ""; }; 467318B82EEC213700714D4C /* font.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = font.h; sourceTree = ""; }; 467318B92EEC213700714D4C /* font.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = font.cpp; sourceTree = ""; }; 46964CE52EE5C23300B7BCF1 /* api_macos.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = api_macos.h; sourceTree = ""; }; @@ -166,6 +171,10 @@ 8998CE322E057F92007DDD96 /* src */ = { isa = PBXGroup; children = ( + 466C38472F3A0B0E00F75AF4 /* draw3d.h */, + 466C38482F3A0B0E00F75AF4 /* draw3d.cpp */, + 466C38492F3A0B0E00F75AF4 /* matrix4d.h */, + 466C384A2F3A0B0E00F75AF4 /* vector4d.h */, 46FD95922F2AA035004EAEC5 /* hw_keyboard.cpp */, 46FB04592F02F7DF00BFA86D /* draw2d_sw.cpp */, 467318B82EEC213700714D4C /* font.h */, @@ -271,6 +280,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 466C384B2F3A0B0E00F75AF4 /* draw3d.cpp in Sources */, 462680D12E6C3C4500799C36 /* gpu_iface.cpp in Sources */, 462680A42E69D03800799C36 /* image.cpp in Sources */, 46E85E0B2E39252B00B74FA3 /* gpu_opengl33.cpp in Sources */, From 89321b195eae7d56289936979f126755175894f6 Mon Sep 17 00:00:00 2001 From: John Galvin <96908304+Johnnyg63@users.noreply.github.com> Date: Mon, 9 Feb 2026 12:38:45 +0000 Subject: [PATCH 24/58] 149-mac-static-inits-are-not-nullptr --- dev/src/api_macos.cpp | 148 +++++++++++++++++++++++++++++++----------- 1 file changed, 111 insertions(+), 37 deletions(-) diff --git a/dev/src/api_macos.cpp b/dev/src/api_macos.cpp index 515eb92f..2017ebea 100644 --- a/dev/src/api_macos.cpp +++ b/dev/src/api_macos.cpp @@ -158,43 +158,117 @@ static constexpr const char* kVoidMethodTypeEncoding = "v@:"; namespace ObjectiveCSEL { - // Application memory management selectors - static SEL allocSel, initSel, setDelegateSel, releaseSel, isKindOfClassSel = nullptr; - - // NSApplication lifecycle and management selectors - static SEL sharedApplicationSel, activateIgnoringOtherAppsSel, setActivationPolicySel,runSel, terminateSEL = nullptr; - - // NSApplicationDelegate lifecycle methods - static SEL applicationWillFinishLaunchingSel, applicationDidFinishLaunchingSel, applicationWillTerminateSel, applicationDidBecomeActiveSel, applicationWillResignActiveSel = nullptr; - - // NSWindow creation, display, and management selectors - static SEL initWithContentRectSel, stringWithUTF8StringSel, setTitleSel, orderFrontRegardlessSel, setAcceptsMouseMovedEventsSel, makeKeyAndOrderFrontSel = nullptr; - static SEL makeKeyWindowSel, frameSel, setFrameDisplaySel, setFrameSel, makeFirstResponderSel = nullptr; - - // NSWindowDelegate lifecycle and event methods selectors - static SEL windowDidResizeSel, windowWillCloseSel, windowDidBecomeKeySel, windowDidResignKeySel, windowDidMiniaturizeSel, windowDidDeminiaturizeSel = nullptr; - - // NSResponder keyboard and mouse event methods selectors - static SEL keyDownSel, keyUpSel, mouseDownSel, mouseUpSel, mouseDraggedSel, mouseMovedSel, rightMouseDownSel, rightMouseUpSel, rightMouseDraggedSel, otherMouseDownSel = nullptr; - static SEL otherMouseUpSel, otherMouseDraggedSel, scrollWheelSel, deltaXSel, deltaYSel = nullptr; - - // Managing first responder status and keyboard focus selectors - static SEL acceptsFirstResponderSel, becomeFirstResponderSel, canBecomeKeyViewSel, needsPanelToBecomeKeySel, drawRectSel, reshapeSel, updateSel = nullptr; - - // NSOpenGL pixel format, view, and context management selectors - static SEL initWithAttributesSel, initWithFramePixelFormatSel, setContentViewSel, contentViewSel, boundsSel, convertPointFromViewSel, openGLContextSel, makeCurrentContextSel = nullptr; - static SEL setAutoresizingMaskSel, flushBufferSel, displaySel, CGLContextObjSel, setValuesSel = nullptr; - - // Extracting data from NSEvent objects selectors - static SEL keyCodeSel, charactersSel, locationInWindowSel, buttonNumberSel, clickCountSel, modifierFlagsSel, utf8StringSel = nullptr; - - // NSImage, NSBitmapImageRep, and image data access selectors - static SEL initWithContentsOfFileSel, representationsSel, countSel, objectAtIndexSel, pixelsWideSel, pixelsHighSel, bitsPerPixelSel, bytesPerRowSel, hasAlphaSel, bitmapDataSel = nullptr; - - // NSLocale selectors - static SEL currentLocaleSel, localeIdentifierSel = nullptr; - - // Initialize all selectors - called once at startup +// Application memory management selectors + static SEL allocSel = nullptr; + static SEL initSel = nullptr; + static SEL setDelegateSel = nullptr; + static SEL releaseSel = nullptr; + static SEL isKindOfClassSel = nullptr; + + // NSApplication lifecycle and management selectors + static SEL sharedApplicationSel = nullptr; + static SEL activateIgnoringOtherAppsSel = nullptr; + static SEL setActivationPolicySel = nullptr; + static SEL runSel = nullptr; + static SEL terminateSEL = nullptr; + + // NSApplicationDelegate lifecycle methods + static SEL applicationWillFinishLaunchingSel = nullptr; + static SEL applicationDidFinishLaunchingSel = nullptr; + static SEL applicationWillTerminateSel = nullptr; + static SEL applicationDidBecomeActiveSel = nullptr; + static SEL applicationWillResignActiveSel = nullptr; + + // NSWindow creation, display, and management selectors + static SEL initWithContentRectSel = nullptr; + static SEL stringWithUTF8StringSel = nullptr; + static SEL setTitleSel = nullptr; + static SEL orderFrontRegardlessSel = nullptr; + static SEL setAcceptsMouseMovedEventsSel = nullptr; + static SEL makeKeyAndOrderFrontSel = nullptr; + static SEL makeKeyWindowSel = nullptr; + static SEL frameSel = nullptr; + static SEL setFrameDisplaySel = nullptr; + static SEL setFrameSel = nullptr; + static SEL makeFirstResponderSel = nullptr; + + // NSWindowDelegate lifecycle and event methods selectors + static SEL windowDidResizeSel = nullptr; + static SEL windowWillCloseSel = nullptr; + static SEL windowDidBecomeKeySel = nullptr; + static SEL windowDidResignKeySel = nullptr; + static SEL windowDidMiniaturizeSel = nullptr; + static SEL windowDidDeminiaturizeSel = nullptr; + + // NSResponder keyboard and mouse event methods selectors + static SEL keyDownSel = nullptr; + static SEL keyUpSel = nullptr; + static SEL mouseDownSel = nullptr; + static SEL mouseUpSel = nullptr; + static SEL mouseDraggedSel = nullptr; + static SEL mouseMovedSel = nullptr; + static SEL rightMouseDownSel = nullptr; + static SEL rightMouseUpSel = nullptr; + static SEL rightMouseDraggedSel = nullptr; + static SEL otherMouseDownSel = nullptr; + static SEL otherMouseUpSel = nullptr; + static SEL otherMouseDraggedSel = nullptr; + static SEL scrollWheelSel = nullptr; + static SEL deltaXSel = nullptr; + static SEL deltaYSel = nullptr; + + // Managing first responder status and keyboard focus selectors + static SEL acceptsFirstResponderSel = nullptr; + static SEL becomeFirstResponderSel = nullptr; + static SEL canBecomeKeyViewSel = nullptr; + static SEL needsPanelToBecomeKeySel = nullptr; + static SEL drawRectSel = nullptr; + static SEL reshapeSel = nullptr; + static SEL updateSel = nullptr; + + // NSOpenGL pixel format, view, and context management selectors + static SEL initWithAttributesSel = nullptr; + static SEL initWithFramePixelFormatSel = nullptr; + static SEL setContentViewSel = nullptr; + static SEL contentViewSel = nullptr; + static SEL boundsSel = nullptr; + static SEL convertPointFromViewSel = nullptr; + static SEL openGLContextSel = nullptr; + static SEL makeCurrentContextSel = nullptr; + static SEL setAutoresizingMaskSel = nullptr; + static SEL flushBufferSel = nullptr; + static SEL displaySel = nullptr; + static SEL CGLContextObjSel = nullptr; + static SEL setValuesSel = nullptr; + + // Extracting data from NSEvent objects selectors + static SEL keyCodeSel = nullptr; + static SEL charactersSel = nullptr; + static SEL locationInWindowSel = nullptr; + static SEL buttonNumberSel = nullptr; + static SEL clickCountSel = nullptr; + static SEL modifierFlagsSel = nullptr; + static SEL utf8StringSel = nullptr; + + // NSImage, NSBitmapImageRep, and image data access selectors + static SEL initWithContentsOfFileSel = nullptr; + static SEL representationsSel = nullptr; + static SEL countSel = nullptr; + static SEL objectAtIndexSel = nullptr; + static SEL pixelsWideSel = nullptr; + static SEL pixelsHighSel = nullptr; + static SEL bitsPerPixelSel = nullptr; + static SEL bytesPerRowSel = nullptr; + static SEL hasAlphaSel = nullptr; + static SEL bitmapDataSel = nullptr; + + // NSLocale selectors + static SEL currentLocaleSel = nullptr; + static SEL localeIdentifierSel = nullptr; + static SEL currentInputContextSel = nullptr; + static SEL localizedNameSel = nullptr; + + // Initialize all selectors - called once at startup void initializeSelectors() { if (allocSel) return; // Already initialized From 8a7070b73490269b36a71751bd57444d5a9d9486 Mon Sep 17 00:00:00 2001 From: John Galvin <96908304+Johnnyg63@users.noreply.github.com> Date: Mon, 9 Feb 2026 13:39:45 +0000 Subject: [PATCH 25/58] Forget to add the SH, now back to work :) --- olcPixelGameEngine3.h | 148 +++++++++++++++++++++++++++++++----------- 1 file changed, 111 insertions(+), 37 deletions(-) diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index d56f898d..cd7b3fe0 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -8026,43 +8026,117 @@ static constexpr const char* kVoidMethodTypeEncoding = "v@:"; namespace ObjectiveCSEL { - // Application memory management selectors - static SEL allocSel, initSel, setDelegateSel, releaseSel, isKindOfClassSel = nullptr; - - // NSApplication lifecycle and management selectors - static SEL sharedApplicationSel, activateIgnoringOtherAppsSel, setActivationPolicySel,runSel, terminateSEL = nullptr; - - // NSApplicationDelegate lifecycle methods - static SEL applicationWillFinishLaunchingSel, applicationDidFinishLaunchingSel, applicationWillTerminateSel, applicationDidBecomeActiveSel, applicationWillResignActiveSel = nullptr; - - // NSWindow creation, display, and management selectors - static SEL initWithContentRectSel, stringWithUTF8StringSel, setTitleSel, orderFrontRegardlessSel, setAcceptsMouseMovedEventsSel, makeKeyAndOrderFrontSel = nullptr; - static SEL makeKeyWindowSel, frameSel, setFrameDisplaySel, setFrameSel, makeFirstResponderSel = nullptr; - - // NSWindowDelegate lifecycle and event methods selectors - static SEL windowDidResizeSel, windowWillCloseSel, windowDidBecomeKeySel, windowDidResignKeySel, windowDidMiniaturizeSel, windowDidDeminiaturizeSel = nullptr; - - // NSResponder keyboard and mouse event methods selectors - static SEL keyDownSel, keyUpSel, mouseDownSel, mouseUpSel, mouseDraggedSel, mouseMovedSel, rightMouseDownSel, rightMouseUpSel, rightMouseDraggedSel, otherMouseDownSel = nullptr; - static SEL otherMouseUpSel, otherMouseDraggedSel, scrollWheelSel, deltaXSel, deltaYSel = nullptr; - - // Managing first responder status and keyboard focus selectors - static SEL acceptsFirstResponderSel, becomeFirstResponderSel, canBecomeKeyViewSel, needsPanelToBecomeKeySel, drawRectSel, reshapeSel, updateSel = nullptr; - - // NSOpenGL pixel format, view, and context management selectors - static SEL initWithAttributesSel, initWithFramePixelFormatSel, setContentViewSel, contentViewSel, boundsSel, convertPointFromViewSel, openGLContextSel, makeCurrentContextSel = nullptr; - static SEL setAutoresizingMaskSel, flushBufferSel, displaySel, CGLContextObjSel, setValuesSel = nullptr; - - // Extracting data from NSEvent objects selectors - static SEL keyCodeSel, charactersSel, locationInWindowSel, buttonNumberSel, clickCountSel, modifierFlagsSel, utf8StringSel = nullptr; - - // NSImage, NSBitmapImageRep, and image data access selectors - static SEL initWithContentsOfFileSel, representationsSel, countSel, objectAtIndexSel, pixelsWideSel, pixelsHighSel, bitsPerPixelSel, bytesPerRowSel, hasAlphaSel, bitmapDataSel = nullptr; - - // NSLocale selectors - static SEL currentLocaleSel, localeIdentifierSel = nullptr; - - // Initialize all selectors - called once at startup +// Application memory management selectors + static SEL allocSel = nullptr; + static SEL initSel = nullptr; + static SEL setDelegateSel = nullptr; + static SEL releaseSel = nullptr; + static SEL isKindOfClassSel = nullptr; + + // NSApplication lifecycle and management selectors + static SEL sharedApplicationSel = nullptr; + static SEL activateIgnoringOtherAppsSel = nullptr; + static SEL setActivationPolicySel = nullptr; + static SEL runSel = nullptr; + static SEL terminateSEL = nullptr; + + // NSApplicationDelegate lifecycle methods + static SEL applicationWillFinishLaunchingSel = nullptr; + static SEL applicationDidFinishLaunchingSel = nullptr; + static SEL applicationWillTerminateSel = nullptr; + static SEL applicationDidBecomeActiveSel = nullptr; + static SEL applicationWillResignActiveSel = nullptr; + + // NSWindow creation, display, and management selectors + static SEL initWithContentRectSel = nullptr; + static SEL stringWithUTF8StringSel = nullptr; + static SEL setTitleSel = nullptr; + static SEL orderFrontRegardlessSel = nullptr; + static SEL setAcceptsMouseMovedEventsSel = nullptr; + static SEL makeKeyAndOrderFrontSel = nullptr; + static SEL makeKeyWindowSel = nullptr; + static SEL frameSel = nullptr; + static SEL setFrameDisplaySel = nullptr; + static SEL setFrameSel = nullptr; + static SEL makeFirstResponderSel = nullptr; + + // NSWindowDelegate lifecycle and event methods selectors + static SEL windowDidResizeSel = nullptr; + static SEL windowWillCloseSel = nullptr; + static SEL windowDidBecomeKeySel = nullptr; + static SEL windowDidResignKeySel = nullptr; + static SEL windowDidMiniaturizeSel = nullptr; + static SEL windowDidDeminiaturizeSel = nullptr; + + // NSResponder keyboard and mouse event methods selectors + static SEL keyDownSel = nullptr; + static SEL keyUpSel = nullptr; + static SEL mouseDownSel = nullptr; + static SEL mouseUpSel = nullptr; + static SEL mouseDraggedSel = nullptr; + static SEL mouseMovedSel = nullptr; + static SEL rightMouseDownSel = nullptr; + static SEL rightMouseUpSel = nullptr; + static SEL rightMouseDraggedSel = nullptr; + static SEL otherMouseDownSel = nullptr; + static SEL otherMouseUpSel = nullptr; + static SEL otherMouseDraggedSel = nullptr; + static SEL scrollWheelSel = nullptr; + static SEL deltaXSel = nullptr; + static SEL deltaYSel = nullptr; + + // Managing first responder status and keyboard focus selectors + static SEL acceptsFirstResponderSel = nullptr; + static SEL becomeFirstResponderSel = nullptr; + static SEL canBecomeKeyViewSel = nullptr; + static SEL needsPanelToBecomeKeySel = nullptr; + static SEL drawRectSel = nullptr; + static SEL reshapeSel = nullptr; + static SEL updateSel = nullptr; + + // NSOpenGL pixel format, view, and context management selectors + static SEL initWithAttributesSel = nullptr; + static SEL initWithFramePixelFormatSel = nullptr; + static SEL setContentViewSel = nullptr; + static SEL contentViewSel = nullptr; + static SEL boundsSel = nullptr; + static SEL convertPointFromViewSel = nullptr; + static SEL openGLContextSel = nullptr; + static SEL makeCurrentContextSel = nullptr; + static SEL setAutoresizingMaskSel = nullptr; + static SEL flushBufferSel = nullptr; + static SEL displaySel = nullptr; + static SEL CGLContextObjSel = nullptr; + static SEL setValuesSel = nullptr; + + // Extracting data from NSEvent objects selectors + static SEL keyCodeSel = nullptr; + static SEL charactersSel = nullptr; + static SEL locationInWindowSel = nullptr; + static SEL buttonNumberSel = nullptr; + static SEL clickCountSel = nullptr; + static SEL modifierFlagsSel = nullptr; + static SEL utf8StringSel = nullptr; + + // NSImage, NSBitmapImageRep, and image data access selectors + static SEL initWithContentsOfFileSel = nullptr; + static SEL representationsSel = nullptr; + static SEL countSel = nullptr; + static SEL objectAtIndexSel = nullptr; + static SEL pixelsWideSel = nullptr; + static SEL pixelsHighSel = nullptr; + static SEL bitsPerPixelSel = nullptr; + static SEL bytesPerRowSel = nullptr; + static SEL hasAlphaSel = nullptr; + static SEL bitmapDataSel = nullptr; + + // NSLocale selectors + static SEL currentLocaleSel = nullptr; + static SEL localeIdentifierSel = nullptr; + static SEL currentInputContextSel = nullptr; + static SEL localizedNameSel = nullptr; + + // Initialize all selectors - called once at startup void initializeSelectors() { if (allocSel) return; // Already initialized From 780a7da46e9f60cbfdb0391b662751f30fb7dd79 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Mon, 9 Feb 2026 15:49:04 -0500 Subject: [PATCH 26/58] [x11] undefine None macro and define a constexpr int in the X11 namespace --- dev/src/gpu_opengl33.cpp | 2 +- dev/src/host_lin_x11.cpp | 2 +- dev/src/host_lin_x11.h | 2 ++ olcPixelGameEngine3.h | 6 ++++-- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/dev/src/gpu_opengl33.cpp b/dev/src/gpu_opengl33.cpp index 02fa8542..008e23b6 100644 --- a/dev/src/gpu_opengl33.cpp +++ b/dev/src/gpu_opengl33.cpp @@ -256,7 +256,7 @@ void main() #if OLC_HOST == OLC_HOST_LINUX_X11 const auto window_handle = reinterpret_cast(os_win_id[0]); auto* display = reinterpret_cast(os_win_id[1]); - GLint olc_GLAttribs[] = { GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None }; + GLint olc_GLAttribs[] = { GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, X11::None }; X11::XVisualInfo* olc_VisualInfo = X11::glXChooseVisual(display, 0, olc_GLAttribs); glRenderContext = X11::glXCreateContext(display, olc_VisualInfo, nullptr, GL_TRUE); diff --git a/dev/src/host_lin_x11.cpp b/dev/src/host_lin_x11.cpp index 5637473e..f7c34b8b 100644 --- a/dev/src/host_lin_x11.cpp +++ b/dev/src/host_lin_x11.cpp @@ -270,7 +270,7 @@ namespace olc::host { // Based on the display capabilities, configure the appearance of the window // to do this namespacing, both x11 and glx have to be included in the x11 namespace - GLint olc_GLAttribs[] = { GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None }; + GLint olc_GLAttribs[] = { GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, X11::None }; olc_VisualInfo = glXChooseVisual(olc_Display, 0, olc_GLAttribs); olc_ColourMap = XCreateColormap(olc_Display, olc_WindowRoot, olc_VisualInfo->visual, AllocNone); olc_SetWindowAttribs.colormap = olc_ColourMap; diff --git a/dev/src/host_lin_x11.h b/dev/src/host_lin_x11.h index f2f070f8..adbd88fd 100644 --- a/dev/src/host_lin_x11.h +++ b/dev/src/host_lin_x11.h @@ -20,6 +20,8 @@ namespace X11 #include #include #include +#undef None +constexpr int None = 0L; } namespace olc::host diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index cd7b3fe0..e49f465a 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -5459,6 +5459,8 @@ namespace X11 #include #include #include +#undef None +constexpr int None = 0L; } namespace olc::host @@ -10267,7 +10269,7 @@ namespace olc::host { // Based on the display capabilities, configure the appearance of the window // to do this namespacing, both x11 and glx have to be included in the x11 namespace - GLint olc_GLAttribs[] = { GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None }; + GLint olc_GLAttribs[] = { GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, X11::None }; olc_VisualInfo = glXChooseVisual(olc_Display, 0, olc_GLAttribs); olc_ColourMap = XCreateColormap(olc_Display, olc_WindowRoot, olc_VisualInfo->visual, AllocNone); olc_SetWindowAttribs.colormap = olc_ColourMap; @@ -13251,7 +13253,7 @@ void main() #if OLC_HOST == OLC_HOST_LINUX_X11 const auto window_handle = reinterpret_cast(os_win_id[0]); auto* display = reinterpret_cast(os_win_id[1]); - GLint olc_GLAttribs[] = { GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, None }; + GLint olc_GLAttribs[] = { GLX_RGBA, GLX_DEPTH_SIZE, 24, GLX_DOUBLEBUFFER, X11::None }; X11::XVisualInfo* olc_VisualInfo = X11::glXChooseVisual(display, 0, olc_GLAttribs); glRenderContext = X11::glXCreateContext(display, olc_VisualInfo, nullptr, GL_TRUE); From 562c2e9ae2f161e5f66704a84cd2d09860d65dc5 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Mon, 9 Feb 2026 16:08:12 -0500 Subject: [PATCH 27/58] refactor mouse support and support all 5 buttons properly --- dev/src/host_web_emscripten.cpp | 59 +++++++++++++++++-------------- dev/src/host_web_emscripten.h | 2 ++ olcPixelGameEngine3.h | 61 +++++++++++++++++++-------------- 3 files changed, 70 insertions(+), 52 deletions(-) diff --git a/dev/src/host_web_emscripten.cpp b/dev/src/host_web_emscripten.cpp index f05eff90..f7408a1b 100644 --- a/dev/src/host_web_emscripten.cpp +++ b/dev/src/host_web_emscripten.cpp @@ -237,6 +237,14 @@ namespace olc::host mapKeys[DOM_PK_COMMA] = Key::COMMA; mapKeys[DOM_PK_MINUS] = Key::MINUS; mapKeys[DOM_PK_PERIOD] = Key::PERIOD; + + // define mouse buttons + mapMouseButtons[0] = 0; // left click + mapMouseButtons[1] = 2; // middle click + mapMouseButtons[2] = 1; // right click + mapMouseButtons[3] = 3; + mapMouseButtons[4] = 4; + } bool Host_Web_Emscripten::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) @@ -387,37 +395,36 @@ namespace olc::host //Mouse Movement if (eventType == EMSCRIPTEN_EVENT_MOUSEMOVE) - olc_OnMouseMove(pCallbackData->pWindow, {e->targetX, e->targetY}); - - - //Mouse button press - if (e->button == 0) // left click { - if (eventType == EMSCRIPTEN_EVENT_MOUSEDOWN) - olc_OnMouseButton(pCallbackData->pWindow, 0, true); - else if (eventType == EMSCRIPTEN_EVENT_MOUSEUP) - olc_OnMouseButton(pCallbackData->pWindow, 0, false); - } - - if (e->button == 2) // right click - { - if (eventType == EMSCRIPTEN_EVENT_MOUSEDOWN) - olc_OnMouseButton(pCallbackData->pWindow, 1, true); - else if (eventType == EMSCRIPTEN_EVENT_MOUSEUP) - olc_OnMouseButton(pCallbackData->pWindow, 1, false); + olc_OnMouseMove(pCallbackData->pWindow, {e->targetX, e->targetY}); + return EM_FALSE; } - if (e->button == 1) // middle click + switch(eventType) { - if (eventType == EMSCRIPTEN_EVENT_MOUSEDOWN) - olc_OnMouseButton(pCallbackData->pWindow, 2, true); - else if (eventType == EMSCRIPTEN_EVENT_MOUSEUP) - olc_OnMouseButton(pCallbackData->pWindow, 2, false); - - //at the moment only middle mouse needs to consume events. - return EM_TRUE; + case EMSCRIPTEN_EVENT_MOUSEDOWN: + { + auto it = pCallbackData->pHost->mapMouseButtons.find(e->button); + if(it != pCallbackData->pHost->mapMouseButtons.end()) + { + olc_OnMouseButton(pCallbackData->pWindow, it->second, true); + } + return EM_TRUE; + } + break; + case EMSCRIPTEN_EVENT_MOUSEUP: // deliberate fallthrough + { + auto it = pCallbackData->pHost->mapMouseButtons.find(e->button); + if(it != pCallbackData->pHost->mapMouseButtons.end()) + { + olc_OnMouseButton(pCallbackData->pWindow, it->second, false); + } + return EM_TRUE; + } + break; + default: break; } - + return EM_FALSE; } diff --git a/dev/src/host_web_emscripten.h b/dev/src/host_web_emscripten.h index f16ea1a9..22d34605 100644 --- a/dev/src/host_web_emscripten.h +++ b/dev/src/host_web_emscripten.h @@ -91,6 +91,8 @@ namespace olc::host // Map of system keycodes to olc::Keycodes std::unordered_map mapKeys; + // Map of system mouse buttons to olc mouse buttons + std::unordered_map mapMouseButtons; }; diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index e49f465a..7123d0d9 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -5781,6 +5781,8 @@ namespace olc::host // Map of system keycodes to olc::Keycodes std::unordered_map mapKeys; + // Map of system mouse buttons to olc mouse buttons + std::unordered_map mapMouseButtons; }; @@ -11407,6 +11409,14 @@ namespace olc::host mapKeys[DOM_PK_COMMA] = Key::COMMA; mapKeys[DOM_PK_MINUS] = Key::MINUS; mapKeys[DOM_PK_PERIOD] = Key::PERIOD; + + // define mouse buttons + mapMouseButtons[0] = 0; // left click + mapMouseButtons[1] = 2; // middle click + mapMouseButtons[2] = 1; // right click + mapMouseButtons[3] = 3; + mapMouseButtons[4] = 4; + } bool Host_Web_Emscripten::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) @@ -11557,37 +11567,36 @@ namespace olc::host //Mouse Movement if (eventType == EMSCRIPTEN_EVENT_MOUSEMOVE) - olc_OnMouseMove(pCallbackData->pWindow, {e->targetX, e->targetY}); - - - //Mouse button press - if (e->button == 0) // left click { - if (eventType == EMSCRIPTEN_EVENT_MOUSEDOWN) - olc_OnMouseButton(pCallbackData->pWindow, 0, true); - else if (eventType == EMSCRIPTEN_EVENT_MOUSEUP) - olc_OnMouseButton(pCallbackData->pWindow, 0, false); - } - - if (e->button == 2) // right click - { - if (eventType == EMSCRIPTEN_EVENT_MOUSEDOWN) - olc_OnMouseButton(pCallbackData->pWindow, 1, true); - else if (eventType == EMSCRIPTEN_EVENT_MOUSEUP) - olc_OnMouseButton(pCallbackData->pWindow, 1, false); + olc_OnMouseMove(pCallbackData->pWindow, {e->targetX, e->targetY}); + return EM_FALSE; } - if (e->button == 1) // middle click + switch(eventType) { - if (eventType == EMSCRIPTEN_EVENT_MOUSEDOWN) - olc_OnMouseButton(pCallbackData->pWindow, 2, true); - else if (eventType == EMSCRIPTEN_EVENT_MOUSEUP) - olc_OnMouseButton(pCallbackData->pWindow, 2, false); - - //at the moment only middle mouse needs to consume events. - return EM_TRUE; + case EMSCRIPTEN_EVENT_MOUSEDOWN: + { + auto it = pCallbackData->pHost->mapMouseButtons.find(e->button); + if(it != pCallbackData->pHost->mapMouseButtons.end()) + { + olc_OnMouseButton(pCallbackData->pWindow, it->second, true); + } + return EM_TRUE; + } + break; + case EMSCRIPTEN_EVENT_MOUSEUP: // deliberate fallthrough + { + auto it = pCallbackData->pHost->mapMouseButtons.find(e->button); + if(it != pCallbackData->pHost->mapMouseButtons.end()) + { + olc_OnMouseButton(pCallbackData->pWindow, it->second, false); + } + return EM_TRUE; + } + break; + default: break; } - + return EM_FALSE; } From ef6e82efb710ccf7a32324bb7d4b076e8441cd02 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Mon, 9 Feb 2026 16:20:41 -0500 Subject: [PATCH 28/58] [x11] refactor mouse to support all 5 buttons properly --- dev/src/host_lin_x11.cpp | 35 ++++++++++++++++++++++------------- dev/src/host_lin_x11.h | 1 + olcPixelGameEngine3.h | 36 +++++++++++++++++++++++------------- 3 files changed, 46 insertions(+), 26 deletions(-) diff --git a/dev/src/host_lin_x11.cpp b/dev/src/host_lin_x11.cpp index f7c34b8b..f777d9b3 100644 --- a/dev/src/host_lin_x11.cpp +++ b/dev/src/host_lin_x11.cpp @@ -71,6 +71,12 @@ namespace olc::host mapKeys[XK_minus] = Key::MINUS; // the minus key on any keyboard mapKeys[XK_Caps_Lock] = Key::CAPS_LOCK; + + mapMouseButtons[1] = 0; // left click + mapMouseButtons[2] = 2; // middle click + mapMouseButtons[3] = 1; // right click + mapMouseButtons[8] = 3; + mapMouseButtons[9] = 4; } bool Host_Linux_X11::OnApplicationStart(olc::PixelGameEngine* pPrimary) @@ -183,28 +189,31 @@ namespace olc::host else if (xev.type == ButtonPress) { if(auto* pge_window = get_pge_window(xev.xbutton.window); pge_window) { + auto it = mapMouseButtons.find(xev.xbutton.button); + if(it != mapMouseButtons.end()) + { + pge_window->olc_OnMouseButton(mapMouseButtons[xev.xbutton.button], true); + continue; // Thank you. NEXT!!! + } + + // If we make it here, we may be dealing with scrolling buttons switch (xev.xbutton.button) { - case 1: pge_window->olc_OnMouseButton(0, true); break; - case 2: pge_window->olc_OnMouseButton(2, true); break; - case 3: pge_window->olc_OnMouseButton(1, true); break; - case 4: pge_window->olc_OnMouseWheel(120); break; - case 5: pge_window->olc_OnMouseWheel(-120); break; - default: break; + case 4: pge_window->olc_OnMouseWheel(120); break; + case 5: pge_window->olc_OnMouseWheel(-120); break; + default: break; } - } } else if (xev.type == ButtonRelease) { if(auto* pge_window = get_pge_window(xev.xbutton.window); pge_window) { - switch (xev.xbutton.button) + auto it = mapMouseButtons.find(xev.xbutton.button); + if(it != mapMouseButtons.end()) { - case 1: pge_window->olc_OnMouseButton(0, false); break; - case 2: pge_window->olc_OnMouseButton(2, false); break; - case 3: pge_window->olc_OnMouseButton(1, false); break; - default: break; - } + pge_window->olc_OnMouseButton(mapMouseButtons[xev.xbutton.button], false); + continue; // Thank you. NEXT!!! + } } } else if (xev.type == MotionNotify) diff --git a/dev/src/host_lin_x11.h b/dev/src/host_lin_x11.h index adbd88fd..eb33e733 100644 --- a/dev/src/host_lin_x11.h +++ b/dev/src/host_lin_x11.h @@ -64,6 +64,7 @@ namespace olc::host std::atomic systemActive {true}; std::unordered_map mapKeys; + std::unordered_map mapMouseButtons; // Keyboard Layout Variables olc::KeyboardLayout keyboardLayout = OLC_DEFAULT_KEYBOARD_LAYOUT; diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index e49f465a..1894c382 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -5503,6 +5503,7 @@ namespace olc::host std::atomic systemActive {true}; std::unordered_map mapKeys; + std::unordered_map mapMouseButtons; // Keyboard Layout Variables olc::KeyboardLayout keyboardLayout = OLC_DEFAULT_KEYBOARD_LAYOUT; @@ -10070,6 +10071,12 @@ namespace olc::host mapKeys[XK_minus] = Key::MINUS; // the minus key on any keyboard mapKeys[XK_Caps_Lock] = Key::CAPS_LOCK; + + mapMouseButtons[1] = 0; // left click + mapMouseButtons[2] = 2; // middle click + mapMouseButtons[3] = 1; // right click + mapMouseButtons[8] = 3; + mapMouseButtons[9] = 4; } bool Host_Linux_X11::OnApplicationStart(olc::PixelGameEngine* pPrimary) @@ -10182,28 +10189,31 @@ namespace olc::host else if (xev.type == ButtonPress) { if(auto* pge_window = get_pge_window(xev.xbutton.window); pge_window) { + auto it = mapMouseButtons.find(xev.xbutton.button); + if(it != mapMouseButtons.end()) + { + pge_window->olc_OnMouseButton(mapMouseButtons[xev.xbutton.button], true); + continue; // Thank you. NEXT!!! + } + + // If we make it here, we may be dealing with scrolling buttons switch (xev.xbutton.button) { - case 1: pge_window->olc_OnMouseButton(0, true); break; - case 2: pge_window->olc_OnMouseButton(2, true); break; - case 3: pge_window->olc_OnMouseButton(1, true); break; - case 4: pge_window->olc_OnMouseWheel(120); break; - case 5: pge_window->olc_OnMouseWheel(-120); break; - default: break; + case 4: pge_window->olc_OnMouseWheel(120); break; + case 5: pge_window->olc_OnMouseWheel(-120); break; + default: break; } - } } else if (xev.type == ButtonRelease) { if(auto* pge_window = get_pge_window(xev.xbutton.window); pge_window) { - switch (xev.xbutton.button) + auto it = mapMouseButtons.find(xev.xbutton.button); + if(it != mapMouseButtons.end()) { - case 1: pge_window->olc_OnMouseButton(0, false); break; - case 2: pge_window->olc_OnMouseButton(2, false); break; - case 3: pge_window->olc_OnMouseButton(1, false); break; - default: break; - } + pge_window->olc_OnMouseButton(mapMouseButtons[xev.xbutton.button], false); + continue; // Thank you. NEXT!!! + } } } else if (xev.type == MotionNotify) From 2abe6cb3d289f5d3afddec0b0606daf4bffb77e7 Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Mon, 9 Feb 2026 21:24:28 +0000 Subject: [PATCH 29/58] refactored draw2d/3d ->draw, also took out sw renderer for now (with a heavy heart) --- dev/msvc/olcPGE3.vcxproj | 7 +- dev/msvc/olcPGE3.vcxproj.filters | 13 +- .../olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj | 6 + .../olcPGE3_BuildSH.vcxproj.filters | 3 + dev/src/core.cpp | 4 +- dev/src/core.h | 9 +- dev/src/{draw2d.cpp => draw.cpp} | 382 +++-- dev/src/{draw2d.h => draw.h} | 249 +-- dev/src/draw2d_sw.cpp | 538 ------ dev/src/draw3d.cpp | 267 --- dev/src/draw3d.h | 201 --- dev/src/sh_template.h | 16 +- dev/src/window.h | 2 +- dev/tests/test_mh.cpp | 22 +- examples/olcPGE3_3DCube.cpp | 20 +- olcPixelGameEngine3.h | 1521 ++++------------- 16 files changed, 705 insertions(+), 2555 deletions(-) rename dev/src/{draw2d.cpp => draw.cpp} (65%) rename dev/src/{draw2d.h => draw.h} (86%) delete mode 100644 dev/src/draw2d_sw.cpp delete mode 100644 dev/src/draw3d.cpp delete mode 100644 dev/src/draw3d.h diff --git a/dev/msvc/olcPGE3.vcxproj b/dev/msvc/olcPGE3.vcxproj index 44257486..78ff7d73 100644 --- a/dev/msvc/olcPGE3.vcxproj +++ b/dev/msvc/olcPGE3.vcxproj @@ -195,8 +195,7 @@ - - + @@ -278,9 +277,7 @@ - - - + diff --git a/dev/msvc/olcPGE3.vcxproj.filters b/dev/msvc/olcPGE3.vcxproj.filters index 56e01d4f..4ecd69df 100644 --- a/dev/msvc/olcPGE3.vcxproj.filters +++ b/dev/msvc/olcPGE3.vcxproj.filters @@ -69,7 +69,7 @@ Header Files - + Header Files @@ -165,9 +165,6 @@ Header Files - - Header Files - @@ -188,7 +185,7 @@ Source Files\tests - + Source Files @@ -200,9 +197,6 @@ Source Files - - Source Files - Hosts\Mac Specific @@ -239,9 +233,6 @@ Hosts\Android Specific - - Source Files - Source Files diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj index 59440b1c..081d8eb6 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj @@ -68,6 +68,12 @@ true true + + true + true + true + true + true true diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters index 98b841bf..ed7a464d 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters @@ -84,6 +84,9 @@ Source Files + + Source Files + diff --git a/dev/src/core.cpp b/dev/src/core.cpp index fc91d03e..98958906 100644 --- a/dev/src/core.cpp +++ b/dev/src/core.cpp @@ -50,7 +50,7 @@ //! START IMPLEMENTATION namespace olc { - PGEWindow::PGEWindow() : Window(), draw(), draw3d(draw) + PGEWindow::PGEWindow() : Window(), draw() { } @@ -252,7 +252,7 @@ namespace olc return imgPrimary; } - olc::Draw2D& PGEWindow::GetDraw() + olc::Draw& PGEWindow::GetDraw() { return draw; } diff --git a/dev/src/core.h b/dev/src/core.h index df39e7cd..7c512a7a 100644 --- a/dev/src/core.h +++ b/dev/src/core.h @@ -21,8 +21,7 @@ #include "host_iface.h" #include "imload_iface.h" #include "font.h" -#include "draw2d.h" -#include "draw3d.h" +#include "draw.h" //! END CUSTOMHEADER GLOBAL //! START DECLARATION @@ -98,7 +97,7 @@ namespace olc public: // Returns the image that represents the primary drawing surface olc::Image& GetScreen(); - olc::Draw2D& GetDraw(); + olc::Draw& GetDraw(); // Input devices are handled by a regular olc::Window, but for convenience... olc::hw::Mouse& GetMouse(); @@ -114,9 +113,7 @@ namespace olc virtual bool olc_WindowUpdate(const float fElapsedTime, const float fTotalElapsedTime); protected: - olc::Draw2D draw; - olc::Draw3D draw3d; - + olc::Draw draw; private: olc::Image imgPrimary; diff --git a/dev/src/draw2d.cpp b/dev/src/draw.cpp similarity index 65% rename from dev/src/draw2d.cpp rename to dev/src/draw.cpp index e115a0f9..caf19ccf 100644 --- a/dev/src/draw2d.cpp +++ b/dev/src/draw.cpp @@ -1,4 +1,4 @@ -#include "draw2d.h" +#include "draw.h" #include "gpu_iface.h" @@ -6,22 +6,22 @@ using namespace olc; // Some local pools to reduce allocations -thread_local Draw2D::buffer Draw2D::buffPoints; -thread_local Draw2D::buffer Draw2D::buffColours; -thread_local Draw2D::buffer Draw2D::buffUnitCirclePoints; -thread_local Draw2D::buffer Draw2D::vecGPUTasks; +thread_local Draw::buffer Draw::buffPoints; +thread_local Draw::buffer Draw::buffColours; +thread_local Draw::buffer Draw::buffUnitCirclePoints; +thread_local Draw::buffer Draw::vecGPUTasks; -Draw2D::Draw2D() +Draw::Draw() { vecGPUTasks.reserve(256); } -void Draw2D::SetGPU(olc::gpu::Renderer* const renderer) +void Draw::SetGPU(olc::gpu::Renderer* const renderer) { pRenderer = renderer; } -void Draw2D::SetTarget(olc::Image& image) +void Draw::SetTarget(olc::Image& image) { // Perform any outstanding tasks for current target ProcessGPUTasks(); @@ -46,17 +46,17 @@ void Draw2D::SetTarget(olc::Image& image) pRenderer->SetViewport({ 0,0 }, pTarget->Size()); } -olc::Image& olc::Draw2D::GetTarget() +olc::Image& olc::Draw::GetTarget() { return *pTarget; } -olc::vi2d olc::Draw2D::GetTargetSize() +olc::vi2d olc::Draw::GetTargetSize() { return pTarget->Size(); } -void olc::Draw2D::ProcessGPUTasks() +void olc::Draw::ProcessGPUTasks() { for (const auto& task : vecGPUTasks.data) pRenderer->DoGPUTask(task); @@ -66,7 +66,7 @@ void olc::Draw2D::ProcessGPUTasks() vecGPUTasks.data.clear(); } -void Draw2D::PrepareTargetForSW() +void Draw::PrepareTargetForSW() { if (pTarget->BoundToGPU()) { @@ -80,13 +80,10 @@ void Draw2D::PrepareTargetForSW() pTarget->BindCPU(); drawMetrics.nGPUtoCPUTransfers++; - - // Create a scanline buffer the height of this target - vScanlines.resize(size_t(pTarget->Size().y), {}); } } -void Draw2D::PrepareTargetForHW() +void Draw::PrepareTargetForHW() { if (pTarget->BoundToCPU()) { @@ -100,7 +97,7 @@ void Draw2D::PrepareTargetForHW() } } -void Draw2D::PrepareImageForSW(olc::Image& image) +void Draw::PrepareImageForSW(olc::Image& image) { if (image.BoundToGPU()) { @@ -117,7 +114,7 @@ void Draw2D::PrepareImageForSW(olc::Image& image) } } -void Draw2D::PrepareImageForHW(olc::Image& image) +void Draw::PrepareImageForHW(olc::Image& image) { if (image.BoundToCPU()) { @@ -138,7 +135,7 @@ void Draw2D::PrepareImageForHW(olc::Image& image) } } -bool olc::Draw2D::SetShader(const olc::gpu::Shader& shader) +bool olc::Draw::SetShader(const olc::gpu::Shader& shader) { // Finish all drawing with current shader ProcessGPUTasks(); @@ -149,85 +146,85 @@ bool olc::Draw2D::SetShader(const olc::gpu::Shader& shader) return pRenderer->ApplyShader(shader); } -bool olc::Draw2D::ResetShader() +bool olc::Draw::ResetShader() { ProcessGPUTasks(); drawMetrics.nShaderChanges++; return pRenderer->ApplyDefaultShader(); } -bool olc::Draw2D::SetShaderUniform(const std::string& name, const float value) +bool olc::Draw::SetShaderUniform(const std::string& name, const float value) { return pRenderer->SetUniform(name, value); } -bool olc::Draw2D::SetShaderUniform(const std::string& name, const olc::vf2d& value) +bool olc::Draw::SetShaderUniform(const std::string& name, const olc::vf2d& value) { return pRenderer->SetUniform(name, value); } -bool olc::Draw2D::SetShaderUniform(const std::string& name, const olc::Pixel value) +bool olc::Draw::SetShaderUniform(const std::string& name, const olc::Pixel value) { return pRenderer->SetUniform(name, value); } -bool olc::Draw2D::SetShaderTexture(const uint32_t nSlot, olc::Image& image) +bool olc::Draw::SetShaderTexture(const uint32_t nSlot, olc::Image& image) { PrepareImageForHW(image); return pRenderer->AssignTextureSource(nSlot, image.GetGPUID()); } -void olc::Draw2D::ResetDrawMetrics() +void olc::Draw::ResetDrawMetrics() { drawMetrics = sDrawMetrics(); } -olc::Draw2D::sDrawMetrics olc::Draw2D::GetDrawMetrics() const +olc::Draw::sDrawMetrics olc::Draw::GetDrawMetrics() const { return drawMetrics; } -void olc::Draw2D::WorldReset() +void olc::Draw::WorldReset() { transformAffine = olc::tf2d(); } -void olc::Draw2D::WorldScale(const olc::vf2d& vScale) +void olc::Draw::WorldScale(const olc::vf2d& vScale) { transformAffine.scale(vScale); } -void olc::Draw2D::WorldOffset(const olc::vf2d& vOffset) +void olc::Draw::WorldOffset(const olc::vf2d& vOffset) { transformAffine.translate(vOffset); } -void olc::Draw2D::WorldRotate(const float& fTheta, const olc::vf2d& vPoint) +void olc::Draw::WorldRotate(const float& fTheta, const olc::vf2d& vPoint) { transformAffine.rotate(fTheta, vPoint); } -void olc::Draw2D::SetWorldTransform(const olc::tf2d& trans) +void olc::Draw::SetWorldTransform(const olc::tf2d& trans) { transformAffine = trans; } -olc::tf2d& olc::Draw2D::GetWorldTransform() +olc::tf2d& olc::Draw::GetWorldTransform() { return transformAffine; } -olc::vf2d olc::Draw2D::WorldToScreen(const olc::vf2d& v) const +olc::vf2d olc::Draw::WorldToScreen(const olc::vf2d& v) const { return transformAffine.forward(v); } -olc::vf2d olc::Draw2D::ScreenToWorld(const olc::vf2d& v) const +olc::vf2d olc::Draw::ScreenToWorld(const olc::vf2d& v) const { return transformAffine.inverse(v); } -void Draw2D::Pixel(const olc::vf2d& pos, const olc::Pixel col, const olc::Pixel tint) +void Draw::Pixel(const olc::vf2d& pos, const olc::Pixel col, const olc::Pixel tint) { // Check if in bounds olc::vf2d tpos = transformAffine.forwardRound(pos); @@ -240,25 +237,25 @@ void Draw2D::Pixel(const olc::vf2d& pos, const olc::Pixel col, const olc::Pixel // otherwise do nothing } -olc::Pixel olc::Draw2D::GetPixel(olc::Image& image, const olc::vf2d& pos) +olc::Pixel olc::Draw::GetPixel(olc::Image& image, const olc::vf2d& pos) { PrepareImageForSW(image); return image.Pixel(pos); } -olc::Pixel olc::Draw2D::GetPixel(const olc::vf2d& pos) +olc::Pixel olc::Draw::GetPixel(const olc::vf2d& pos) { PrepareImageForSW(GetTarget()); return GetTarget().Pixel(pos); } -void olc::Draw2D::Clear(const olc::Pixel& col) +void olc::Draw::Clear(const olc::Pixel& col) { PrepareTargetForHW(); pRenderer->ClearViewport(col, true, true); } -GPUTask olc::Draw2D::TaskDrawLine(const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) +GPUTask olc::Draw::TaskDrawLine(const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) { GPUTask task; task.structure = olc::Structure::Line; @@ -272,7 +269,7 @@ GPUTask olc::Draw2D::TaskDrawLine(const std::vector& vPoints, const s return task; } -GPUTask olc::Draw2D::TaskDrawPolygon(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) +GPUTask olc::Draw::TaskDrawPolygon(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) { GPUTask task; task.structure = structure; @@ -284,7 +281,7 @@ GPUTask olc::Draw2D::TaskDrawPolygon(olc::Structure structure, const std::vector return task; } -GPUTask olc::Draw2D::TaskDrawPolygon(olc::Structure structure, const std::vector& vPoints, const olc::Pixel colour, const olc::Pixel tint) +GPUTask olc::Draw::TaskDrawPolygon(olc::Structure structure, const std::vector& vPoints, const olc::Pixel colour, const olc::Pixel tint) { GPUTask task; task.structure = structure; @@ -296,7 +293,7 @@ GPUTask olc::Draw2D::TaskDrawPolygon(olc::Structure structure, const std::vector return task; } -GPUTask olc::Draw2D::TaskFillPolygon(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) +GPUTask olc::Draw::TaskFillPolygon(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) { GPUTask task; task.structure = structure; @@ -307,7 +304,7 @@ GPUTask olc::Draw2D::TaskFillPolygon(olc::Structure structure, const std::vector return task; } -GPUTask olc::Draw2D::TaskFillPolygon(olc::Structure structure, const std::vector& vPoints, const olc::Pixel colour, const olc::Pixel tint) +GPUTask olc::Draw::TaskFillPolygon(olc::Structure structure, const std::vector& vPoints, const olc::Pixel colour, const olc::Pixel tint) { GPUTask task; task.structure = structure; @@ -318,7 +315,7 @@ GPUTask olc::Draw2D::TaskFillPolygon(olc::Structure structure, const std::vector return task; } -GPUTask olc::Draw2D::TaskTexturedPolygon(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const std::vector& vTexCoords, olc::Image* const image, const olc::Pixel tint) +GPUTask olc::Draw::TaskTexturedPolygon(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const std::vector& vTexCoords, olc::Image* const image, const olc::Pixel tint) { GPUTask task; task.structure = structure; @@ -330,7 +327,7 @@ GPUTask olc::Draw2D::TaskTexturedPolygon(olc::Structure structure, const std::ve return task; } -GPUTask olc::Draw2D::TaskTexturedPolygon(olc::Structure structure, const std::vector& vPoints, const std::vector& vZWs, const std::vector& vColours, const std::vector& vTexCoords, olc::Image* const image, const olc::Pixel tint) +GPUTask olc::Draw::TaskTexturedPolygon(olc::Structure structure, const std::vector& vPoints, const std::vector& vZWs, const std::vector& vColours, const std::vector& vTexCoords, olc::Image* const image, const olc::Pixel tint) { GPUTask task; task.structure = structure; @@ -342,9 +339,58 @@ GPUTask olc::Draw2D::TaskTexturedPolygon(olc::Structure structure, const std::ve return task; } +GPUTask olc::Draw::TaskWireMesh(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) +{ + GPUTask task; + task.structure = structure; + task.tint = tint; + task.vertexBuffer.resize(vPoints.size()); + task.bWireframe = true; + task.bDepth = bDepth; + task.cullmode = cullMode; + task.bIs3D = true; + task.mvpMatrix = matMVP.m; + for (size_t i = 0; i < vPoints.size(); i++) + task.vertexBuffer[i] = { {vPoints[i].x, vPoints[i].y, vPoints[i].z, vPoints[i].w}, vColours[i], {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + return task; +} -const GPUTask& Draw2D::Line(const olc::vf2d& p1, const olc::vf2d& p2, const olc::Pixel col, const olc::Pixel tint) +GPUTask olc::Draw::TaskFillMesh(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) +{ + GPUTask task; + task.structure = structure; + task.tint = tint; + task.vertexBuffer.resize(vPoints.size()); + task.bDepth = bDepth; + task.cullmode = cullMode; + task.bIs3D = true; + task.mvpMatrix = matMVP.m; + + for (size_t i = 0; i < vPoints.size(); i++) + task.vertexBuffer[i] = { {vPoints[i].x, vPoints[i].y, vPoints[i].z, vPoints[i].w}, vColours[i], {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + return task; +} + +GPUTask olc::Draw::TaskTexturedMesh(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const std::vector& vTexCoords, olc::Image* const image, const olc::Pixel tint) +{ + GPUTask task; + task.structure = structure; + task.tint = tint; + task.vertexBuffer.resize(vPoints.size()); + task.pImage = image; + task.bIs3D = true; + task.bDepth = bDepth; + task.cullmode = cullMode; + task.mvpMatrix = matMVP.m; + for (size_t i = 0; i < vPoints.size(); i++) + task.vertexBuffer[i] = { {vPoints[i].x, vPoints[i].y, vPoints[i].z, vPoints[i].w}, vColours[i], {vTexCoords[i].x, vTexCoords[i].y}, {0, 0}, {0, 0}, {0, 0} }; + return task; +} + + + +const GPUTask& Draw::Line(const olc::vf2d& p1, const olc::vf2d& p2, const olc::Pixel col, const olc::Pixel tint) { PrepareTargetForHW(); @@ -356,12 +402,12 @@ const GPUTask& Draw2D::Line(const olc::vf2d& p1, const olc::vf2d& p2, const olc: ))); } -const LineBatch& olc::Draw2D::Line(olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::Pixel col) +const LineBatch& olc::Draw::Line(olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::Pixel col) { return Line(batch, p1, col, p2, col); } -const GPUTask& Draw2D::Line(const olc::vf2d& p1, const olc::Pixel c1, const olc::vf2d& p2, const olc::Pixel c2, const olc::Pixel tint) +const GPUTask& Draw::Line(const olc::vf2d& p1, const olc::Pixel c1, const olc::vf2d& p2, const olc::Pixel c2, const olc::Pixel tint) { PrepareTargetForHW(); @@ -374,7 +420,7 @@ const GPUTask& Draw2D::Line(const olc::vf2d& p1, const olc::Pixel c1, const olc: ))); } -const LineBatch& olc::Draw2D::Line(olc::LineBatch& batch, const olc::vf2d& p1, const olc::Pixel c1, const olc::vf2d& p2, const olc::Pixel c2) +const LineBatch& olc::Draw::Line(olc::LineBatch& batch, const olc::vf2d& p1, const olc::Pixel c1, const olc::vf2d& p2, const olc::Pixel c2) { batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + 2); size_t idx = batch.task.vertexBuffer.size() - 2; @@ -385,7 +431,7 @@ const LineBatch& olc::Draw2D::Line(olc::LineBatch& batch, const olc::vf2d& p1, c return batch; } -const GPUTask& olc::Draw2D::Rect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col, const olc::Pixel tint) +const GPUTask& olc::Draw::Rect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col, const olc::Pixel tint) { // Right a big, hearty, F&^% you to OpenGL's Diamond Exit Strategy. It makes line drawing // with OpenGL a smidge unreliable @@ -406,12 +452,12 @@ const GPUTask& olc::Draw2D::Rect(const olc::vf2d& pos, const olc::vf2d& size, co ))); } -const LineBatch& olc::Draw2D::Rect(olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) +const LineBatch& olc::Draw::Rect(olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) { return Rect(batch, pos, size, col, col, col, col); } -const GPUTask& olc::Draw2D::Rect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR, const olc::Pixel tint) +const GPUTask& olc::Draw::Rect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR, const olc::Pixel tint) { PrepareTargetForHW(); @@ -429,7 +475,7 @@ const GPUTask& olc::Draw2D::Rect(const olc::vf2d& pos, const olc::vf2d& size, co ))); } -const LineBatch& olc::Draw2D::Rect(olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR) +const LineBatch& olc::Draw::Rect(olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR) { const olc::vf2d pTL = olc::vf2d(pos.x, pos.y); const olc::vf2d pTR = olc::vf2d(pos.x + size.x, pos.y); @@ -446,7 +492,7 @@ const LineBatch& olc::Draw2D::Rect(olc::LineBatch& batch, const olc::vf2d& pos, } -const GPUTask& olc::Draw2D::FilledRect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col, const olc::Pixel tint) +const GPUTask& olc::Draw::FilledRect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col, const olc::Pixel tint) { PrepareTargetForHW(); @@ -463,14 +509,14 @@ const GPUTask& olc::Draw2D::FilledRect(const olc::vf2d& pos, const olc::vf2d& si ))); } -const FilledBatch& olc::Draw2D::FilledRect(olc::FilledBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) +const FilledBatch& olc::Draw::FilledRect(olc::FilledBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) { FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y), olc::vf2d(pos.x + size.x, pos.y + size.y), col, col, col); FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y + size.y), olc::vf2d(pos.x, pos.y + size.y), col, col, col); return batch; } -const GPUTask& olc::Draw2D::FilledRect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR, const olc::Pixel tint) +const GPUTask& olc::Draw::FilledRect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR, const olc::Pixel tint) { PrepareTargetForHW(); return vecGPUTasks.data.emplace_back(std::move( @@ -486,14 +532,14 @@ const GPUTask& olc::Draw2D::FilledRect(const olc::vf2d& pos, const olc::vf2d& si ))); } -const FilledBatch& olc::Draw2D::FilledRect(olc::FilledBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR) +const FilledBatch& olc::Draw::FilledRect(olc::FilledBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR) { FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y), olc::vf2d(pos.x + size.x, pos.y + size.y), colTL, colTR, colBR); FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y + size.y), olc::vf2d(pos.x, pos.y + size.y), colTL, colBR, colBL); return batch; } -void olc::Draw2D::RedefineUnitCircleBuffer(const int32_t nFacets) +void olc::Draw::RedefineUnitCircleBuffer(const int32_t nFacets) { buffUnitCirclePoints.reserve(nFacets + 1); for (int32_t i = 0; i <= nFacets; i++) @@ -503,37 +549,37 @@ void olc::Draw2D::RedefineUnitCircleBuffer(const int32_t nFacets) } } -const GPUTask& olc::Draw2D::Circle(const olc::vf2d& pos, const float& radius, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) +const GPUTask& olc::Draw::Circle(const olc::vf2d& pos, const float& radius, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { return Ellipse(pos, radius, radius, col, tint, nFacets); } -const LineBatch& olc::Draw2D::Circle(olc::LineBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel col, int32_t nFacets) +const LineBatch& olc::Draw::Circle(olc::LineBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel col, int32_t nFacets) { return Ellipse(batch, pos, radius, radius, col, nFacets); } -const GPUTask& olc::Draw2D::FilledCircle(const olc::vf2d& pos, const float& radius, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) +const GPUTask& olc::Draw::FilledCircle(const olc::vf2d& pos, const float& radius, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { return FilledEllipse(pos, radius, radius, col, tint, nFacets); } -const FilledBatch& olc::Draw2D::FilledCircle(olc::FilledBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel col, int32_t nFacets) +const FilledBatch& olc::Draw::FilledCircle(olc::FilledBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel col, int32_t nFacets) { return FilledEllipse(batch, pos, radius, radius, col, nFacets); } -const GPUTask& olc::Draw2D::FilledCircle(const olc::vf2d& pos, const float& radius, const olc::Pixel colInner, const olc::Pixel colOuter, const olc::Pixel tint, int32_t nFacets) +const GPUTask& olc::Draw::FilledCircle(const olc::vf2d& pos, const float& radius, const olc::Pixel colInner, const olc::Pixel colOuter, const olc::Pixel tint, int32_t nFacets) { return FilledEllipse(pos, radius, radius, colInner, colOuter, tint, nFacets); } -const FilledBatch& olc::Draw2D::FilledCircle(olc::FilledBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel colInner, const olc::Pixel colOuter, int32_t nFacets) +const FilledBatch& olc::Draw::FilledCircle(olc::FilledBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel colInner, const olc::Pixel colOuter, int32_t nFacets) { return FilledEllipse(batch, pos, radius, radius, colInner, colOuter, nFacets); } -const GPUTask& olc::Draw2D::Ellipse(const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) +const GPUTask& olc::Draw::Ellipse(const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { // Fundamental for outline circle/ellipse with colour solid/gradient PrepareTargetForHW(); @@ -559,7 +605,7 @@ const GPUTask& olc::Draw2D::Ellipse(const olc::vf2d& pos, const float& rx, const ))); } -const LineBatch& olc::Draw2D::Ellipse(olc::LineBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, int32_t nFacets) +const LineBatch& olc::Draw::Ellipse(olc::LineBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, int32_t nFacets) { // Fundamental for batched outline circle/ellipse with colour solid/gradient @@ -578,17 +624,17 @@ const LineBatch& olc::Draw2D::Ellipse(olc::LineBatch& batch, const olc::vf2d& po return batch; } -const GPUTask& olc::Draw2D::FilledEllipse(const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) +const GPUTask& olc::Draw::FilledEllipse(const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { return FilledEllipse(pos, rx, ry, col, col, tint, nFacets); } -const FilledBatch& olc::Draw2D::FilledEllipse(olc::FilledBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, int32_t nFacets) +const FilledBatch& olc::Draw::FilledEllipse(olc::FilledBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, int32_t nFacets) { return FilledEllipse(batch, pos, rx, ry, col, col, nFacets); } -const GPUTask& olc::Draw2D::FilledEllipse(const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel colInner, const olc::Pixel colOuter, const olc::Pixel tint, int32_t nFacets) +const GPUTask& olc::Draw::FilledEllipse(const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel colInner, const olc::Pixel colOuter, const olc::Pixel tint, int32_t nFacets) { // Fundamental for filled circle/ellipse with colour solid/gradient @@ -617,7 +663,7 @@ const GPUTask& olc::Draw2D::FilledEllipse(const olc::vf2d& pos, const float& rx, ))); } -const FilledBatch& olc::Draw2D::FilledEllipse(olc::FilledBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel colInner, const olc::Pixel colOuter, int32_t nFacets) +const FilledBatch& olc::Draw::FilledEllipse(olc::FilledBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel colInner, const olc::Pixel colOuter, int32_t nFacets) { // Fundamental for batch filled circle/ellipse with colour solid/gradient @@ -638,7 +684,7 @@ const FilledBatch& olc::Draw2D::FilledEllipse(olc::FilledBatch& batch, const olc return batch; } -const GPUTask& olc::Draw2D::RoundedRect(const olc::vf2d& pos, const olc::vf2d& size, const float& radius, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) +const GPUTask& olc::Draw::RoundedRect(const olc::vf2d& pos, const olc::vf2d& size, const float& radius, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { PrepareTargetForHW(); @@ -688,7 +734,7 @@ const GPUTask& olc::Draw2D::RoundedRect(const olc::vf2d& pos, const olc::vf2d& s ))); } -const LineBatch& olc::Draw2D::RoundedRect(olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const float& radius, const olc::Pixel col, int32_t nFacets) +const LineBatch& olc::Draw::RoundedRect(olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const float& radius, const olc::Pixel col, int32_t nFacets) { buffPoints.reserve((nFacets + 1) * 4 + 1); buffPoints.data.clear(); @@ -734,7 +780,7 @@ const LineBatch& olc::Draw2D::RoundedRect(olc::LineBatch& batch, const olc::vf2d return batch; } -const GPUTask& olc::Draw2D::FilledRoundedRect(const olc::vf2d& pos, const olc::vf2d& size, const float& radius, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) +const GPUTask& olc::Draw::FilledRoundedRect(const olc::vf2d& pos, const olc::vf2d& size, const float& radius, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { PrepareTargetForHW(); @@ -786,17 +832,17 @@ const GPUTask& olc::Draw2D::FilledRoundedRect(const olc::vf2d& pos, const olc::v -const GPUTask& olc::Draw2D::Triangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col, const olc::Pixel tint) +const GPUTask& olc::Draw::Triangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col, const olc::Pixel tint) { return Triangle(p1, p2, p3, col, col, col, tint); } -const LineBatch& olc::Draw2D::Triangle(olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col) +const LineBatch& olc::Draw::Triangle(olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col) { return Triangle(batch, p1, p2, p3, col, col, col); } -const GPUTask& olc::Draw2D::Triangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3, const olc::Pixel tint) +const GPUTask& olc::Draw::Triangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3, const olc::Pixel tint) { PrepareTargetForHW(); @@ -809,7 +855,7 @@ const GPUTask& olc::Draw2D::Triangle(const olc::vf2d& p1, const olc::vf2d& p2, c ))); } -const LineBatch& olc::Draw2D::Triangle(olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3) +const LineBatch& olc::Draw::Triangle(olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3) { Line(batch, p1, c1, p2, c2); Line(batch, p2, c2, p3, c3); @@ -817,17 +863,17 @@ const LineBatch& olc::Draw2D::Triangle(olc::LineBatch& batch, const olc::vf2d& p return batch; } -const GPUTask& olc::Draw2D::FilledTriangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col, const olc::Pixel tint) +const GPUTask& olc::Draw::FilledTriangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col, const olc::Pixel tint) { return FilledTriangle(p1, p2, p3, col, col, col, tint); } -const FilledBatch& olc::Draw2D::FilledTriangle(olc::FilledBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col) +const FilledBatch& olc::Draw::FilledTriangle(olc::FilledBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col) { return FilledTriangle(batch, p1, p2, p3, col, col, col); } -const GPUTask& olc::Draw2D::FilledTriangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3, const olc::Pixel tint) +const GPUTask& olc::Draw::FilledTriangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3, const olc::Pixel tint) { PrepareTargetForHW(); @@ -840,7 +886,7 @@ const GPUTask& olc::Draw2D::FilledTriangle(const olc::vf2d& p1, const olc::vf2d& ))); } -const FilledBatch& olc::Draw2D::FilledTriangle(olc::FilledBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3) +const FilledBatch& olc::Draw::FilledTriangle(olc::FilledBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3) { batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + 3); size_t idx = batch.task.vertexBuffer.size() - 3; @@ -853,7 +899,7 @@ const FilledBatch& olc::Draw2D::FilledTriangle(olc::FilledBatch& batch, const ol return batch; } -const GPUTask& olc::Draw2D::TexturedTriangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3, const olc::vf2d& t1, const olc::vf2d& t2, const olc::vf2d& t3, olc::Image& texture, const olc::Pixel tint) +const GPUTask& olc::Draw::TexturedTriangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3, const olc::vf2d& t1, const olc::vf2d& t2, const olc::vf2d& t3, olc::Image& texture, const olc::Pixel tint) { PrepareTargetForHW(); PrepareImageForHW(texture); @@ -869,12 +915,12 @@ const GPUTask& olc::Draw2D::TexturedTriangle(const olc::vf2d& p1, const olc::vf2 ))); } -const GPUTask& olc::Draw2D::Polygon(const std::vector& vecPoints, const olc::Pixel col, const olc::Pixel tint) +const GPUTask& olc::Draw::Polygon(const std::vector& vecPoints, const olc::Pixel col, const olc::Pixel tint) { return Polygon(olc::Structure::LineLoop, vecPoints, std::vector(vecPoints.size(), col), tint); } -const LineBatch& olc::Draw2D::Polygon(olc::LineBatch& batch, const std::vector& vecPoints, const olc::Pixel col) +const LineBatch& olc::Draw::Polygon(olc::LineBatch& batch, const std::vector& vecPoints, const olc::Pixel col) { for (size_t i = 0; i < vecPoints.size(); i++) { @@ -884,12 +930,12 @@ const LineBatch& olc::Draw2D::Polygon(olc::LineBatch& batch, const std::vector& vecPoints, const std::vector& vecColours, const olc::Pixel tint) +const GPUTask& olc::Draw::Polygon(const std::vector& vecPoints, const std::vector& vecColours, const olc::Pixel tint) { return Polygon(olc::Structure::LineLoop, vecPoints, vecColours, tint); } -const LineBatch& olc::Draw2D::Polygon(olc::LineBatch& batch, const std::vector& vecPoints, const std::vector& vecColours) +const LineBatch& olc::Draw::Polygon(olc::LineBatch& batch, const std::vector& vecPoints, const std::vector& vecColours) { for(size_t i = 0; i& vecPoints, const olc::Pixel col, const olc::Pixel tint) +const GPUTask& olc::Draw::Polygon(const olc::Structure structure, const std::vector& vecPoints, const olc::Pixel col, const olc::Pixel tint) { return Polygon(structure, vecPoints, std::vector(vecPoints.size(), col), tint); } -const GPUTask& olc::Draw2D::Polygon(const olc::Structure structure, const std::vector& vecPoints, const std::vector& vecColours, const olc::Pixel tint) +const GPUTask& olc::Draw::Polygon(const olc::Structure structure, const std::vector& vecPoints, const std::vector& vecColours, const olc::Pixel tint) { PrepareTargetForHW(); @@ -917,12 +963,12 @@ const GPUTask& olc::Draw2D::Polygon(const olc::Structure structure, const std::v ))); } -const GPUTask& olc::Draw2D::FilledPolygon(const olc::Structure structure, const std::vector& vecPoints, const olc::Pixel col, const olc::Pixel tint) +const GPUTask& olc::Draw::FilledPolygon(const olc::Structure structure, const std::vector& vecPoints, const olc::Pixel col, const olc::Pixel tint) { return FilledPolygon(structure, vecPoints, std::vector(vecPoints.size(), col), tint); } -const GPUTask& olc::Draw2D::FilledPolygon(const olc::Structure structure, const std::vector& vecPoints, const std::vector& vecColours, const olc::Pixel tint) +const GPUTask& olc::Draw::FilledPolygon(const olc::Structure structure, const std::vector& vecPoints, const std::vector& vecColours, const olc::Pixel tint) { PrepareTargetForHW(); @@ -935,7 +981,7 @@ const GPUTask& olc::Draw2D::FilledPolygon(const olc::Structure structure, const ))); } -const GPUTask& olc::Draw2D::TexturedPolygon(const olc::Structure structure, const std::vector& vecPoints, const std::vector& vecColours, const std::vector& vecTexCoords, olc::Image& texture, const olc::Pixel tint) +const GPUTask& olc::Draw::TexturedPolygon(const olc::Structure structure, const std::vector& vecPoints, const std::vector& vecColours, const std::vector& vecTexCoords, olc::Image& texture, const olc::Pixel tint) { PrepareTargetForHW(); PrepareImageForHW(texture); @@ -951,7 +997,7 @@ const GPUTask& olc::Draw2D::TexturedPolygon(const olc::Structure structure, cons ))); } -const GPUTask& olc::Draw2D::String(const olc::vf2d& pos, const std::string& text, const olc::Pixel col, const olc::vf2d& scale, olc::Font& font) +const GPUTask& olc::Draw::String(const olc::vf2d& pos, const std::string& text, const olc::Pixel col, const olc::vf2d& scale, olc::Font& font) { PrepareTargetForHW(); @@ -974,7 +1020,7 @@ const GPUTask& olc::Draw2D::String(const olc::vf2d& pos, const std::string& text } else { - Draw2D::Image(task, font.glyphs[c].imgGlyph, pos + spos + olc::vf2d{ glyph.spacing * scale.x, 0.0f }, scale, col); + Draw::Image(task, font.glyphs[c].imgGlyph, pos + spos + olc::vf2d{ glyph.spacing * scale.x, 0.0f }, scale, col); spos.x += glyph.vMonoSize.x * scale.x; } } @@ -982,7 +1028,7 @@ const GPUTask& olc::Draw2D::String(const olc::vf2d& pos, const std::string& text return Batch(task); } -const GPUTask& olc::Draw2D::StringProp(const olc::vf2d& pos, const std::string& text, const olc::Pixel col, const olc::vf2d& scale, olc::Font& font) +const GPUTask& olc::Draw::StringProp(const olc::vf2d& pos, const std::string& text, const olc::Pixel col, const olc::vf2d& scale, olc::Font& font) { PrepareTargetForHW(); @@ -1004,7 +1050,7 @@ const GPUTask& olc::Draw2D::StringProp(const olc::vf2d& pos, const std::string& } else { - Draw2D::Image(task, font.glyphs[c].imgGlyph, pos + spos, scale, col); + Draw::Image(task, font.glyphs[c].imgGlyph, pos + spos, scale, col); spos.x += glyph.vPropSize.x * scale.x; } } @@ -1012,7 +1058,7 @@ const GPUTask& olc::Draw2D::StringProp(const olc::vf2d& pos, const std::string& return Batch(task); } -olc::vf2d olc::Draw2D::GetTextSize(const std::string& text, const bool bProportional, const olc::vf2d& scale, olc::Font& font) +olc::vf2d olc::Draw::GetTextSize(const std::string& text, const bool bProportional, const olc::vf2d& scale, olc::Font& font) { olc::vf2d size = { 0, font.fLineHeight * scale.y }; olc::vf2d pos = { 0, font.fLineHeight * scale.y }; @@ -1045,7 +1091,49 @@ olc::vf2d olc::Draw2D::GetTextSize(const std::string& text, const bool bProporti return size; } -ImageBatch olc::Draw2D::CreateImageBatch(olc::Image &image) +GPUTask& olc::Draw::Line(const olc::vf4d& vStart, const olc::vf4d& vEnd, const olc::Pixel& col, const olc::Pixel tint) +{ + PrepareTargetForHW(); + + return vecGPUTasks.data.emplace_back(std::move( + TaskWireMesh( + olc::Structure::Line, + { vStart, vEnd }, + { col, col }, + tint + ))); +} + +GPUTask& olc::Draw::Mesh(const olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) +{ + PrepareTargetForHW(); + + return vecGPUTasks.data.emplace_back(std::move( + TaskWireMesh( + structure, + vPoints, + vColours, + tint + ))); +} + +GPUTask& olc::Draw::Mesh(const olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const std::vector& vUVs, olc::Image& texture, const olc::Pixel tint) +{ + PrepareImageForHW(texture); + PrepareTargetForHW(); + + return vecGPUTasks.data.emplace_back(std::move( + TaskTexturedMesh( + structure, + vPoints, + vColours, + vUVs, + &texture, + tint + ))); +} + +ImageBatch olc::Draw::CreateImageBatch(olc::Image &image) { PrepareImageForHW(image); PrepareTargetForHW(); @@ -1056,39 +1144,39 @@ ImageBatch olc::Draw2D::CreateImageBatch(olc::Image &image) return b; } -const GPUTask& olc::Draw2D::Batch(olc::ImageBatch& batch, const olc::Pixel tint) +const GPUTask& olc::Draw::Batch(olc::ImageBatch& batch, const olc::Pixel tint) { batch.task.tint = tint; return vecGPUTasks.data.emplace_back(batch.task); } -FilledBatch olc::Draw2D::CreateFilledBatch() +FilledBatch olc::Draw::CreateFilledBatch() { FilledBatch b; b.task.structure = olc::Structure::List; return b; } -const GPUTask& olc::Draw2D::Batch(olc::FilledBatch& batch, const olc::Pixel tint) +const GPUTask& olc::Draw::Batch(olc::FilledBatch& batch, const olc::Pixel tint) { batch.task.tint = tint; return vecGPUTasks.data.emplace_back(batch.task); } -LineBatch olc::Draw2D::CreateLineBatch() +LineBatch olc::Draw::CreateLineBatch() { LineBatch b; b.task.structure = olc::Structure::LineList; return b; } -const GPUTask& olc::Draw2D::Batch(olc::LineBatch& batch, const olc::Pixel tint) +const GPUTask& olc::Draw::Batch(olc::LineBatch& batch, const olc::Pixel tint) { batch.task.tint = tint; return vecGPUTasks.data.emplace_back(batch.task); } -const ImageBatch& olc::Draw2D::Image(ImageBatch& batch, olc::ImageRegion image, const olc::vf2d& pos, const olc::vf2d& scale, const olc::Pixel tint) +const ImageBatch& olc::Draw::Image(ImageBatch& batch, olc::ImageRegion image, const olc::vf2d& pos, const olc::vf2d& scale, const olc::Pixel tint) { // Add quad to existing task olc::vf2d size = image.regionsize * scale; @@ -1108,7 +1196,7 @@ const ImageBatch& olc::Draw2D::Image(ImageBatch& batch, olc::ImageRegion image, return batch; } -const GPUTask& olc::Draw2D::Image(olc::ImageRegion image, const olc::vf2d& pos, const olc::vf2d& scale, const olc::Pixel tint) +const GPUTask& olc::Draw::Image(olc::ImageRegion image, const olc::vf2d& pos, const olc::vf2d& scale, const olc::Pixel tint) { // Ensure source image is up to date in VRAM PrepareImageForHW(image.image); @@ -1139,7 +1227,7 @@ const GPUTask& olc::Draw2D::Image(olc::ImageRegion image, const olc::vf2d& pos, } -const GPUTask& olc::Draw2D::ImageRotated(olc::ImageRegion image, const olc::vf2d& pos, const float theta, const olc::vf2d& center, const olc::vf2d& scale, const olc::Pixel tint) +const GPUTask& olc::Draw::ImageRotated(olc::ImageRegion image, const olc::vf2d& pos, const float theta, const olc::vf2d& center, const olc::vf2d& scale, const olc::Pixel tint) { // Ensure source image is up to date in VRAM PrepareImageForHW(image.image); @@ -1167,7 +1255,7 @@ const GPUTask& olc::Draw2D::ImageRotated(olc::ImageRegion image, const olc::vf2d ))); } -const ImageBatch& olc::Draw2D::ImageRotated(olc::ImageBatch& batch, olc::ImageRegion image, const olc::vf2d& pos, const float theta, const olc::vf2d& center, const olc::vf2d& scale, const olc::Pixel tint) +const ImageBatch& olc::Draw::ImageRotated(olc::ImageBatch& batch, olc::ImageRegion image, const olc::vf2d& pos, const float theta, const olc::vf2d& center, const olc::vf2d& scale, const olc::Pixel tint) { // Add quad to existing task olc::vf2d size = image.regionsize * scale; @@ -1197,7 +1285,7 @@ const ImageBatch& olc::Draw2D::ImageRotated(olc::ImageBatch& batch, olc::ImageRe return batch; } -const GPUTask& olc::Draw2D::ImageQuad(olc::ImageRegion image, const olc::vf2d& vTL, const olc::vf2d& vTR, const olc::vf2d& vBR, const olc::vf2d& vBL, const olc::Pixel tint) +const GPUTask& olc::Draw::ImageQuad(olc::ImageRegion image, const olc::vf2d& vTL, const olc::vf2d& vTR, const olc::vf2d& vBR, const olc::vf2d& vBL, const olc::Pixel tint) { // Ensure source image is up to date in VRAM PrepareImageForHW(image.image); @@ -1252,7 +1340,7 @@ const GPUTask& olc::Draw2D::ImageQuad(olc::ImageRegion image, const olc::vf2d& v ))); } -const ImageBatch& olc::Draw2D::ImageQuad(olc::ImageBatch& batch, olc::ImageRegion image, const olc::vf2d& vTL, const olc::vf2d& vTR, const olc::vf2d& vBR, const olc::vf2d& vBL, const olc::Pixel tint) +const ImageBatch& olc::Draw::ImageQuad(olc::ImageBatch& batch, olc::ImageRegion image, const olc::vf2d& vTL, const olc::vf2d& vTR, const olc::vf2d& vBR, const olc::vf2d& vBL, const olc::Pixel tint) { float rd = ((vBR.x - vTL.x) * (vTR.y - vBL.y) - (vTR.x - vBL.x) * (vBR.y - vTL.y)); if (rd != 0) @@ -1295,21 +1383,21 @@ const ImageBatch& olc::Draw2D::ImageQuad(olc::ImageBatch& batch, olc::ImageRegio } // Default is just return a textured quad - return Draw2D::Image(batch, image, vTL, vBR - vTL, tint); + return Draw::Image(batch, image, vTL, vBR - vTL, tint); } -const GPUTask& olc::Draw2D::ImageQuad(olc::ImageRegion image, const std::vector& vecPoints, const olc::Pixel tint) +const GPUTask& olc::Draw::ImageQuad(olc::ImageRegion image, const std::vector& vecPoints, const olc::Pixel tint) { return ImageQuad(image, vecPoints[0], vecPoints[1], vecPoints[2], vecPoints[3], tint); } -const ImageBatch& olc::Draw2D::ImageQuad(olc::ImageBatch& batch, olc::ImageRegion image, const std::vector& vecPoints, const olc::Pixel tint) +const ImageBatch& olc::Draw::ImageQuad(olc::ImageBatch& batch, olc::ImageRegion image, const std::vector& vecPoints, const olc::Pixel tint) { return ImageQuad(batch, image, vecPoints[0], vecPoints[1], vecPoints[2], vecPoints[3], tint); } -const GPUTask& olc::Draw2D::ImageRect(olc::ImageRegion image, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel tint) +const GPUTask& olc::Draw::ImageRect(olc::ImageRegion image, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel tint) { // Ensure source image is up to date in VRAM PrepareImageForHW(image.image); @@ -1327,11 +1415,79 @@ const GPUTask& olc::Draw2D::ImageRect(olc::ImageRegion image, const olc::vf2d& p ))); } -const ImageBatch& olc::Draw2D::ImageRect(olc::ImageBatch& batch, olc::ImageRegion image, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel tint) +const ImageBatch& olc::Draw::ImageRect(olc::ImageBatch& batch, olc::ImageRegion image, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel tint) { olc_IgnoreUnused(image, pos, size, tint); // TODO: Implement this function return batch; } +void olc::Draw::SetCullMode(const olc::GPUTask::CullMode mode) +{ + cullMode = mode; +} + +void olc::Draw::EnableDepth(const bool bEnable) +{ + bDepth = bEnable; +} + +void olc::Draw::SetViewport(const olc::vi2d& pos, const olc::vi2d& size) +{ + pRenderer->SetViewport(pos, size); +} + +void olc::Draw::MatrixReset() +{ + matMVP.identity(); + matModel.identity(); + matView.identity(); + matProjection.identity(); +} + +void olc::Draw::SetModelMatrix(const olc::mf4d& mat) +{ + matModel = mat; + matMVP = matVP * matModel; +} + +const olc::mf4d& olc::Draw::GetModelMatrix() const +{ + return matModel; +} + +void olc::Draw::SetViewMatrix(const olc::mf4d& mat) +{ + matView = mat; + matVP = matProjection * matView; + matMVP = matVP * matModel; +} + +const olc::mf4d& olc::Draw::GetViewMatrix() const +{ + return matView; +} + +void olc::Draw::SetProjectionMatrix(const olc::mf4d& mat) +{ + matProjection = mat; + matVP = matProjection * matView; + matMVP = matVP * matModel; +} + +const olc::mf4d& olc::Draw::GetProjectionMatrix() const +{ + return matProjection; +} + +void olc::Draw::SetMVPMatrix(const olc::mf4d& mat) +{ + matMVP = mat; +} + +const olc::mf4d& olc::Draw::GetMVPMatrix() const +{ + return matMVP; +} + //! END IMPLEMENTATION \ No newline at end of file diff --git a/dev/src/draw2d.h b/dev/src/draw.h similarity index 86% rename from dev/src/draw2d.h rename to dev/src/draw.h index 92c4184c..e7c748f3 100644 --- a/dev/src/draw2d.h +++ b/dev/src/draw.h @@ -13,6 +13,8 @@ #include "config.h" #include "vector2d.h" #include "transform2d.h" +#include "vector4d.h" +#include "matrix4d.h" #include "pixel.h" #include "image.h" #include "gputask.h" @@ -148,6 +150,12 @@ [#] Batch(FilledBatch, [tint]) [#] Batch(ImageBatch, [tint]) + + 3D Rendering Functions + ~~~~~~~~~~~~~~~~~~~~~~ + + + */ @@ -172,12 +180,12 @@ namespace olc struct FilledBatch { GPUTask task; }; struct LineBatch { GPUTask task; }; - class Draw2D + class Draw { friend class olc::Draw3D; public: - Draw2D(); + Draw(); // Associate this drawing toolbox with a renderer void SetGPU(olc::gpu::Renderer* const renderer); @@ -734,6 +742,52 @@ namespace olc const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel tint = olc::Colour::WHITE); + + public: // Applied Rendering Modes + void SetCullMode(const olc::GPUTask::CullMode mode); + void EnableDepth(const bool bEnable); + void SetViewport(const olc::vi2d& pos, const olc::vi2d& size); + + public: // 3D Transformation Functions + void MatrixReset(); + void SetModelMatrix(const olc::mf4d& mat); + const olc::mf4d& GetModelMatrix() const; + void SetViewMatrix(const olc::mf4d& mat); + const olc::mf4d& GetViewMatrix() const; + void SetProjectionMatrix(const olc::mf4d& mat); + const olc::mf4d& GetProjectionMatrix() const; + void SetMVPMatrix(const olc::mf4d& mat); + const olc::mf4d& GetMVPMatrix() const; + + + + public: // 3D Primitive Drawing Functions + + GPUTask& Line( + const olc::vf4d& vStart, + const olc::vf4d& vEnd, + const olc::Pixel& col = olc::Colour::WHITE, + const olc::Pixel tint = olc::Colour::WHITE); + + GPUTask& Mesh( + const olc::Structure structure, + const std::vector& vPoints, + const std::vector& vColours, + const olc::Pixel tint = olc::Colour::WHITE); + + GPUTask& Mesh( + const olc::Structure structure, + const std::vector& vPoints, + const std::vector& vColours, + const std::vector& vUVs, + olc::Image& texture, + const olc::Pixel tint = olc::Colour::WHITE); + + + + public: // GPU Task Creator Functions (not normally called by user) + + public: @@ -806,153 +860,26 @@ namespace olc olc::Image* const image, const olc::Pixel tint = olc::Colour::WHITE); + // 3D Task Generators + GPUTask TaskWireMesh( + olc::Structure structure, + const std::vector& vPoints, + const std::vector& vColours, + const olc::Pixel tint = olc::Colour::WHITE); - public: // Precision drawing functions via software rasteriser - // Draws a single pixel wide line of fixed colour - void swLine( - const olc::vf2d& p1, - const olc::vf2d& p2, - const olc::Pixel col = olc::Colour::WHITE); - - // Draws a single pixel wide line with a gradient - void swLine( - const olc::vf2d& p1, - const olc::vf2d& p2, - const olc::Pixel c1, - const olc::Pixel c2); - - // Draws a rectangle outline - void swRect( - const olc::vf2d& pos, - const olc::vf2d& size, - const olc::Pixel col = olc::Colour::WHITE); - - // Draws a multiple colour rectangle, with linear colour interpolation - void swRect( - const olc::vf2d& pos, - const olc::vf2d& size, - const olc::Pixel colTL, - const olc::Pixel colTR, - const olc::Pixel colBL, - const olc::Pixel colBR); - - // Draws a filled, single colour rectangle - void swFilledRect( - const olc::vf2d& pos, - const olc::vf2d& size, - const olc::Pixel col = olc::Colour::WHITE); - - // Draws a filled, multiple colour rectangle, with linear colour interpolation - void swFilledRect( - const olc::vf2d& pos, - const olc::vf2d& size, - const olc::Pixel colTL, - const olc::Pixel colTR, - const olc::Pixel colBL, - const olc::Pixel colBR); - - // Draws a triangle outline - void swTriangle( - const olc::vf2d& p1, - const olc::vf2d& p2, - const olc::vf2d& p3, - const olc::Pixel col = olc::Colour::WHITE); - - // Draws a multiple colour triangle, with linear colour interpolation - void swTriangle( - const olc::vf2d& p1, - const olc::vf2d& p2, - const olc::vf2d& p3, - const olc::Pixel c1, - const olc::Pixel c2, - const olc::Pixel c3); - - // Draws a filled, single colour triangle - void swFilledTriangle( - const olc::vf2d& p1, - const olc::vf2d& p2, - const olc::vf2d& p3, - const olc::Pixel col = olc::Colour::WHITE); - - // Draws a filled, multiple colour triangle, with linear colour interpolation - void swFilledTriangle( - const olc::vf2d& p1, - const olc::vf2d& p2, - const olc::vf2d& p3, - const olc::Pixel c1, - const olc::Pixel c2, - const olc::Pixel c3); - - // Rasterises a textured triangle in integer space - void swTexturedTriangle( - const olc::vf2d& p1, - const olc::vf2d& p2, - const olc::vf2d& p3, - const olc::Pixel c1, - const olc::Pixel c2, - const olc::Pixel c3, - const olc::vf2d& t1, - const olc::vf2d& t2, - const olc::vf2d& t3, - olc::Image& texture); - - - - protected: // Software rasteriser helper functions - - // Clips a line to a rectangular region, returns true if line is visible - bool swClipLine( - olc::vf2d& v0, - olc::vf2d& v1, - const olc::vf2d& vMin, - const olc::vf2d& vMax); - - // Clips a line to a rectangular region, returns true if line is visible. - // The returned weights correspond to distance along the line from v0 to v1 - bool swClipWeightedLine( - olc::vf2d& v0, - olc::vf2d& v1, - const olc::vf2d& vMin, - const olc::vf2d& vMax, - float& w0, - float& w1); - - /* bool swClipTriangle( - olc::vf2d& v1, - olc::vf2d& v2, - olc::vf2d& v3, - const olc::vf2d& vMin, - const olc::vf2d& vMax);*/ - - // Rasterises a shaded line in integer space - void swRasterShadedLine( - const olc::vi2d& v1, - const olc::vi2d& v2, - const olc::Pixel c1, - const olc::Pixel c2); - - // Rasterises a shaded triangle in integer space - void swRasterShadedTriangle( - const olc::vi2d& v1, - const olc::vi2d& v2, - const olc::vi2d& v3, - const olc::Pixel c1, - const olc::Pixel c2, - const olc::Pixel c3); - - // Rasterises a textured triangle in integer space - void swRasterTexturedTriangle( - const olc::vi2d& v1, - const olc::vi2d& v2, - const olc::vi2d& v3, - const olc::Pixel c1, - const olc::Pixel c2, - const olc::Pixel c3, - const olc::vf2d& t1, - const olc::vf2d& t2, - const olc::vf2d& t3, - olc::Image& texture); + GPUTask TaskFillMesh( + olc::Structure structure, + const std::vector& vPoints, + const std::vector& vColours, + const olc::Pixel tint = olc::Colour::WHITE); + GPUTask TaskTexturedMesh( + olc::Structure structure, + const std::vector& vPoints, + const std::vector& vColours, + const std::vector& vTexCoords, + olc::Image* const image, + const olc::Pixel tint = olc::Colour::WHITE); public: @@ -1002,26 +929,18 @@ namespace olc olc::gpu::Renderer* pRenderer = nullptr; olc::tf2d transformAffine; - //std::vector vecGPUTasks; - - protected: // SW Rasteriser Helpers - struct Scanline - { - int32_t nMin = std::numeric_limits::max(); - int32_t nMax = std::numeric_limits::min(); - std::array fBaryMin; - std::array fBaryMax; - }; - - std::vector vScanlines; + // 3D Drawing Things + mf4d matModel; + mf4d matView; + mf4d matProjection; + mf4d matVP; + mf4d matMVP; + olc::vf2d vViewportPos = { 0, 0 }; + olc::vi2d vViewportSize = { 0, 0 }; - // Fills scanline buffer with visible triangle extents and barycentric coordinates. - // Returns vertical, visible extents of triangle scanlines - std::pair swBaryFillTriangle( - const olc::vi2d& v1, - const olc::vi2d& v2, - const olc::vi2d& v3); + olc::GPUTask::CullMode cullMode = olc::GPUTask::CullMode::None; + bool bDepth = true; private: diff --git a/dev/src/draw2d_sw.cpp b/dev/src/draw2d_sw.cpp deleted file mode 100644 index 6ecfab79..00000000 --- a/dev/src/draw2d_sw.cpp +++ /dev/null @@ -1,538 +0,0 @@ -#include "draw2d.h" - -#include "gpu_iface.h" - - - -//! START IMPLEMENTATION - -// This is all essentially the olc::PixelGameEngine 2 rasteriser code -using namespace olc; - - -void Draw2D::swLine(const olc::vf2d& p1, const olc::vf2d& p2, const olc::Pixel col) -{ - swLine(p1, p2, col, col); -} - -void Draw2D::swLine(const olc::vf2d& p1, const olc::vf2d& p2, const olc::Pixel c1, const olc::Pixel c2) -{ - const auto vTransformedPoints = transformAffine.forward({ p1, p2 }); - swRasterShadedLine( - vTransformedPoints[0], - vTransformedPoints[1], - c1, c2); -} - -void olc::Draw2D::swRect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) -{ - swRect(pos, size, col, col, col, col); -} - -void olc::Draw2D::swRect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR) -{ - swLine(pos, { pos.x + size.x, pos.y }, colTL, colTR); - swLine({ pos.x + size.x, pos.y }, { pos.x + size.x, pos.y + size.y }, colTR, colBR); - swLine({ pos.x + size.x, pos.y + size.y }, { pos.x, pos.y + size.y }, colBR, colBL); - swLine({ pos.x, pos.y + size.y }, pos, colBL, colTL); -} - -void olc::Draw2D::swFilledRect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) -{ - swFilledRect(pos, size, col, col, col, col); -} - -void olc::Draw2D::swFilledRect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR) -{ - const auto vTransformedPoints = transformAffine.forward({pos, {pos.x + size.x, pos.y}, pos + size, {pos.x, pos.y + size.y}}); - - // Most draws will be single colour, axis aligned rectangle. - // Optimise for that case first - - // Check if all one colour - if (colTL == colBL && colTL == colTR && colTL == colBR) - { - // Check if axis aligned - if(vTransformedPoints[0].y == vTransformedPoints[1].y && - vTransformedPoints[1].x == vTransformedPoints[2].x && - vTransformedPoints[2].y == vTransformedPoints[3].y && - vTransformedPoints[3].x == vTransformedPoints[0].x) - { - PrepareTargetForSW(); - - // Clip to target - olc::vi2d p1 = vTransformedPoints[0].max({ 0,0 }); - olc::vi2d p2 = vTransformedPoints[2].min(pTarget->Size()); - - // Draw filled rectangle - for (int32_t y = p1.y; y < p2.y; y++) - for (int32_t x = p1.x; x < p2.x; x++) - pTarget->Pixel({ x, y }) = colTL; - - // Exit early - return; - } - } - - // Fallback to general case rasteriser, where we split into two triangles - swRasterShadedTriangle( - vTransformedPoints[0], - vTransformedPoints[1], - vTransformedPoints[2], - colTL, colTR, colBR); - swRasterShadedTriangle( - vTransformedPoints[0], - vTransformedPoints[2], - vTransformedPoints[3], - colTL, colBR, colBL); -} - -void olc::Draw2D::swTriangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col) -{ - swTriangle(p1, p2, p3, col, col, col); -} - -void olc::Draw2D::swTriangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3) -{ - swLine(p1, p2, c1, c2); - swLine(p2, p3, c2, c3); - swLine(p3, p1, c3, c1); -} - -void olc::Draw2D::swFilledTriangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col) -{ - swFilledTriangle(p1, p2, p3, col, col, col); -} - -void olc::Draw2D::swFilledTriangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3) -{ - const auto vTransformedPoints = transformAffine.forward({ p1, p2, p3 }); - swRasterShadedTriangle( - vTransformedPoints[0], - vTransformedPoints[1], - vTransformedPoints[2], - c1, c2, c3); -} - -void olc::Draw2D::swTexturedTriangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3, const olc::vf2d& t1, const olc::vf2d& t2, const olc::vf2d& t3, olc::Image& texture) -{ - const auto vTransformedPoints = transformAffine.forward({ p1, p2, p3 }); - swRasterTexturedTriangle( - vTransformedPoints[0], - vTransformedPoints[1], - vTransformedPoints[2], - c1, c2, c3, - t1, t2, t3, - texture); -} - -bool olc::Draw2D::swClipLine(olc::vf2d& p1, olc::vf2d& p2, const olc::vf2d& vMin, const olc::vf2d& vMax) -{ - // https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm - static constexpr int SEG_I = 0b0000, SEG_L = 0b0001, SEG_R = 0b0010, SEG_B = 0b0100, SEG_T = 0b1000; - auto Segment = [&vMin = vMin, &vMax = vMax](const olc::vf2d& v) - { - int i = SEG_I; - if (v.x < vMin.x) i |= SEG_L; else if (v.x > vMax.x) i |= SEG_R; - if (v.y < vMin.y) i |= SEG_B; else if (v.y > vMax.y) i |= SEG_T; - return i; - }; - - int s1 = Segment(p1), s2 = Segment(p2); - - while (true) - { - if (!(s1 | s2)) return true; - else if (s1 & s2) return false; - else - { - int s3 = s2 > s1 ? s2 : s1; - olc::vf2d n; - if (s3 & SEG_T) { n.x = p1.x + (p2.x - p1.x) * (vMax.y - p1.y) / (p2.y - p1.y); n.y = vMax.y; } - else if (s3 & SEG_B) { n.x = p1.x + (p2.x - p1.x) * (vMin.y - p1.y) / (p2.y - p1.y); n.y = vMin.y; } - else if (s3 & SEG_R) { n.x = vMax.x; n.y = p1.y + (p2.y - p1.y) * (vMax.x - p1.x) / (p2.x - p1.x); } - else if (s3 & SEG_L) { n.x = vMin.x; n.y = p1.y + (p2.y - p1.y) * (vMin.x - p1.x) / (p2.x - p1.x); } - if (s3 == s1) { p1 = n; s1 = Segment(p1); } - else { p2 = n; s2 = Segment(p2); } - } - } - return true; -} - -bool olc::Draw2D::swClipWeightedLine(olc::vf2d& v0, olc::vf2d& v1, const olc::vf2d& vMin, const olc::vf2d& vMax, float& w0, float& w1) -{ - // Liang-Barsky line clipping algorithm adapted for weighted lines - // https://en.wikipedia.org/wiki/Liang%E2%80%93Barsky_algorithm - - olc::vf2d diff = v1 - v0; - - float p[4] = { -diff.x, diff.x, -diff.y, diff.y }; - - float q[4] = - { - v0.x - vMin.x, - vMax.x - v0.x, - v0.y - vMin.y, - vMax.y - v0.y - }; - - // Weights are ideal to start, we'll contact them as we clip - w0 = 0.0f; - w1 = 1.0f; - - for (int i = 0; i < 4; i++) - { - if (p[i] == 0.0f) - { - if (q[i] < 0.0f) - return false; // Line is parallel and outside the clipping boundary - } - else - { - float t = float(q[i]) / float(p[i]); - if (p[i] < 0.0f) - { - if (t > w1) - return false; // Line is outside the clipping boundary - else if (t > w0) - w0 = t; - } - else - { - if (t < w0) - return false; // Line is outside the clipping boundary - else if (t < w1) - w1 = t; - } - } - } - - if (w1 < w0) - return false; // Line is outside the clipping boundary - - // Return new line segment ends - olc::vf2d v = v0; - v0 = v + (diff * w0); - v1 = v + (diff * w1); - - // Line has visible pixels inside clipping boundary - return true; -} - -std::pair olc::Draw2D::swBaryFillTriangle(const olc::vi2d& v1, const olc::vi2d& v2, const olc::vi2d& v3) -{ - // Get height of triangle in whole pixels - int32_t nMinY = std::min({ v1.y, v2.y, v3.y }); - int32_t nMaxY = std::max({ v1.y, v2.y, v3.y }); - int32_t nHeight = nMaxY - nMinY; - - if (nHeight <= 0) - return { 0, 0 }; // Degenerate triangle - - // Scanline buffer is already allocated to be the max vertical size - // of the draw target. Obviously it only represents visible scanlines - // that are to be filled for the current triangle. - - // Get visible height of triangle - int32_t y_min = std::max(0, nMinY); - int32_t y_max = std::min(nMaxY, pTarget->Size().y); - - // Zero out scanline buffer (by resetting min and max values) - for (int32_t y = y_min; y < y_max; y++) - { - vScanlines[y].nMin = std::numeric_limits::max(); - vScanlines[y].nMax = std::numeric_limits::min(); - } - - // This function scans an edge of the triangle, updating - // the scanline buffer with min/max extents and barycentric coords. - // It returns the number of scanlines updated. - auto scanEdge = [&](olc::vi2d p0, olc::vi2d p1, int id1, int id2) -> size_t - { - if (p0.y == p1.y) - return 0; - - // Ensure p0.y < p1.y - bool swapped = false; - if (p0.y > p1.y) - { - std::swap(p0, p1); - swapped = true; - } - - // Cache edge step deltas - int dy = p1.y - p0.y; - float dx_step = (p1.x - p0.x) / float(dy); - float dy_step = 1.0f / float(dy); - float x = float(p0.x); - - // Rasterise edge - if pixel lies on visible scanline then - // update the scanline bounds and barycentric coords - size_t nScanline = 0; - for (int y = p0.y; y <= p1.y; y++) - { - // If this pixel row is visible - if (y >= 0 && y < vScanlines.size()) - { - int ix = int(std::round(x)); - - // interpolation along edge - // Note: We may need to do this differently when clipping - float t = (y - p0.y) * dy_step; - - std::array bary = { 0.0f, 0.0f, 0.0f }; - - // Set barycentric coords depending on edge direction - if (swapped) - { - bary[id1] = t; - bary[id2] = 1.0f - t; - } - else - { - bary[id1] = 1.0f - t; - bary[id2] = t; - } - - // Update scanline extents and barycentric coords - if (ix < vScanlines[y].nMin) - { - vScanlines[y].nMin = ix; - vScanlines[y].fBaryMin = bary; - } - - if (ix > vScanlines[y].nMax) - { - vScanlines[y].nMax = ix; - vScanlines[y].fBaryMax = bary; - } - - nScanline++; - } - - x += dx_step; - } - - return nScanline; - }; - - // Rasterise triangle edges into scanline buffer - scanEdge(v1, v2, 0, 1); - scanEdge(v1, v3, 0, 2); - scanEdge(v2, v3, 1, 2); - - return { y_min, y_max }; -} - -void olc::Draw2D::swRasterShadedTriangle(const olc::vi2d& v1, const olc::vi2d& v2, const olc::vi2d& v3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3) -{ - // We are writing to the target image, so make sure its memory resident (and up to date) - PrepareTargetForSW(); - - auto [y_min, y_max] = swBaryFillTriangle(v1, v2, v3); - - // Now draw the scanlines - for (int32_t y = y_min; y < y_max; y++) - { - const auto& scanline = vScanlines[y]; - - int32_t xStart = scanline.nMin; - int32_t xEnd = scanline.nMax; - - int32_t x_min = std::max(0, xStart); - int32_t x_max = std::min(xEnd, pTarget->Size().x); - - float fSpan = float(xEnd - xStart); - float fSpanStep = fSpan > 0.0f ? 1.0f / fSpan : 0.0f; - - float b0_step = fSpanStep * (scanline.fBaryMax[0] - scanline.fBaryMin[0]); - float b1_step = fSpanStep * (scanline.fBaryMax[1] - scanline.fBaryMin[1]); - float b2_step = fSpanStep * (scanline.fBaryMax[2] - scanline.fBaryMin[2]); - - float b0 = scanline.fBaryMin[0]; - float b1 = scanline.fBaryMin[1]; - float b2 = scanline.fBaryMin[2]; - - if (xStart < 0) - { - b0 = scanline.fBaryMin[0] + (-xStart * b0_step); - b1 = scanline.fBaryMin[1] + (-xStart * b1_step); - b2 = scanline.fBaryMin[2] + (-xStart * b2_step); - } - - for (int32_t x = x_min; x < x_max; x++) - { - olc::Pixel col = olc::Pixel( - uint8_t(c1.r * b0 + c2.r * b1 + c3.r * b2), - uint8_t(c1.g * b0 + c2.g * b1 + c3.g * b2), - uint8_t(c1.b * b0 + c2.b * b1 + c3.b * b2), - uint8_t(c1.a * b0 + c2.a * b1 + c3.a * b2)); - - // In theory, target (x,y) is always valid here due to clipping above - pTarget->Pixel({ x, y }) = col; - - b0 += b0_step; - b1 += b1_step; - b2 += b2_step; - } - } - - - return; -} - -void olc::Draw2D::swRasterTexturedTriangle(const olc::vi2d& v1, const olc::vi2d& v2, const olc::vi2d& v3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3, const olc::vf2d& t1, const olc::vf2d& t2, const olc::vf2d& t3, olc::Image& texture) -{ - // We are writing to the target image, so make sure its memory resident (and up to date) - PrepareTargetForSW(); - PrepareImageForSW(texture); - - auto [y_min, y_max] = swBaryFillTriangle(v1, v2, v3); - - // Now draw the scanlines - for (int32_t y = y_min; y < y_max; y++) - { - const auto& scanline = vScanlines[y]; - - int32_t xStart = scanline.nMin; - int32_t xEnd = scanline.nMax; - - int32_t x_min = std::max(0, xStart); - int32_t x_max = std::min(xEnd, pTarget->Size().x); - - float fSpan = float(xEnd - xStart); - float fSpanStep = fSpan > 0.0f ? 1.0f / fSpan : 0.0f; - - float b0_step = fSpanStep * (scanline.fBaryMax[0] - scanline.fBaryMin[0]); - float b1_step = fSpanStep * (scanline.fBaryMax[1] - scanline.fBaryMin[1]); - float b2_step = fSpanStep * (scanline.fBaryMax[2] - scanline.fBaryMin[2]); - - float b0 = scanline.fBaryMin[0]; - float b1 = scanline.fBaryMin[1]; - float b2 = scanline.fBaryMin[2]; - - if(xStart < 0) - { - b0 = scanline.fBaryMin[0] + (-xStart * b0_step); - b1 = scanline.fBaryMin[1] + (-xStart * b1_step); - b2 = scanline.fBaryMin[2] + (-xStart * b2_step); - } - - for (int32_t x = x_min; x < x_max; x++) - { - olc::Pixel col = olc::Pixel( - uint8_t(c1.r * b0 + c2.r * b1 + c3.r * b2), - uint8_t(c1.g * b0 + c2.g * b1 + c3.g * b2), - uint8_t(c1.b * b0 + c2.b * b1 + c3.b * b2), - uint8_t(c1.a * b0 + c2.a * b1 + c3.a * b2)); - - olc::vf2d uv = olc::vf2d( - b0 * t1.x + b1 * t2.x + b2 * t3.x, - b0 * t1.y + b1 * t2.y + b2 * t3.y); - - - // In theory, target (x,y) is always valid here due to clipping above - pTarget->Pixel({ x, y }) = col.blend(texture.Sample(uv)); - - b0 += b0_step; - b1 += b1_step; - b2 += b2_step; - } - } - - return; -} - - - -void olc::Draw2D::swRasterShadedLine(const olc::vi2d& v1, const olc::vi2d& v2, const olc::Pixel c1, const olc::Pixel c2) -{ - PrepareTargetForSW(); - - // Lambda to draw a pixel gated by a pattern bit - uint32_t pattern = 0xFFFFFFFF; - auto rol = [&](void) - { - pattern = (pattern << 1) | (pattern >> 31); - return pattern & 1; - }; - - // Lambda to draw a pixel at integer location - auto Plot = [&](int32_t x, int32_t y, const olc::Pixel& p) - { - if (x >= 0 && x < pTarget->Size().x && y >= 0 && y < pTarget->Size().y) - pTarget->Pixel({ x, y }) = p; - }; - - // Clip line to draw target - olc::vf2d clipped_p1 = v1; - olc::vf2d clipped_p2 = v2; - - // If line is completely outside bounds, exit - //if (!swClipLine(clipped_p1, clipped_p2, { 0,0 }, pTarget->Size())) - //return; - - float w0=0, w1=1; - if (!swClipWeightedLine(clipped_p1, clipped_p2, { 0,0 }, pTarget->Size(), w0, w1)) - return; - - // Move to integer space - olc::vi2d ip1 = clipped_p1; - olc::vi2d ip2 = clipped_p2; - olc::vi2d pixel; - - // Calculate deltas - int dx = ip2.x - ip1.x; - int dy = ip2.y - ip1.y; - int absDx = std::abs(dx); - int absDy = std::abs(dy); - - // Determine dominant axis - bool xMajor = absDx >= absDy; - int steps = xMajor ? absDx : absDy; - - // Handle degenerate case (single pixel) - if (steps == 0) - { - Plot(ip1.x, ip1.y, c1); - return; - } - - // Calculate step increments - float xStep = float(dx) / float(steps); - float yStep = float(dy) / float(steps); - float colorStep = 1.0f / float(steps) * (w1 - w0); - - olc::Pixel cStart = olc::PixelLerp(c1, c2, w0); - olc::Pixel cEnd = olc::PixelLerp(c1, c2, w1); - - // Starting position and color interpolation parameter - float x = float(ip1.x); - float y = float(ip1.y); - float t = 0.0f; - - // Draw line pixel by pixel - for (int i = 0; i <= steps; i++) - { - // Interpolate color - //olc::Pixel col = olc::PixelLerp(c1, c2, t); - olc::Pixel col = olc::PixelLerp(cStart, cEnd, t); - - // Plot pixel - if(rol()) - Plot((int)std::round(x), (int)std::round(y), col); - - // Step to next pixel - x += xStep; - y += yStep; - t += colorStep; - } - - - - return; -} - - - -//! END IMPLEMENTATION \ No newline at end of file diff --git a/dev/src/draw3d.cpp b/dev/src/draw3d.cpp deleted file mode 100644 index 5c5deb2b..00000000 --- a/dev/src/draw3d.cpp +++ /dev/null @@ -1,267 +0,0 @@ -#include "draw3d.h" -#include "gpu_iface.h" - -using namespace olc; - -//! START IMPLEMENTATION -thread_local Draw3D::buffer Draw3D::buffPoints; -thread_local Draw3D::buffer Draw3D::buffColours; -thread_local Draw3D::buffer Draw3D::vecGPUTasks; - -olc::Draw3D::Draw3D(olc::Draw2D& d2d) : draw2d(d2d) -{ - MatrixReset(); -} - -void olc::Draw3D::SetGPU(olc::gpu::Renderer* const renderer) -{ - draw2d.SetGPU(renderer); -} - -void olc::Draw3D::ProcessGPUTasks() -{ - draw2d.ProcessGPUTasks(); -} - -void olc::Draw3D::SetTarget(olc::Image& image) -{ - draw2d.SetTarget(image); -} - -olc::Image& olc::Draw3D::GetTarget() -{ - return draw2d.GetTarget(); -} - -olc::vi2d olc::Draw3D::GetTargetSize() -{ - return draw2d.GetTargetSize(); -} - -void olc::Draw3D::SetViewport(const olc::vi2d& pos, const olc::vi2d& size) -{ - draw2d.pRenderer->SetViewport(pos, size); -} - -bool olc::Draw3D::SetShader(const olc::gpu::Shader& shader) -{ - return draw2d.SetShader(shader); -} - -bool olc::Draw3D::ResetShader() -{ - return draw2d.ResetShader(); -} - -bool olc::Draw3D::SetShaderUniform(const std::string& name, const float value) -{ - return draw2d.SetShaderUniform(name, value); -} - -bool olc::Draw3D::SetShaderUniform(const std::string& name, const olc::vf2d& value) -{ - return draw2d.SetShaderUniform(name, value); -} - -bool olc::Draw3D::SetShaderUniform(const std::string& name, const olc::Pixel value) -{ - return draw2d.SetShaderUniform(name, value); -} - -bool olc::Draw3D::SetShaderTexture(const uint32_t nSlot, olc::Image& image) -{ - return draw2d.SetShaderTexture(nSlot, image); -} - -void olc::Draw3D::PrepareTargetForSW() -{ - draw2d.PrepareTargetForSW(); -} - -void olc::Draw3D::PrepareTargetForHW() -{ - draw2d.PrepareTargetForHW(); -} - -void olc::Draw3D::PrepareImageForSW(olc::Image& image) -{ - draw2d.PrepareImageForSW(image); -} - -void olc::Draw3D::PrepareImageForHW(olc::Image& image) -{ - draw2d.PrepareImageForHW(image); -} - - - - - -void olc::Draw3D::MatrixReset() -{ - matMVP.identity(); - matModel.identity(); - matView.identity(); - matProjection.identity(); -} - -void olc::Draw3D::SetModelMatrix(const olc::mf4d& mat) -{ - matModel = mat; - matMVP = matVP * matModel; -} - -const olc::mf4d& olc::Draw3D::GetModelMatrix() const -{ - return matModel; -} - -void olc::Draw3D::SetViewMatrix(const olc::mf4d& mat) -{ - matView = mat; - matVP = matProjection * matView; - matMVP = matVP * matModel; -} - -const olc::mf4d& olc::Draw3D::GetViewMatrix() const -{ - return matView; -} - -void olc::Draw3D::SetProjectionMatrix(const olc::mf4d& mat) -{ - matProjection = mat; - matVP = matProjection * matView; - matMVP = matVP * matModel; -} - -const olc::mf4d& olc::Draw3D::GetProjectionMatrix() const -{ - return matProjection; -} - -void olc::Draw3D::SetMVPMatrix(const olc::mf4d& mat) -{ - matMVP = mat; -} - -const olc::mf4d& olc::Draw3D::GetMVPMatrix() const -{ - return matMVP; -} - -void olc::Draw3D::SetCullMode(const olc::GPUTask::CullMode mode) -{ - cullMode = mode; -} - -void olc::Draw3D::EnableDepth(const bool bEnable) -{ - bDepth = bEnable; -} - - - -GPUTask olc::Draw3D::TaskWireMesh(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) -{ - GPUTask task; - task.structure = structure; - task.tint = tint; - task.vertexBuffer.resize(vPoints.size()); - task.bWireframe = true; - task.bDepth = bDepth; - task.cullmode = cullMode; - task.bIs3D = true; - task.mvpMatrix = matMVP.m; - - for (size_t i = 0; i < vPoints.size(); i++) - task.vertexBuffer[i] = { {vPoints[i].x, vPoints[i].y, vPoints[i].z, vPoints[i].w}, vColours[i], {0, 0}, {0, 0}, {0, 0}, {0, 0}}; - return task; -} - -GPUTask olc::Draw3D::TaskFillMesh(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) -{ - GPUTask task; - task.structure = structure; - task.tint = tint; - task.vertexBuffer.resize(vPoints.size()); - task.bDepth = bDepth; - task.cullmode = cullMode; - task.bIs3D = true; - task.mvpMatrix = matMVP.m; - - for (size_t i = 0; i < vPoints.size(); i++) - task.vertexBuffer[i] = { {vPoints[i].x, vPoints[i].y, vPoints[i].z, vPoints[i].w}, vColours[i], {0, 0}, {0, 0}, {0, 0}, {0, 0} }; - return task; -} - -GPUTask olc::Draw3D::TaskTexturedMesh(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const std::vector& vTexCoords, olc::Image* const image, const olc::Pixel tint) -{ - GPUTask task; - task.structure = structure; - task.tint = tint; - task.vertexBuffer.resize(vPoints.size()); - task.pImage = image; - task.bIs3D = true; - task.bDepth = bDepth; - task.cullmode = cullMode; - task.mvpMatrix = matMVP.m; - for (size_t i = 0; i < vPoints.size(); i++) - task.vertexBuffer[i] = { {vPoints[i].x, vPoints[i].y, vPoints[i].z, vPoints[i].w}, vColours[i], {vTexCoords[i].x, vTexCoords[i].y}, {0, 0}, {0, 0}, {0, 0} }; - return task; -} - -void olc::Draw3D::Clear(const olc::Pixel& col) -{ - draw2d.Clear(col); -} - -GPUTask& olc::Draw3D::Line(const olc::vf4d& vStart, const olc::vf4d& vEnd, const olc::Pixel& col, const olc::Pixel tint) -{ - PrepareTargetForHW(); - - return draw2d.vecGPUTasks.data.emplace_back(std::move( - TaskWireMesh( - olc::Structure::Line, - { vStart, vEnd }, - { col, col }, - tint - ))); -} - -GPUTask& olc::Draw3D::Mesh(const olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) -{ - PrepareTargetForHW(); - - return draw2d.vecGPUTasks.data.emplace_back(std::move( - TaskWireMesh( - structure, - vPoints, - vColours, - tint - ))); - -} - -GPUTask& olc::Draw3D::Mesh(const olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const std::vector& vUVs, olc::Image& texture, const olc::Pixel tint) -{ - PrepareImageForHW(texture); - PrepareTargetForHW(); - return draw2d.vecGPUTasks.data.emplace_back(std::move( - TaskTexturedMesh( - structure, - vPoints, - vColours, - vUVs, - &texture, - tint - ))); -} - - - - - - -//! END IMPLEMENTATION - diff --git a/dev/src/draw3d.h b/dev/src/draw3d.h deleted file mode 100644 index 05ba068a..00000000 --- a/dev/src/draw3d.h +++ /dev/null @@ -1,201 +0,0 @@ -#pragma once - -//! START STDHEADER -#include -#include -#include -#include -#include -#include -//! END STDHEADER - -//! START CUSTOMHEADER -#include "config.h" -#include "vector2d.h" -#include "vector4d.h" -#include "matrix4d.h" -#include "pixel.h" -#include "image.h" -#include "gputask.h" -#include "font.h" -#include "draw2d.h" -//! END CUSTOMHEADER - -//! START DECLARATION -#if !defined(PGE_DRAW3D_DECLARED) -namespace olc -{ - namespace gpu - { - class Renderer; - class Shader; - } - - class Draw3D - { - - - public: - Draw3D(olc::Draw2D& d2d); - - // Associate this drawing toolbox with a renderer - void SetGPU(olc::gpu::Renderer* const renderer); - void ProcessGPUTasks(); - - public: - // Sets the drawing target of this drawing toolbox - void SetTarget(olc::Image& image); - // Get the current drawing target - olc::Image& GetTarget(); - // Get Size of drawing target (aka GetTarget()->Size()) - olc::vi2d GetTargetSize(); - // Set the area in the target to 3d draw to - void SetViewport(const olc::vi2d& pos, const olc::vi2d& size); - - public: // Applied Matrices - void MatrixReset(); - void SetModelMatrix(const olc::mf4d& mat); - const olc::mf4d& GetModelMatrix() const; - void SetViewMatrix(const olc::mf4d& mat); - const olc::mf4d& GetViewMatrix() const; - void SetProjectionMatrix(const olc::mf4d& mat); - const olc::mf4d& GetProjectionMatrix() const; - void SetMVPMatrix(const olc::mf4d& mat); - const olc::mf4d& GetMVPMatrix() const; - - public: // Applied Rendering Modes - void SetCullMode(const olc::GPUTask::CullMode mode); - void EnableDepth(const bool bEnable); - - public: // Primitive Drawing Functions - // Clear entire draw target to specific colour - void Clear(const olc::Pixel& col); - - GPUTask& Line( - const olc::vf4d& vStart, - const olc::vf4d& vEnd, - const olc::Pixel& col = olc::Colour::WHITE, - const olc::Pixel tint = olc::Colour::WHITE); - - GPUTask& Mesh( - const olc::Structure structure, - const std::vector& vPoints, - const std::vector& vColours, - const olc::Pixel tint = olc::Colour::WHITE); - - GPUTask& Mesh( - const olc::Structure structure, - const std::vector& vPoints, - const std::vector& vColours, - const std::vector& vUVs, - olc::Image& texture, - const olc::Pixel tint = olc::Colour::WHITE); - - - - public: // GPU Task Creator Functions (not normally called by user) - GPUTask TaskWireMesh( - olc::Structure structure, - const std::vector& vPoints, - const std::vector& vColours, - const olc::Pixel tint = olc::Colour::WHITE); - - GPUTask TaskFillMesh( - olc::Structure structure, - const std::vector& vPoints, - const std::vector& vColours, - const olc::Pixel tint = olc::Colour::WHITE); - - GPUTask TaskTexturedMesh( - olc::Structure structure, - const std::vector& vPoints, - const std::vector& vColours, - const std::vector& vTexCoords, - olc::Image* const image, - const olc::Pixel tint = olc::Colour::WHITE); - - public: - // Change the shader used for subsequent GPU drawing tasks - bool SetShader(const olc::gpu::Shader& shader); - // Reset to default shader for subsequent GPU drawing tasks - bool ResetShader(); - // Set uniform variable for subsequent GPU drawing tasks - bool SetShaderUniform(const std::string& name, const float value); - // Set uniform variable for subsequent GPU drawing tasks - bool SetShaderUniform(const std::string& name, const olc::vf2d& value); - // Set uniform variable for subsequent GPU drawing tasks - bool SetShaderUniform(const std::string& name, const olc::Pixel value); - // Assign an image to a texture slot for subsequent GPU drawing tasks - bool SetShaderTexture(const uint32_t nSlot, olc::Image& image); - - public: - struct sDrawMetrics - { - uint32_t nGPUTasks = 0; - uint32_t nGPUtoCPUTransfers = 0; - uint32_t nCPUtoGPUTransfers = 0; - uint32_t nShaderChanges = 0; - }; - - void ResetDrawMetrics(); - sDrawMetrics GetDrawMetrics() const; - - private: - sDrawMetrics drawMetrics; - - - - - protected: - // Checks residency of image resource, and brings it to cpu RAM for r/w - void PrepareTargetForSW(); - // Checks residency of image resource, and brings it to gpu VRAM for r/w - void PrepareTargetForHW(); - - // Checks residency of image resource, and brings it to cpu RAM for r/w - void PrepareImageForSW(olc::Image& image); - // Checks residency of image resource, and brings it to gpu VRAM for r/w - void PrepareImageForHW(olc::Image& image); - - olc::Image* pTarget = nullptr; - olc::gpu::Renderer* pRenderer = nullptr; - - mf4d matModel; - mf4d matView; - mf4d matProjection; - mf4d matVP; - mf4d matMVP; - olc::vf2d vViewportPos = { 0, 0 }; - olc::vi2d vViewportSize = { 0, 0 }; - olc::GPUTask::CullMode cullMode = olc::GPUTask::CullMode::None; - bool bDepth = true; - - olc::Draw2D& draw2d; - - private: - // Simple dynamic buffer that only grows as needed - template - struct buffer - { - std::vector data; - - void reserve(size_t n) - { - if (n > data.capacity()) - data.reserve(n); - - // Ensure size matches requested so we - // can index into it directly - data.resize(n); - } - }; - - // Thread local buffers to avoid repeated allocations - static thread_local buffer buffPoints; - static thread_local buffer buffColours; - static thread_local buffer vecGPUTasks; - }; -} -#define PGE_DRAW3D_DECLARED -#endif -//! END DECLARATION \ No newline at end of file diff --git a/dev/src/sh_template.h b/dev/src/sh_template.h index 0a77fada..19b11159 100644 --- a/dev/src/sh_template.h +++ b/dev/src/sh_template.h @@ -135,9 +135,7 @@ //! GRAB gpu_iface.h DECLARATION -//! GRAB draw2d.h DECLARATION - -//! GRAB draw3d.h DECLARATION +//! GRAB draw.h DECLARATION //! GRAB hw_input.h DECLARATION @@ -270,15 +268,9 @@ #define PGE_GPU_IMPLEMENTED 1 #endif -#if defined(OLC_PGE3_APPLICATION) && !defined(PGE_DRAW2D_IMPLEMENTED) -//! GRAB draw2d.cpp IMPLEMENTATION -//! GRAB draw2d_sw.cpp IMPLEMENTATION -#define PGE_DRAW2D_IMPLEMENTED 1 -#endif - -#if defined(OLC_PGE3_APPLICATION) && !defined(PGE_DRAW3D_IMPLEMENTED) -//! GRAB draw3d.cpp IMPLEMENTATION -#define PGE_DRAW3D_IMPLEMENTED 1 +#if defined(OLC_PGE3_APPLICATION) && !defined(PGE_DRAW_IMPLEMENTED) +//! GRAB draw.cpp IMPLEMENTATION +#define PGE_DRAW_IMPLEMENTED 1 #endif #if defined(OLC_PGE3_APPLICATION) && !defined(PGE_CORE_IMPLEMENTED) diff --git a/dev/src/window.h b/dev/src/window.h index 2e500546..30e2c248 100644 --- a/dev/src/window.h +++ b/dev/src/window.h @@ -13,7 +13,7 @@ #include "config.h" #include "pixel.h" #include "vector2d.h" -#include "draw2d.h" +#include "draw.h" #include "hw_mouse.h" #include "hw_keyboard.h" //! END CUSTOMHEADER diff --git a/dev/tests/test_mh.cpp b/dev/tests/test_mh.cpp index 03b98227..754d3d58 100644 --- a/dev/tests/test_mh.cpp +++ b/dev/tests/test_mh.cpp @@ -382,12 +382,12 @@ class Example : public olc::PixelGameEngine olc::mf4d matView; olc::mf4d matWorld; - draw3d.SetViewport({ 0,0 }, GetScreen().Size()); - draw3d.MatrixReset(); + draw.SetViewport({ 0,0 }, GetScreen().Size()); + draw.MatrixReset(); matProj.perspective(90.0f * 3.14159f / 180.0f, float(ScreenSize().x) / float(ScreenSize().y), 0.1f, 1000.0f); - draw3d.SetProjectionMatrix(matProj); - draw3d.SetViewMatrix(matView); + draw.SetProjectionMatrix(matProj); + draw.SetViewMatrix(matView); if (keyboard.GetKey(olc::Key::LEFT).bHeld) vCubePos.x -= 5.0f * fElapsedTime; @@ -403,24 +403,24 @@ class Example : public olc::PixelGameEngine vCubePos.z -= 5.0f * fElapsedTime; matWorld.translate(vCubePos); - draw3d.SetModelMatrix(matWorld); + draw.SetModelMatrix(matWorld); for(int x = 0; x < 10; x++) for (int y = 0; y < 10; y++) for (int z = 0; z < 10; z++) { matWorld.translate(vCubePos + olc::vf4d(float(x) * 2.0f, float(y) * 2.0f, float(z) * 2.0f, 0)); - draw3d.SetModelMatrix(matWorld); - //draw3d.SetMVPMatrix(matProj * matView * matWorld); - draw3d.Mesh(cube.layout, cube.pos, cube.col, cube.uv, imSanityCube); + draw.SetModelMatrix(matWorld); + //draw.SetMVPMatrix(matProj * matView * matWorld); + draw.Mesh(cube.layout, cube.pos, cube.col, cube.uv, imSanityCube); } //draw3d.Mesh(cube.layout, cube.pos, cube.col); - draw3d.Line({ 0.0f, 0.0f, 0.0f },{ 1.0f, 0.0f, 0.0f },olc::Colour::RED); - draw3d.Line({ 0.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }, olc::Colour::GREEN); - draw3d.Line({ 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 1.0f }, olc::Colour::BLUE); + draw.Line({ 0.0f, 0.0f, 0.0f },{ 1.0f, 0.0f, 0.0f },olc::Colour::RED); + draw.Line({ 0.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }, olc::Colour::GREEN); + draw.Line({ 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 1.0f }, olc::Colour::BLUE); diff --git a/examples/olcPGE3_3DCube.cpp b/examples/olcPGE3_3DCube.cpp index 19009e46..0a6a2628 100644 --- a/examples/olcPGE3_3DCube.cpp +++ b/examples/olcPGE3_3DCube.cpp @@ -43,7 +43,7 @@ class Example_3DCube : public olc::PixelGameEngine // Only needs setting once, as the projection matrix doesn't change in this example matProj.perspective(90.0f * 3.14159f / 180.0f, float(ScreenSize().x) / float(ScreenSize().y), 0.1f, 100.0f); - draw3d.SetProjectionMatrix(matProj); + draw.SetProjectionMatrix(matProj); return true; } @@ -87,7 +87,7 @@ class Example_3DCube : public olc::PixelGameEngine matViewRotateX.rotateX(3.14159f); matViewTranslate.translate(vViewTranslate); matView = matViewRotateX * matViewTranslate; - draw3d.SetViewMatrix(matView); + draw.SetViewMatrix(matView); // Create a world matrix that rotates the cube over time. The cube is offset // so it rotates around its centre. Its verts are defined in the range 0..1 @@ -104,24 +104,24 @@ class Example_3DCube : public olc::PixelGameEngine // 2. Rotate the cube around the Y axis // 3. Rotate the cube around the X axis matWorld = matRotX * matRotY * matTrans; - draw3d.SetModelMatrix(matWorld); + draw.SetModelMatrix(matWorld); // The olc::SanityCube (TM) (c) is defined with vertices in clockwise order, // so cull counter-clockwise faces to show it off in all its glory! This is // counter to OpenGL's default culling mode, so it's a good test of the culling // system as well. - draw3d.SetCullMode(olc::GPUTask::CullMode::CounterClockWise); + draw.SetCullMode(olc::GPUTask::CullMode::CounterClockWise); // Draw the cube using the sanity cube's layout, and vectors of vertices, colours // and texture coordinates. - draw3d.Mesh(meshSanityCube.layout, meshSanityCube.pos, meshSanityCube.col, meshSanityCube.uv, imSanityCube); + draw.Mesh(meshSanityCube.layout, meshSanityCube.pos, meshSanityCube.col, meshSanityCube.uv, imSanityCube); // Draw a little RGB axis indicator matWorld.translate(-1,-1,-1); - draw3d.SetModelMatrix(matWorld); - draw3d.Line({ 0,0,0 }, { 1, 0, 0 }, olc::Colour::RED); - draw3d.Line({ 0,0,0 }, { 0, 1, 0 }, olc::Colour::GREEN); - draw3d.Line({ 0,0,0 }, { 0, 0, 1 }, olc::Colour::BLUE); + draw.SetModelMatrix(matWorld); + draw.Line({ 0,0,0 }, { 1, 0, 0 }, olc::Colour::RED); + draw.Line({ 0,0,0 }, { 0, 1, 0 }, olc::Colour::GREEN); + draw.Line({ 0,0,0 }, { 0, 0, 1 }, olc::Colour::BLUE); draw.StringProp({ 4, 4 }, "+X: Right\n-X: Left\n+Y: Up\n-Y: Down\n+Z: Q\n-Z: A\nSPIN: Space", olc::Colour::BLACK); @@ -143,7 +143,7 @@ class Example_3DCube : public olc::PixelGameEngine olc::Image imSanityCube; mesh meshSanityCube; - bool bSpinning = false; + bool bSpinning = true; // Behold!! The Sanity Cube!! A cube with all the correct vertex attributes, to diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index e49f465a..194e5e9a 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -2604,12 +2604,12 @@ namespace olc struct FilledBatch { GPUTask task; }; struct LineBatch { GPUTask task; }; - class Draw2D + class Draw { friend class olc::Draw3D; public: - Draw2D(); + Draw(); // Associate this drawing toolbox with a renderer void SetGPU(olc::gpu::Renderer* const renderer); @@ -3166,6 +3166,52 @@ namespace olc const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel tint = olc::Colour::WHITE); + + public: // Applied Rendering Modes + void SetCullMode(const olc::GPUTask::CullMode mode); + void EnableDepth(const bool bEnable); + void SetViewport(const olc::vi2d& pos, const olc::vi2d& size); + + public: // 3D Transformation Functions + void MatrixReset(); + void SetModelMatrix(const olc::mf4d& mat); + const olc::mf4d& GetModelMatrix() const; + void SetViewMatrix(const olc::mf4d& mat); + const olc::mf4d& GetViewMatrix() const; + void SetProjectionMatrix(const olc::mf4d& mat); + const olc::mf4d& GetProjectionMatrix() const; + void SetMVPMatrix(const olc::mf4d& mat); + const olc::mf4d& GetMVPMatrix() const; + + + + public: // 3D Primitive Drawing Functions + + GPUTask& Line( + const olc::vf4d& vStart, + const olc::vf4d& vEnd, + const olc::Pixel& col = olc::Colour::WHITE, + const olc::Pixel tint = olc::Colour::WHITE); + + GPUTask& Mesh( + const olc::Structure structure, + const std::vector& vPoints, + const std::vector& vColours, + const olc::Pixel tint = olc::Colour::WHITE); + + GPUTask& Mesh( + const olc::Structure structure, + const std::vector& vPoints, + const std::vector& vColours, + const std::vector& vUVs, + olc::Image& texture, + const olc::Pixel tint = olc::Colour::WHITE); + + + + public: // GPU Task Creator Functions (not normally called by user) + + public: @@ -3238,153 +3284,26 @@ namespace olc olc::Image* const image, const olc::Pixel tint = olc::Colour::WHITE); + // 3D Task Generators + GPUTask TaskWireMesh( + olc::Structure structure, + const std::vector& vPoints, + const std::vector& vColours, + const olc::Pixel tint = olc::Colour::WHITE); - public: // Precision drawing functions via software rasteriser - // Draws a single pixel wide line of fixed colour - void swLine( - const olc::vf2d& p1, - const olc::vf2d& p2, - const olc::Pixel col = olc::Colour::WHITE); - - // Draws a single pixel wide line with a gradient - void swLine( - const olc::vf2d& p1, - const olc::vf2d& p2, - const olc::Pixel c1, - const olc::Pixel c2); - - // Draws a rectangle outline - void swRect( - const olc::vf2d& pos, - const olc::vf2d& size, - const olc::Pixel col = olc::Colour::WHITE); - - // Draws a multiple colour rectangle, with linear colour interpolation - void swRect( - const olc::vf2d& pos, - const olc::vf2d& size, - const olc::Pixel colTL, - const olc::Pixel colTR, - const olc::Pixel colBL, - const olc::Pixel colBR); - - // Draws a filled, single colour rectangle - void swFilledRect( - const olc::vf2d& pos, - const olc::vf2d& size, - const olc::Pixel col = olc::Colour::WHITE); - - // Draws a filled, multiple colour rectangle, with linear colour interpolation - void swFilledRect( - const olc::vf2d& pos, - const olc::vf2d& size, - const olc::Pixel colTL, - const olc::Pixel colTR, - const olc::Pixel colBL, - const olc::Pixel colBR); - - // Draws a triangle outline - void swTriangle( - const olc::vf2d& p1, - const olc::vf2d& p2, - const olc::vf2d& p3, - const olc::Pixel col = olc::Colour::WHITE); - - // Draws a multiple colour triangle, with linear colour interpolation - void swTriangle( - const olc::vf2d& p1, - const olc::vf2d& p2, - const olc::vf2d& p3, - const olc::Pixel c1, - const olc::Pixel c2, - const olc::Pixel c3); - - // Draws a filled, single colour triangle - void swFilledTriangle( - const olc::vf2d& p1, - const olc::vf2d& p2, - const olc::vf2d& p3, - const olc::Pixel col = olc::Colour::WHITE); - - // Draws a filled, multiple colour triangle, with linear colour interpolation - void swFilledTriangle( - const olc::vf2d& p1, - const olc::vf2d& p2, - const olc::vf2d& p3, - const olc::Pixel c1, - const olc::Pixel c2, - const olc::Pixel c3); - - // Rasterises a textured triangle in integer space - void swTexturedTriangle( - const olc::vf2d& p1, - const olc::vf2d& p2, - const olc::vf2d& p3, - const olc::Pixel c1, - const olc::Pixel c2, - const olc::Pixel c3, - const olc::vf2d& t1, - const olc::vf2d& t2, - const olc::vf2d& t3, - olc::Image& texture); - - - - protected: // Software rasteriser helper functions - - // Clips a line to a rectangular region, returns true if line is visible - bool swClipLine( - olc::vf2d& v0, - olc::vf2d& v1, - const olc::vf2d& vMin, - const olc::vf2d& vMax); - - // Clips a line to a rectangular region, returns true if line is visible. - // The returned weights correspond to distance along the line from v0 to v1 - bool swClipWeightedLine( - olc::vf2d& v0, - olc::vf2d& v1, - const olc::vf2d& vMin, - const olc::vf2d& vMax, - float& w0, - float& w1); - - /* bool swClipTriangle( - olc::vf2d& v1, - olc::vf2d& v2, - olc::vf2d& v3, - const olc::vf2d& vMin, - const olc::vf2d& vMax);*/ - - // Rasterises a shaded line in integer space - void swRasterShadedLine( - const olc::vi2d& v1, - const olc::vi2d& v2, - const olc::Pixel c1, - const olc::Pixel c2); - - // Rasterises a shaded triangle in integer space - void swRasterShadedTriangle( - const olc::vi2d& v1, - const olc::vi2d& v2, - const olc::vi2d& v3, - const olc::Pixel c1, - const olc::Pixel c2, - const olc::Pixel c3); - - // Rasterises a textured triangle in integer space - void swRasterTexturedTriangle( - const olc::vi2d& v1, - const olc::vi2d& v2, - const olc::vi2d& v3, - const olc::Pixel c1, - const olc::Pixel c2, - const olc::Pixel c3, - const olc::vf2d& t1, - const olc::vf2d& t2, - const olc::vf2d& t3, - olc::Image& texture); + GPUTask TaskFillMesh( + olc::Structure structure, + const std::vector& vPoints, + const std::vector& vColours, + const olc::Pixel tint = olc::Colour::WHITE); + GPUTask TaskTexturedMesh( + olc::Structure structure, + const std::vector& vPoints, + const std::vector& vColours, + const std::vector& vTexCoords, + olc::Image* const image, + const olc::Pixel tint = olc::Colour::WHITE); public: @@ -3434,26 +3353,18 @@ namespace olc olc::gpu::Renderer* pRenderer = nullptr; olc::tf2d transformAffine; - //std::vector vecGPUTasks; - - protected: // SW Rasteriser Helpers - struct Scanline - { - int32_t nMin = std::numeric_limits::max(); - int32_t nMax = std::numeric_limits::min(); - std::array fBaryMin; - std::array fBaryMax; - }; - - std::vector vScanlines; + // 3D Drawing Things + mf4d matModel; + mf4d matView; + mf4d matProjection; + mf4d matVP; + mf4d matMVP; + olc::vf2d vViewportPos = { 0, 0 }; + olc::vi2d vViewportSize = { 0, 0 }; - // Fills scanline buffer with visible triangle extents and barycentric coordinates. - // Returns vertical, visible extents of triangle scanlines - std::pair swBaryFillTriangle( - const olc::vi2d& v1, - const olc::vi2d& v2, - const olc::vi2d& v3); + olc::GPUTask::CullMode cullMode = olc::GPUTask::CullMode::None; + bool bDepth = true; private: @@ -3487,183 +3398,6 @@ namespace olc #define PGE_DRAW2D_DECLARED #endif -#if !defined(PGE_DRAW3D_DECLARED) -namespace olc -{ - namespace gpu - { - class Renderer; - class Shader; - } - - class Draw3D - { - - - public: - Draw3D(olc::Draw2D& d2d); - - // Associate this drawing toolbox with a renderer - void SetGPU(olc::gpu::Renderer* const renderer); - void ProcessGPUTasks(); - - public: - // Sets the drawing target of this drawing toolbox - void SetTarget(olc::Image& image); - // Get the current drawing target - olc::Image& GetTarget(); - // Get Size of drawing target (aka GetTarget()->Size()) - olc::vi2d GetTargetSize(); - // Set the area in the target to 3d draw to - void SetViewport(const olc::vi2d& pos, const olc::vi2d& size); - - public: // Applied Matrices - void MatrixReset(); - void SetModelMatrix(const olc::mf4d& mat); - const olc::mf4d& GetModelMatrix() const; - void SetViewMatrix(const olc::mf4d& mat); - const olc::mf4d& GetViewMatrix() const; - void SetProjectionMatrix(const olc::mf4d& mat); - const olc::mf4d& GetProjectionMatrix() const; - void SetMVPMatrix(const olc::mf4d& mat); - const olc::mf4d& GetMVPMatrix() const; - - public: // Applied Rendering Modes - void SetCullMode(const olc::GPUTask::CullMode mode); - void EnableDepth(const bool bEnable); - - public: // Primitive Drawing Functions - // Clear entire draw target to specific colour - void Clear(const olc::Pixel& col); - - GPUTask& Line( - const olc::vf4d& vStart, - const olc::vf4d& vEnd, - const olc::Pixel& col = olc::Colour::WHITE, - const olc::Pixel tint = olc::Colour::WHITE); - - GPUTask& Mesh( - const olc::Structure structure, - const std::vector& vPoints, - const std::vector& vColours, - const olc::Pixel tint = olc::Colour::WHITE); - - GPUTask& Mesh( - const olc::Structure structure, - const std::vector& vPoints, - const std::vector& vColours, - const std::vector& vUVs, - olc::Image& texture, - const olc::Pixel tint = olc::Colour::WHITE); - - - - public: // GPU Task Creator Functions (not normally called by user) - GPUTask TaskWireMesh( - olc::Structure structure, - const std::vector& vPoints, - const std::vector& vColours, - const olc::Pixel tint = olc::Colour::WHITE); - - GPUTask TaskFillMesh( - olc::Structure structure, - const std::vector& vPoints, - const std::vector& vColours, - const olc::Pixel tint = olc::Colour::WHITE); - - GPUTask TaskTexturedMesh( - olc::Structure structure, - const std::vector& vPoints, - const std::vector& vColours, - const std::vector& vTexCoords, - olc::Image* const image, - const olc::Pixel tint = olc::Colour::WHITE); - - public: - // Change the shader used for subsequent GPU drawing tasks - bool SetShader(const olc::gpu::Shader& shader); - // Reset to default shader for subsequent GPU drawing tasks - bool ResetShader(); - // Set uniform variable for subsequent GPU drawing tasks - bool SetShaderUniform(const std::string& name, const float value); - // Set uniform variable for subsequent GPU drawing tasks - bool SetShaderUniform(const std::string& name, const olc::vf2d& value); - // Set uniform variable for subsequent GPU drawing tasks - bool SetShaderUniform(const std::string& name, const olc::Pixel value); - // Assign an image to a texture slot for subsequent GPU drawing tasks - bool SetShaderTexture(const uint32_t nSlot, olc::Image& image); - - public: - struct sDrawMetrics - { - uint32_t nGPUTasks = 0; - uint32_t nGPUtoCPUTransfers = 0; - uint32_t nCPUtoGPUTransfers = 0; - uint32_t nShaderChanges = 0; - }; - - void ResetDrawMetrics(); - sDrawMetrics GetDrawMetrics() const; - - private: - sDrawMetrics drawMetrics; - - - - - protected: - // Checks residency of image resource, and brings it to cpu RAM for r/w - void PrepareTargetForSW(); - // Checks residency of image resource, and brings it to gpu VRAM for r/w - void PrepareTargetForHW(); - - // Checks residency of image resource, and brings it to cpu RAM for r/w - void PrepareImageForSW(olc::Image& image); - // Checks residency of image resource, and brings it to gpu VRAM for r/w - void PrepareImageForHW(olc::Image& image); - - olc::Image* pTarget = nullptr; - olc::gpu::Renderer* pRenderer = nullptr; - - mf4d matModel; - mf4d matView; - mf4d matProjection; - mf4d matVP; - mf4d matMVP; - olc::vf2d vViewportPos = { 0, 0 }; - olc::vi2d vViewportSize = { 0, 0 }; - olc::GPUTask::CullMode cullMode = olc::GPUTask::CullMode::None; - bool bDepth = true; - - olc::Draw2D& draw2d; - - private: - // Simple dynamic buffer that only grows as needed - template - struct buffer - { - std::vector data; - - void reserve(size_t n) - { - if (n > data.capacity()) - data.reserve(n); - - // Ensure size matches requested so we - // can index into it directly - data.resize(n); - } - }; - - // Thread local buffers to avoid repeated allocations - static thread_local buffer buffPoints; - static thread_local buffer buffColours; - static thread_local buffer vecGPUTasks; - }; -} -#define PGE_DRAW3D_DECLARED -#endif - #if !defined(PGE_HARDWAREINPUT_DECLARED) namespace olc { @@ -4103,7 +3837,7 @@ namespace olc public: // Returns the image that represents the primary drawing surface olc::Image& GetScreen(); - olc::Draw2D& GetDraw(); + olc::Draw& GetDraw(); // Input devices are handled by a regular olc::Window, but for convenience... olc::hw::Mouse& GetMouse(); @@ -4119,9 +3853,7 @@ namespace olc virtual bool olc_WindowUpdate(const float fElapsedTime, const float fTotalElapsedTime); protected: - olc::Draw2D draw; - olc::Draw3D draw3d; - + olc::Draw draw; private: olc::Image imgPrimary; @@ -14284,26 +14016,26 @@ void main() #define PGE_GPU_IMPLEMENTED 1 #endif -#if defined(OLC_PGE3_APPLICATION) && !defined(PGE_DRAW2D_IMPLEMENTED) +#if defined(OLC_PGE3_APPLICATION) && !defined(PGE_DRAW_IMPLEMENTED) using namespace olc; // Some local pools to reduce allocations -thread_local Draw2D::buffer Draw2D::buffPoints; -thread_local Draw2D::buffer Draw2D::buffColours; -thread_local Draw2D::buffer Draw2D::buffUnitCirclePoints; -thread_local Draw2D::buffer Draw2D::vecGPUTasks; +thread_local Draw::buffer Draw::buffPoints; +thread_local Draw::buffer Draw::buffColours; +thread_local Draw::buffer Draw::buffUnitCirclePoints; +thread_local Draw::buffer Draw::vecGPUTasks; -Draw2D::Draw2D() +Draw::Draw() { vecGPUTasks.reserve(256); } -void Draw2D::SetGPU(olc::gpu::Renderer* const renderer) +void Draw::SetGPU(olc::gpu::Renderer* const renderer) { pRenderer = renderer; } -void Draw2D::SetTarget(olc::Image& image) +void Draw::SetTarget(olc::Image& image) { // Perform any outstanding tasks for current target ProcessGPUTasks(); @@ -14328,17 +14060,17 @@ void Draw2D::SetTarget(olc::Image& image) pRenderer->SetViewport({ 0,0 }, pTarget->Size()); } -olc::Image& olc::Draw2D::GetTarget() +olc::Image& olc::Draw::GetTarget() { return *pTarget; } -olc::vi2d olc::Draw2D::GetTargetSize() +olc::vi2d olc::Draw::GetTargetSize() { return pTarget->Size(); } -void olc::Draw2D::ProcessGPUTasks() +void olc::Draw::ProcessGPUTasks() { for (const auto& task : vecGPUTasks.data) pRenderer->DoGPUTask(task); @@ -14348,7 +14080,7 @@ void olc::Draw2D::ProcessGPUTasks() vecGPUTasks.data.clear(); } -void Draw2D::PrepareTargetForSW() +void Draw::PrepareTargetForSW() { if (pTarget->BoundToGPU()) { @@ -14362,13 +14094,10 @@ void Draw2D::PrepareTargetForSW() pTarget->BindCPU(); drawMetrics.nGPUtoCPUTransfers++; - - // Create a scanline buffer the height of this target - vScanlines.resize(size_t(pTarget->Size().y), {}); } } -void Draw2D::PrepareTargetForHW() +void Draw::PrepareTargetForHW() { if (pTarget->BoundToCPU()) { @@ -14382,7 +14111,7 @@ void Draw2D::PrepareTargetForHW() } } -void Draw2D::PrepareImageForSW(olc::Image& image) +void Draw::PrepareImageForSW(olc::Image& image) { if (image.BoundToGPU()) { @@ -14399,7 +14128,7 @@ void Draw2D::PrepareImageForSW(olc::Image& image) } } -void Draw2D::PrepareImageForHW(olc::Image& image) +void Draw::PrepareImageForHW(olc::Image& image) { if (image.BoundToCPU()) { @@ -14420,7 +14149,7 @@ void Draw2D::PrepareImageForHW(olc::Image& image) } } -bool olc::Draw2D::SetShader(const olc::gpu::Shader& shader) +bool olc::Draw::SetShader(const olc::gpu::Shader& shader) { // Finish all drawing with current shader ProcessGPUTasks(); @@ -14431,85 +14160,85 @@ bool olc::Draw2D::SetShader(const olc::gpu::Shader& shader) return pRenderer->ApplyShader(shader); } -bool olc::Draw2D::ResetShader() +bool olc::Draw::ResetShader() { ProcessGPUTasks(); drawMetrics.nShaderChanges++; return pRenderer->ApplyDefaultShader(); } -bool olc::Draw2D::SetShaderUniform(const std::string& name, const float value) +bool olc::Draw::SetShaderUniform(const std::string& name, const float value) { return pRenderer->SetUniform(name, value); } -bool olc::Draw2D::SetShaderUniform(const std::string& name, const olc::vf2d& value) +bool olc::Draw::SetShaderUniform(const std::string& name, const olc::vf2d& value) { return pRenderer->SetUniform(name, value); } -bool olc::Draw2D::SetShaderUniform(const std::string& name, const olc::Pixel value) +bool olc::Draw::SetShaderUniform(const std::string& name, const olc::Pixel value) { return pRenderer->SetUniform(name, value); } -bool olc::Draw2D::SetShaderTexture(const uint32_t nSlot, olc::Image& image) +bool olc::Draw::SetShaderTexture(const uint32_t nSlot, olc::Image& image) { PrepareImageForHW(image); return pRenderer->AssignTextureSource(nSlot, image.GetGPUID()); } -void olc::Draw2D::ResetDrawMetrics() +void olc::Draw::ResetDrawMetrics() { drawMetrics = sDrawMetrics(); } -olc::Draw2D::sDrawMetrics olc::Draw2D::GetDrawMetrics() const +olc::Draw::sDrawMetrics olc::Draw::GetDrawMetrics() const { return drawMetrics; } -void olc::Draw2D::WorldReset() +void olc::Draw::WorldReset() { transformAffine = olc::tf2d(); } -void olc::Draw2D::WorldScale(const olc::vf2d& vScale) +void olc::Draw::WorldScale(const olc::vf2d& vScale) { transformAffine.scale(vScale); } -void olc::Draw2D::WorldOffset(const olc::vf2d& vOffset) +void olc::Draw::WorldOffset(const olc::vf2d& vOffset) { transformAffine.translate(vOffset); } -void olc::Draw2D::WorldRotate(const float& fTheta, const olc::vf2d& vPoint) +void olc::Draw::WorldRotate(const float& fTheta, const olc::vf2d& vPoint) { transformAffine.rotate(fTheta, vPoint); } -void olc::Draw2D::SetWorldTransform(const olc::tf2d& trans) +void olc::Draw::SetWorldTransform(const olc::tf2d& trans) { transformAffine = trans; } -olc::tf2d& olc::Draw2D::GetWorldTransform() +olc::tf2d& olc::Draw::GetWorldTransform() { return transformAffine; } -olc::vf2d olc::Draw2D::WorldToScreen(const olc::vf2d& v) const +olc::vf2d olc::Draw::WorldToScreen(const olc::vf2d& v) const { return transformAffine.forward(v); } -olc::vf2d olc::Draw2D::ScreenToWorld(const olc::vf2d& v) const +olc::vf2d olc::Draw::ScreenToWorld(const olc::vf2d& v) const { return transformAffine.inverse(v); } -void Draw2D::Pixel(const olc::vf2d& pos, const olc::Pixel col, const olc::Pixel tint) +void Draw::Pixel(const olc::vf2d& pos, const olc::Pixel col, const olc::Pixel tint) { // Check if in bounds olc::vf2d tpos = transformAffine.forwardRound(pos); @@ -14522,25 +14251,25 @@ void Draw2D::Pixel(const olc::vf2d& pos, const olc::Pixel col, const olc::Pixel // otherwise do nothing } -olc::Pixel olc::Draw2D::GetPixel(olc::Image& image, const olc::vf2d& pos) +olc::Pixel olc::Draw::GetPixel(olc::Image& image, const olc::vf2d& pos) { PrepareImageForSW(image); return image.Pixel(pos); } -olc::Pixel olc::Draw2D::GetPixel(const olc::vf2d& pos) +olc::Pixel olc::Draw::GetPixel(const olc::vf2d& pos) { PrepareImageForSW(GetTarget()); return GetTarget().Pixel(pos); } -void olc::Draw2D::Clear(const olc::Pixel& col) +void olc::Draw::Clear(const olc::Pixel& col) { PrepareTargetForHW(); pRenderer->ClearViewport(col, true, true); } -GPUTask olc::Draw2D::TaskDrawLine(const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) +GPUTask olc::Draw::TaskDrawLine(const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) { GPUTask task; task.structure = olc::Structure::Line; @@ -14554,7 +14283,7 @@ GPUTask olc::Draw2D::TaskDrawLine(const std::vector& vPoints, const s return task; } -GPUTask olc::Draw2D::TaskDrawPolygon(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) +GPUTask olc::Draw::TaskDrawPolygon(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) { GPUTask task; task.structure = structure; @@ -14566,7 +14295,7 @@ GPUTask olc::Draw2D::TaskDrawPolygon(olc::Structure structure, const std::vector return task; } -GPUTask olc::Draw2D::TaskDrawPolygon(olc::Structure structure, const std::vector& vPoints, const olc::Pixel colour, const olc::Pixel tint) +GPUTask olc::Draw::TaskDrawPolygon(olc::Structure structure, const std::vector& vPoints, const olc::Pixel colour, const olc::Pixel tint) { GPUTask task; task.structure = structure; @@ -14578,7 +14307,7 @@ GPUTask olc::Draw2D::TaskDrawPolygon(olc::Structure structure, const std::vector return task; } -GPUTask olc::Draw2D::TaskFillPolygon(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) +GPUTask olc::Draw::TaskFillPolygon(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) { GPUTask task; task.structure = structure; @@ -14589,7 +14318,7 @@ GPUTask olc::Draw2D::TaskFillPolygon(olc::Structure structure, const std::vector return task; } -GPUTask olc::Draw2D::TaskFillPolygon(olc::Structure structure, const std::vector& vPoints, const olc::Pixel colour, const olc::Pixel tint) +GPUTask olc::Draw::TaskFillPolygon(olc::Structure structure, const std::vector& vPoints, const olc::Pixel colour, const olc::Pixel tint) { GPUTask task; task.structure = structure; @@ -14600,7 +14329,7 @@ GPUTask olc::Draw2D::TaskFillPolygon(olc::Structure structure, const std::vector return task; } -GPUTask olc::Draw2D::TaskTexturedPolygon(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const std::vector& vTexCoords, olc::Image* const image, const olc::Pixel tint) +GPUTask olc::Draw::TaskTexturedPolygon(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const std::vector& vTexCoords, olc::Image* const image, const olc::Pixel tint) { GPUTask task; task.structure = structure; @@ -14612,7 +14341,7 @@ GPUTask olc::Draw2D::TaskTexturedPolygon(olc::Structure structure, const std::ve return task; } -GPUTask olc::Draw2D::TaskTexturedPolygon(olc::Structure structure, const std::vector& vPoints, const std::vector& vZWs, const std::vector& vColours, const std::vector& vTexCoords, olc::Image* const image, const olc::Pixel tint) +GPUTask olc::Draw::TaskTexturedPolygon(olc::Structure structure, const std::vector& vPoints, const std::vector& vZWs, const std::vector& vColours, const std::vector& vTexCoords, olc::Image* const image, const olc::Pixel tint) { GPUTask task; task.structure = structure; @@ -14624,9 +14353,58 @@ GPUTask olc::Draw2D::TaskTexturedPolygon(olc::Structure structure, const std::ve return task; } +GPUTask olc::Draw::TaskWireMesh(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) +{ + GPUTask task; + task.structure = structure; + task.tint = tint; + task.vertexBuffer.resize(vPoints.size()); + task.bWireframe = true; + task.bDepth = bDepth; + task.cullmode = cullMode; + task.bIs3D = true; + task.mvpMatrix = matMVP.m; + + for (size_t i = 0; i < vPoints.size(); i++) + task.vertexBuffer[i] = { {vPoints[i].x, vPoints[i].y, vPoints[i].z, vPoints[i].w}, vColours[i], {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + return task; +} + +GPUTask olc::Draw::TaskFillMesh(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) +{ + GPUTask task; + task.structure = structure; + task.tint = tint; + task.vertexBuffer.resize(vPoints.size()); + task.bDepth = bDepth; + task.cullmode = cullMode; + task.bIs3D = true; + task.mvpMatrix = matMVP.m; + + for (size_t i = 0; i < vPoints.size(); i++) + task.vertexBuffer[i] = { {vPoints[i].x, vPoints[i].y, vPoints[i].z, vPoints[i].w}, vColours[i], {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + return task; +} + +GPUTask olc::Draw::TaskTexturedMesh(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const std::vector& vTexCoords, olc::Image* const image, const olc::Pixel tint) +{ + GPUTask task; + task.structure = structure; + task.tint = tint; + task.vertexBuffer.resize(vPoints.size()); + task.pImage = image; + task.bIs3D = true; + task.bDepth = bDepth; + task.cullmode = cullMode; + task.mvpMatrix = matMVP.m; + for (size_t i = 0; i < vPoints.size(); i++) + task.vertexBuffer[i] = { {vPoints[i].x, vPoints[i].y, vPoints[i].z, vPoints[i].w}, vColours[i], {vTexCoords[i].x, vTexCoords[i].y}, {0, 0}, {0, 0}, {0, 0} }; + return task; +} + -const GPUTask& Draw2D::Line(const olc::vf2d& p1, const olc::vf2d& p2, const olc::Pixel col, const olc::Pixel tint) +const GPUTask& Draw::Line(const olc::vf2d& p1, const olc::vf2d& p2, const olc::Pixel col, const olc::Pixel tint) { PrepareTargetForHW(); @@ -14638,12 +14416,12 @@ const GPUTask& Draw2D::Line(const olc::vf2d& p1, const olc::vf2d& p2, const olc: ))); } -const LineBatch& olc::Draw2D::Line(olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::Pixel col) +const LineBatch& olc::Draw::Line(olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::Pixel col) { return Line(batch, p1, col, p2, col); } -const GPUTask& Draw2D::Line(const olc::vf2d& p1, const olc::Pixel c1, const olc::vf2d& p2, const olc::Pixel c2, const olc::Pixel tint) +const GPUTask& Draw::Line(const olc::vf2d& p1, const olc::Pixel c1, const olc::vf2d& p2, const olc::Pixel c2, const olc::Pixel tint) { PrepareTargetForHW(); @@ -14656,7 +14434,7 @@ const GPUTask& Draw2D::Line(const olc::vf2d& p1, const olc::Pixel c1, const olc: ))); } -const LineBatch& olc::Draw2D::Line(olc::LineBatch& batch, const olc::vf2d& p1, const olc::Pixel c1, const olc::vf2d& p2, const olc::Pixel c2) +const LineBatch& olc::Draw::Line(olc::LineBatch& batch, const olc::vf2d& p1, const olc::Pixel c1, const olc::vf2d& p2, const olc::Pixel c2) { batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + 2); size_t idx = batch.task.vertexBuffer.size() - 2; @@ -14667,7 +14445,7 @@ const LineBatch& olc::Draw2D::Line(olc::LineBatch& batch, const olc::vf2d& p1, c return batch; } -const GPUTask& olc::Draw2D::Rect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col, const olc::Pixel tint) +const GPUTask& olc::Draw::Rect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col, const olc::Pixel tint) { // Right a big, hearty, F&^% you to OpenGL's Diamond Exit Strategy. It makes line drawing // with OpenGL a smidge unreliable @@ -14688,12 +14466,12 @@ const GPUTask& olc::Draw2D::Rect(const olc::vf2d& pos, const olc::vf2d& size, co ))); } -const LineBatch& olc::Draw2D::Rect(olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) +const LineBatch& olc::Draw::Rect(olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) { return Rect(batch, pos, size, col, col, col, col); } -const GPUTask& olc::Draw2D::Rect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR, const olc::Pixel tint) +const GPUTask& olc::Draw::Rect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR, const olc::Pixel tint) { PrepareTargetForHW(); @@ -14711,7 +14489,7 @@ const GPUTask& olc::Draw2D::Rect(const olc::vf2d& pos, const olc::vf2d& size, co ))); } -const LineBatch& olc::Draw2D::Rect(olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR) +const LineBatch& olc::Draw::Rect(olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR) { const olc::vf2d pTL = olc::vf2d(pos.x, pos.y); const olc::vf2d pTR = olc::vf2d(pos.x + size.x, pos.y); @@ -14728,7 +14506,7 @@ const LineBatch& olc::Draw2D::Rect(olc::LineBatch& batch, const olc::vf2d& pos, } -const GPUTask& olc::Draw2D::FilledRect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col, const olc::Pixel tint) +const GPUTask& olc::Draw::FilledRect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col, const olc::Pixel tint) { PrepareTargetForHW(); @@ -14745,14 +14523,14 @@ const GPUTask& olc::Draw2D::FilledRect(const olc::vf2d& pos, const olc::vf2d& si ))); } -const FilledBatch& olc::Draw2D::FilledRect(olc::FilledBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) +const FilledBatch& olc::Draw::FilledRect(olc::FilledBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) { FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y), olc::vf2d(pos.x + size.x, pos.y + size.y), col, col, col); FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y + size.y), olc::vf2d(pos.x, pos.y + size.y), col, col, col); return batch; } -const GPUTask& olc::Draw2D::FilledRect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR, const olc::Pixel tint) +const GPUTask& olc::Draw::FilledRect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR, const olc::Pixel tint) { PrepareTargetForHW(); return vecGPUTasks.data.emplace_back(std::move( @@ -14768,14 +14546,14 @@ const GPUTask& olc::Draw2D::FilledRect(const olc::vf2d& pos, const olc::vf2d& si ))); } -const FilledBatch& olc::Draw2D::FilledRect(olc::FilledBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR) +const FilledBatch& olc::Draw::FilledRect(olc::FilledBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR) { FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y), olc::vf2d(pos.x + size.x, pos.y + size.y), colTL, colTR, colBR); FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y + size.y), olc::vf2d(pos.x, pos.y + size.y), colTL, colBR, colBL); return batch; } -void olc::Draw2D::RedefineUnitCircleBuffer(const int32_t nFacets) +void olc::Draw::RedefineUnitCircleBuffer(const int32_t nFacets) { buffUnitCirclePoints.reserve(nFacets + 1); for (int32_t i = 0; i <= nFacets; i++) @@ -14785,37 +14563,37 @@ void olc::Draw2D::RedefineUnitCircleBuffer(const int32_t nFacets) } } -const GPUTask& olc::Draw2D::Circle(const olc::vf2d& pos, const float& radius, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) +const GPUTask& olc::Draw::Circle(const olc::vf2d& pos, const float& radius, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { return Ellipse(pos, radius, radius, col, tint, nFacets); } -const LineBatch& olc::Draw2D::Circle(olc::LineBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel col, int32_t nFacets) +const LineBatch& olc::Draw::Circle(olc::LineBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel col, int32_t nFacets) { return Ellipse(batch, pos, radius, radius, col, nFacets); } -const GPUTask& olc::Draw2D::FilledCircle(const olc::vf2d& pos, const float& radius, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) +const GPUTask& olc::Draw::FilledCircle(const olc::vf2d& pos, const float& radius, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { return FilledEllipse(pos, radius, radius, col, tint, nFacets); } -const FilledBatch& olc::Draw2D::FilledCircle(olc::FilledBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel col, int32_t nFacets) +const FilledBatch& olc::Draw::FilledCircle(olc::FilledBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel col, int32_t nFacets) { return FilledEllipse(batch, pos, radius, radius, col, nFacets); } -const GPUTask& olc::Draw2D::FilledCircle(const olc::vf2d& pos, const float& radius, const olc::Pixel colInner, const olc::Pixel colOuter, const olc::Pixel tint, int32_t nFacets) +const GPUTask& olc::Draw::FilledCircle(const olc::vf2d& pos, const float& radius, const olc::Pixel colInner, const olc::Pixel colOuter, const olc::Pixel tint, int32_t nFacets) { return FilledEllipse(pos, radius, radius, colInner, colOuter, tint, nFacets); } -const FilledBatch& olc::Draw2D::FilledCircle(olc::FilledBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel colInner, const olc::Pixel colOuter, int32_t nFacets) +const FilledBatch& olc::Draw::FilledCircle(olc::FilledBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel colInner, const olc::Pixel colOuter, int32_t nFacets) { return FilledEllipse(batch, pos, radius, radius, colInner, colOuter, nFacets); } -const GPUTask& olc::Draw2D::Ellipse(const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) +const GPUTask& olc::Draw::Ellipse(const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { // Fundamental for outline circle/ellipse with colour solid/gradient PrepareTargetForHW(); @@ -14841,7 +14619,7 @@ const GPUTask& olc::Draw2D::Ellipse(const olc::vf2d& pos, const float& rx, const ))); } -const LineBatch& olc::Draw2D::Ellipse(olc::LineBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, int32_t nFacets) +const LineBatch& olc::Draw::Ellipse(olc::LineBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, int32_t nFacets) { // Fundamental for batched outline circle/ellipse with colour solid/gradient @@ -14860,17 +14638,17 @@ const LineBatch& olc::Draw2D::Ellipse(olc::LineBatch& batch, const olc::vf2d& po return batch; } -const GPUTask& olc::Draw2D::FilledEllipse(const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) +const GPUTask& olc::Draw::FilledEllipse(const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { return FilledEllipse(pos, rx, ry, col, col, tint, nFacets); } -const FilledBatch& olc::Draw2D::FilledEllipse(olc::FilledBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, int32_t nFacets) +const FilledBatch& olc::Draw::FilledEllipse(olc::FilledBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, int32_t nFacets) { return FilledEllipse(batch, pos, rx, ry, col, col, nFacets); } -const GPUTask& olc::Draw2D::FilledEllipse(const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel colInner, const olc::Pixel colOuter, const olc::Pixel tint, int32_t nFacets) +const GPUTask& olc::Draw::FilledEllipse(const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel colInner, const olc::Pixel colOuter, const olc::Pixel tint, int32_t nFacets) { // Fundamental for filled circle/ellipse with colour solid/gradient @@ -14899,7 +14677,7 @@ const GPUTask& olc::Draw2D::FilledEllipse(const olc::vf2d& pos, const float& rx, ))); } -const FilledBatch& olc::Draw2D::FilledEllipse(olc::FilledBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel colInner, const olc::Pixel colOuter, int32_t nFacets) +const FilledBatch& olc::Draw::FilledEllipse(olc::FilledBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel colInner, const olc::Pixel colOuter, int32_t nFacets) { // Fundamental for batch filled circle/ellipse with colour solid/gradient @@ -14920,7 +14698,7 @@ const FilledBatch& olc::Draw2D::FilledEllipse(olc::FilledBatch& batch, const olc return batch; } -const GPUTask& olc::Draw2D::RoundedRect(const olc::vf2d& pos, const olc::vf2d& size, const float& radius, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) +const GPUTask& olc::Draw::RoundedRect(const olc::vf2d& pos, const olc::vf2d& size, const float& radius, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { PrepareTargetForHW(); @@ -14970,7 +14748,7 @@ const GPUTask& olc::Draw2D::RoundedRect(const olc::vf2d& pos, const olc::vf2d& s ))); } -const LineBatch& olc::Draw2D::RoundedRect(olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const float& radius, const olc::Pixel col, int32_t nFacets) +const LineBatch& olc::Draw::RoundedRect(olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const float& radius, const olc::Pixel col, int32_t nFacets) { buffPoints.reserve((nFacets + 1) * 4 + 1); buffPoints.data.clear(); @@ -15016,7 +14794,7 @@ const LineBatch& olc::Draw2D::RoundedRect(olc::LineBatch& batch, const olc::vf2d return batch; } -const GPUTask& olc::Draw2D::FilledRoundedRect(const olc::vf2d& pos, const olc::vf2d& size, const float& radius, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) +const GPUTask& olc::Draw::FilledRoundedRect(const olc::vf2d& pos, const olc::vf2d& size, const float& radius, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { PrepareTargetForHW(); @@ -15068,17 +14846,17 @@ const GPUTask& olc::Draw2D::FilledRoundedRect(const olc::vf2d& pos, const olc::v -const GPUTask& olc::Draw2D::Triangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col, const olc::Pixel tint) +const GPUTask& olc::Draw::Triangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col, const olc::Pixel tint) { return Triangle(p1, p2, p3, col, col, col, tint); } -const LineBatch& olc::Draw2D::Triangle(olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col) +const LineBatch& olc::Draw::Triangle(olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col) { return Triangle(batch, p1, p2, p3, col, col, col); } -const GPUTask& olc::Draw2D::Triangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3, const olc::Pixel tint) +const GPUTask& olc::Draw::Triangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3, const olc::Pixel tint) { PrepareTargetForHW(); @@ -15091,7 +14869,7 @@ const GPUTask& olc::Draw2D::Triangle(const olc::vf2d& p1, const olc::vf2d& p2, c ))); } -const LineBatch& olc::Draw2D::Triangle(olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3) +const LineBatch& olc::Draw::Triangle(olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3) { Line(batch, p1, c1, p2, c2); Line(batch, p2, c2, p3, c3); @@ -15099,17 +14877,17 @@ const LineBatch& olc::Draw2D::Triangle(olc::LineBatch& batch, const olc::vf2d& p return batch; } -const GPUTask& olc::Draw2D::FilledTriangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col, const olc::Pixel tint) +const GPUTask& olc::Draw::FilledTriangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col, const olc::Pixel tint) { return FilledTriangle(p1, p2, p3, col, col, col, tint); } -const FilledBatch& olc::Draw2D::FilledTriangle(olc::FilledBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col) +const FilledBatch& olc::Draw::FilledTriangle(olc::FilledBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col) { return FilledTriangle(batch, p1, p2, p3, col, col, col); } -const GPUTask& olc::Draw2D::FilledTriangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3, const olc::Pixel tint) +const GPUTask& olc::Draw::FilledTriangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3, const olc::Pixel tint) { PrepareTargetForHW(); @@ -15122,7 +14900,7 @@ const GPUTask& olc::Draw2D::FilledTriangle(const olc::vf2d& p1, const olc::vf2d& ))); } -const FilledBatch& olc::Draw2D::FilledTriangle(olc::FilledBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3) +const FilledBatch& olc::Draw::FilledTriangle(olc::FilledBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3) { batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + 3); size_t idx = batch.task.vertexBuffer.size() - 3; @@ -15135,7 +14913,7 @@ const FilledBatch& olc::Draw2D::FilledTriangle(olc::FilledBatch& batch, const ol return batch; } -const GPUTask& olc::Draw2D::TexturedTriangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3, const olc::vf2d& t1, const olc::vf2d& t2, const olc::vf2d& t3, olc::Image& texture, const olc::Pixel tint) +const GPUTask& olc::Draw::TexturedTriangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3, const olc::vf2d& t1, const olc::vf2d& t2, const olc::vf2d& t3, olc::Image& texture, const olc::Pixel tint) { PrepareTargetForHW(); PrepareImageForHW(texture); @@ -15151,12 +14929,12 @@ const GPUTask& olc::Draw2D::TexturedTriangle(const olc::vf2d& p1, const olc::vf2 ))); } -const GPUTask& olc::Draw2D::Polygon(const std::vector& vecPoints, const olc::Pixel col, const olc::Pixel tint) +const GPUTask& olc::Draw::Polygon(const std::vector& vecPoints, const olc::Pixel col, const olc::Pixel tint) { return Polygon(olc::Structure::LineLoop, vecPoints, std::vector(vecPoints.size(), col), tint); } -const LineBatch& olc::Draw2D::Polygon(olc::LineBatch& batch, const std::vector& vecPoints, const olc::Pixel col) +const LineBatch& olc::Draw::Polygon(olc::LineBatch& batch, const std::vector& vecPoints, const olc::Pixel col) { for (size_t i = 0; i < vecPoints.size(); i++) { @@ -15166,12 +14944,12 @@ const LineBatch& olc::Draw2D::Polygon(olc::LineBatch& batch, const std::vector& vecPoints, const std::vector& vecColours, const olc::Pixel tint) +const GPUTask& olc::Draw::Polygon(const std::vector& vecPoints, const std::vector& vecColours, const olc::Pixel tint) { return Polygon(olc::Structure::LineLoop, vecPoints, vecColours, tint); } -const LineBatch& olc::Draw2D::Polygon(olc::LineBatch& batch, const std::vector& vecPoints, const std::vector& vecColours) +const LineBatch& olc::Draw::Polygon(olc::LineBatch& batch, const std::vector& vecPoints, const std::vector& vecColours) { for(size_t i = 0; i& vecPoints, const olc::Pixel col, const olc::Pixel tint) +const GPUTask& olc::Draw::Polygon(const olc::Structure structure, const std::vector& vecPoints, const olc::Pixel col, const olc::Pixel tint) { return Polygon(structure, vecPoints, std::vector(vecPoints.size(), col), tint); } -const GPUTask& olc::Draw2D::Polygon(const olc::Structure structure, const std::vector& vecPoints, const std::vector& vecColours, const olc::Pixel tint) +const GPUTask& olc::Draw::Polygon(const olc::Structure structure, const std::vector& vecPoints, const std::vector& vecColours, const olc::Pixel tint) { PrepareTargetForHW(); @@ -15199,12 +14977,12 @@ const GPUTask& olc::Draw2D::Polygon(const olc::Structure structure, const std::v ))); } -const GPUTask& olc::Draw2D::FilledPolygon(const olc::Structure structure, const std::vector& vecPoints, const olc::Pixel col, const olc::Pixel tint) +const GPUTask& olc::Draw::FilledPolygon(const olc::Structure structure, const std::vector& vecPoints, const olc::Pixel col, const olc::Pixel tint) { return FilledPolygon(structure, vecPoints, std::vector(vecPoints.size(), col), tint); } -const GPUTask& olc::Draw2D::FilledPolygon(const olc::Structure structure, const std::vector& vecPoints, const std::vector& vecColours, const olc::Pixel tint) +const GPUTask& olc::Draw::FilledPolygon(const olc::Structure structure, const std::vector& vecPoints, const std::vector& vecColours, const olc::Pixel tint) { PrepareTargetForHW(); @@ -15217,7 +14995,7 @@ const GPUTask& olc::Draw2D::FilledPolygon(const olc::Structure structure, const ))); } -const GPUTask& olc::Draw2D::TexturedPolygon(const olc::Structure structure, const std::vector& vecPoints, const std::vector& vecColours, const std::vector& vecTexCoords, olc::Image& texture, const olc::Pixel tint) +const GPUTask& olc::Draw::TexturedPolygon(const olc::Structure structure, const std::vector& vecPoints, const std::vector& vecColours, const std::vector& vecTexCoords, olc::Image& texture, const olc::Pixel tint) { PrepareTargetForHW(); PrepareImageForHW(texture); @@ -15233,7 +15011,7 @@ const GPUTask& olc::Draw2D::TexturedPolygon(const olc::Structure structure, cons ))); } -const GPUTask& olc::Draw2D::String(const olc::vf2d& pos, const std::string& text, const olc::Pixel col, const olc::vf2d& scale, olc::Font& font) +const GPUTask& olc::Draw::String(const olc::vf2d& pos, const std::string& text, const olc::Pixel col, const olc::vf2d& scale, olc::Font& font) { PrepareTargetForHW(); @@ -15256,7 +15034,7 @@ const GPUTask& olc::Draw2D::String(const olc::vf2d& pos, const std::string& text } else { - Draw2D::Image(task, font.glyphs[c].imgGlyph, pos + spos + olc::vf2d{ glyph.spacing * scale.x, 0.0f }, scale, col); + Draw::Image(task, font.glyphs[c].imgGlyph, pos + spos + olc::vf2d{ glyph.spacing * scale.x, 0.0f }, scale, col); spos.x += glyph.vMonoSize.x * scale.x; } } @@ -15264,7 +15042,7 @@ const GPUTask& olc::Draw2D::String(const olc::vf2d& pos, const std::string& text return Batch(task); } -const GPUTask& olc::Draw2D::StringProp(const olc::vf2d& pos, const std::string& text, const olc::Pixel col, const olc::vf2d& scale, olc::Font& font) +const GPUTask& olc::Draw::StringProp(const olc::vf2d& pos, const std::string& text, const olc::Pixel col, const olc::vf2d& scale, olc::Font& font) { PrepareTargetForHW(); @@ -15286,7 +15064,7 @@ const GPUTask& olc::Draw2D::StringProp(const olc::vf2d& pos, const std::string& } else { - Draw2D::Image(task, font.glyphs[c].imgGlyph, pos + spos, scale, col); + Draw::Image(task, font.glyphs[c].imgGlyph, pos + spos, scale, col); spos.x += glyph.vPropSize.x * scale.x; } } @@ -15294,7 +15072,7 @@ const GPUTask& olc::Draw2D::StringProp(const olc::vf2d& pos, const std::string& return Batch(task); } -olc::vf2d olc::Draw2D::GetTextSize(const std::string& text, const bool bProportional, const olc::vf2d& scale, olc::Font& font) +olc::vf2d olc::Draw::GetTextSize(const std::string& text, const bool bProportional, const olc::vf2d& scale, olc::Font& font) { olc::vf2d size = { 0, font.fLineHeight * scale.y }; olc::vf2d pos = { 0, font.fLineHeight * scale.y }; @@ -15327,50 +15105,92 @@ olc::vf2d olc::Draw2D::GetTextSize(const std::string& text, const bool bProporti return size; } -ImageBatch olc::Draw2D::CreateImageBatch(olc::Image &image) +GPUTask& olc::Draw::Line(const olc::vf4d& vStart, const olc::vf4d& vEnd, const olc::Pixel& col, const olc::Pixel tint) { - PrepareImageForHW(image); PrepareTargetForHW(); - ImageBatch b; - b.task.structure = olc::Structure::List; - b.task.pImage = ℑ - return b; + return vecGPUTasks.data.emplace_back(std::move( + TaskWireMesh( + olc::Structure::Line, + { vStart, vEnd }, + { col, col }, + tint + ))); } -const GPUTask& olc::Draw2D::Batch(olc::ImageBatch& batch, const olc::Pixel tint) +GPUTask& olc::Draw::Mesh(const olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) { - batch.task.tint = tint; - return vecGPUTasks.data.emplace_back(batch.task); + PrepareTargetForHW(); + + return vecGPUTasks.data.emplace_back(std::move( + TaskWireMesh( + structure, + vPoints, + vColours, + tint + ))); } -FilledBatch olc::Draw2D::CreateFilledBatch() +GPUTask& olc::Draw::Mesh(const olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const std::vector& vUVs, olc::Image& texture, const olc::Pixel tint) { - FilledBatch b; + PrepareImageForHW(texture); + PrepareTargetForHW(); + + return vecGPUTasks.data.emplace_back(std::move( + TaskTexturedMesh( + structure, + vPoints, + vColours, + vUVs, + &texture, + tint + ))); +} + +ImageBatch olc::Draw::CreateImageBatch(olc::Image &image) +{ + PrepareImageForHW(image); + PrepareTargetForHW(); + + ImageBatch b; b.task.structure = olc::Structure::List; + b.task.pImage = ℑ return b; } -const GPUTask& olc::Draw2D::Batch(olc::FilledBatch& batch, const olc::Pixel tint) +const GPUTask& olc::Draw::Batch(olc::ImageBatch& batch, const olc::Pixel tint) { batch.task.tint = tint; return vecGPUTasks.data.emplace_back(batch.task); } -LineBatch olc::Draw2D::CreateLineBatch() +FilledBatch olc::Draw::CreateFilledBatch() +{ + FilledBatch b; + b.task.structure = olc::Structure::List; + return b; +} + +const GPUTask& olc::Draw::Batch(olc::FilledBatch& batch, const olc::Pixel tint) +{ + batch.task.tint = tint; + return vecGPUTasks.data.emplace_back(batch.task); +} + +LineBatch olc::Draw::CreateLineBatch() { LineBatch b; b.task.structure = olc::Structure::LineList; return b; } -const GPUTask& olc::Draw2D::Batch(olc::LineBatch& batch, const olc::Pixel tint) +const GPUTask& olc::Draw::Batch(olc::LineBatch& batch, const olc::Pixel tint) { batch.task.tint = tint; return vecGPUTasks.data.emplace_back(batch.task); } -const ImageBatch& olc::Draw2D::Image(ImageBatch& batch, olc::ImageRegion image, const olc::vf2d& pos, const olc::vf2d& scale, const olc::Pixel tint) +const ImageBatch& olc::Draw::Image(ImageBatch& batch, olc::ImageRegion image, const olc::vf2d& pos, const olc::vf2d& scale, const olc::Pixel tint) { // Add quad to existing task olc::vf2d size = image.regionsize * scale; @@ -15390,7 +15210,7 @@ const ImageBatch& olc::Draw2D::Image(ImageBatch& batch, olc::ImageRegion image, return batch; } -const GPUTask& olc::Draw2D::Image(olc::ImageRegion image, const olc::vf2d& pos, const olc::vf2d& scale, const olc::Pixel tint) +const GPUTask& olc::Draw::Image(olc::ImageRegion image, const olc::vf2d& pos, const olc::vf2d& scale, const olc::Pixel tint) { // Ensure source image is up to date in VRAM PrepareImageForHW(image.image); @@ -15421,7 +15241,7 @@ const GPUTask& olc::Draw2D::Image(olc::ImageRegion image, const olc::vf2d& pos, } -const GPUTask& olc::Draw2D::ImageRotated(olc::ImageRegion image, const olc::vf2d& pos, const float theta, const olc::vf2d& center, const olc::vf2d& scale, const olc::Pixel tint) +const GPUTask& olc::Draw::ImageRotated(olc::ImageRegion image, const olc::vf2d& pos, const float theta, const olc::vf2d& center, const olc::vf2d& scale, const olc::Pixel tint) { // Ensure source image is up to date in VRAM PrepareImageForHW(image.image); @@ -15449,7 +15269,7 @@ const GPUTask& olc::Draw2D::ImageRotated(olc::ImageRegion image, const olc::vf2d ))); } -const ImageBatch& olc::Draw2D::ImageRotated(olc::ImageBatch& batch, olc::ImageRegion image, const olc::vf2d& pos, const float theta, const olc::vf2d& center, const olc::vf2d& scale, const olc::Pixel tint) +const ImageBatch& olc::Draw::ImageRotated(olc::ImageBatch& batch, olc::ImageRegion image, const olc::vf2d& pos, const float theta, const olc::vf2d& center, const olc::vf2d& scale, const olc::Pixel tint) { // Add quad to existing task olc::vf2d size = image.regionsize * scale; @@ -15479,7 +15299,7 @@ const ImageBatch& olc::Draw2D::ImageRotated(olc::ImageBatch& batch, olc::ImageRe return batch; } -const GPUTask& olc::Draw2D::ImageQuad(olc::ImageRegion image, const olc::vf2d& vTL, const olc::vf2d& vTR, const olc::vf2d& vBR, const olc::vf2d& vBL, const olc::Pixel tint) +const GPUTask& olc::Draw::ImageQuad(olc::ImageRegion image, const olc::vf2d& vTL, const olc::vf2d& vTR, const olc::vf2d& vBR, const olc::vf2d& vBL, const olc::Pixel tint) { // Ensure source image is up to date in VRAM PrepareImageForHW(image.image); @@ -15534,7 +15354,7 @@ const GPUTask& olc::Draw2D::ImageQuad(olc::ImageRegion image, const olc::vf2d& v ))); } -const ImageBatch& olc::Draw2D::ImageQuad(olc::ImageBatch& batch, olc::ImageRegion image, const olc::vf2d& vTL, const olc::vf2d& vTR, const olc::vf2d& vBR, const olc::vf2d& vBL, const olc::Pixel tint) +const ImageBatch& olc::Draw::ImageQuad(olc::ImageBatch& batch, olc::ImageRegion image, const olc::vf2d& vTL, const olc::vf2d& vTR, const olc::vf2d& vBR, const olc::vf2d& vBL, const olc::Pixel tint) { float rd = ((vBR.x - vTL.x) * (vTR.y - vBL.y) - (vTR.x - vBL.x) * (vBR.y - vTL.y)); if (rd != 0) @@ -15577,21 +15397,21 @@ const ImageBatch& olc::Draw2D::ImageQuad(olc::ImageBatch& batch, olc::ImageRegio } // Default is just return a textured quad - return Draw2D::Image(batch, image, vTL, vBR - vTL, tint); + return Draw::Image(batch, image, vTL, vBR - vTL, tint); } -const GPUTask& olc::Draw2D::ImageQuad(olc::ImageRegion image, const std::vector& vecPoints, const olc::Pixel tint) +const GPUTask& olc::Draw::ImageQuad(olc::ImageRegion image, const std::vector& vecPoints, const olc::Pixel tint) { return ImageQuad(image, vecPoints[0], vecPoints[1], vecPoints[2], vecPoints[3], tint); } -const ImageBatch& olc::Draw2D::ImageQuad(olc::ImageBatch& batch, olc::ImageRegion image, const std::vector& vecPoints, const olc::Pixel tint) +const ImageBatch& olc::Draw::ImageQuad(olc::ImageBatch& batch, olc::ImageRegion image, const std::vector& vecPoints, const olc::Pixel tint) { return ImageQuad(batch, image, vecPoints[0], vecPoints[1], vecPoints[2], vecPoints[3], tint); } -const GPUTask& olc::Draw2D::ImageRect(olc::ImageRegion image, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel tint) +const GPUTask& olc::Draw::ImageRect(olc::ImageRegion image, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel tint) { // Ensure source image is up to date in VRAM PrepareImageForHW(image.image); @@ -15609,813 +15429,88 @@ const GPUTask& olc::Draw2D::ImageRect(olc::ImageRegion image, const olc::vf2d& p ))); } -const ImageBatch& olc::Draw2D::ImageRect(olc::ImageBatch& batch, olc::ImageRegion image, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel tint) +const ImageBatch& olc::Draw::ImageRect(olc::ImageBatch& batch, olc::ImageRegion image, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel tint) { olc_IgnoreUnused(image, pos, size, tint); // TODO: Implement this function return batch; } - -// This is all essentially the olc::PixelGameEngine 2 rasteriser code -using namespace olc; - - -void Draw2D::swLine(const olc::vf2d& p1, const olc::vf2d& p2, const olc::Pixel col) -{ - swLine(p1, p2, col, col); -} - -void Draw2D::swLine(const olc::vf2d& p1, const olc::vf2d& p2, const olc::Pixel c1, const olc::Pixel c2) -{ - const auto vTransformedPoints = transformAffine.forward({ p1, p2 }); - swRasterShadedLine( - vTransformedPoints[0], - vTransformedPoints[1], - c1, c2); -} - -void olc::Draw2D::swRect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) -{ - swRect(pos, size, col, col, col, col); -} - -void olc::Draw2D::swRect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR) -{ - swLine(pos, { pos.x + size.x, pos.y }, colTL, colTR); - swLine({ pos.x + size.x, pos.y }, { pos.x + size.x, pos.y + size.y }, colTR, colBR); - swLine({ pos.x + size.x, pos.y + size.y }, { pos.x, pos.y + size.y }, colBR, colBL); - swLine({ pos.x, pos.y + size.y }, pos, colBL, colTL); -} - -void olc::Draw2D::swFilledRect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) +void olc::Draw::SetCullMode(const olc::GPUTask::CullMode mode) { - swFilledRect(pos, size, col, col, col, col); -} - -void olc::Draw2D::swFilledRect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR) -{ - const auto vTransformedPoints = transformAffine.forward({pos, {pos.x + size.x, pos.y}, pos + size, {pos.x, pos.y + size.y}}); - - // Most draws will be single colour, axis aligned rectangle. - // Optimise for that case first - - // Check if all one colour - if (colTL == colBL && colTL == colTR && colTL == colBR) - { - // Check if axis aligned - if(vTransformedPoints[0].y == vTransformedPoints[1].y && - vTransformedPoints[1].x == vTransformedPoints[2].x && - vTransformedPoints[2].y == vTransformedPoints[3].y && - vTransformedPoints[3].x == vTransformedPoints[0].x) - { - PrepareTargetForSW(); - - // Clip to target - olc::vi2d p1 = vTransformedPoints[0].max({ 0,0 }); - olc::vi2d p2 = vTransformedPoints[2].min(pTarget->Size()); - - // Draw filled rectangle - for (int32_t y = p1.y; y < p2.y; y++) - for (int32_t x = p1.x; x < p2.x; x++) - pTarget->Pixel({ x, y }) = colTL; - - // Exit early - return; - } - } - - // Fallback to general case rasteriser, where we split into two triangles - swRasterShadedTriangle( - vTransformedPoints[0], - vTransformedPoints[1], - vTransformedPoints[2], - colTL, colTR, colBR); - swRasterShadedTriangle( - vTransformedPoints[0], - vTransformedPoints[2], - vTransformedPoints[3], - colTL, colBR, colBL); -} - -void olc::Draw2D::swTriangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col) -{ - swTriangle(p1, p2, p3, col, col, col); -} - -void olc::Draw2D::swTriangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3) -{ - swLine(p1, p2, c1, c2); - swLine(p2, p3, c2, c3); - swLine(p3, p1, c3, c1); -} - -void olc::Draw2D::swFilledTriangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col) -{ - swFilledTriangle(p1, p2, p3, col, col, col); -} - -void olc::Draw2D::swFilledTriangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3) -{ - const auto vTransformedPoints = transformAffine.forward({ p1, p2, p3 }); - swRasterShadedTriangle( - vTransformedPoints[0], - vTransformedPoints[1], - vTransformedPoints[2], - c1, c2, c3); -} - -void olc::Draw2D::swTexturedTriangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3, const olc::vf2d& t1, const olc::vf2d& t2, const olc::vf2d& t3, olc::Image& texture) -{ - const auto vTransformedPoints = transformAffine.forward({ p1, p2, p3 }); - swRasterTexturedTriangle( - vTransformedPoints[0], - vTransformedPoints[1], - vTransformedPoints[2], - c1, c2, c3, - t1, t2, t3, - texture); -} - -bool olc::Draw2D::swClipLine(olc::vf2d& p1, olc::vf2d& p2, const olc::vf2d& vMin, const olc::vf2d& vMax) -{ - // https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm - static constexpr int SEG_I = 0b0000, SEG_L = 0b0001, SEG_R = 0b0010, SEG_B = 0b0100, SEG_T = 0b1000; - auto Segment = [&vMin = vMin, &vMax = vMax](const olc::vf2d& v) - { - int i = SEG_I; - if (v.x < vMin.x) i |= SEG_L; else if (v.x > vMax.x) i |= SEG_R; - if (v.y < vMin.y) i |= SEG_B; else if (v.y > vMax.y) i |= SEG_T; - return i; - }; - - int s1 = Segment(p1), s2 = Segment(p2); - - while (true) - { - if (!(s1 | s2)) return true; - else if (s1 & s2) return false; - else - { - int s3 = s2 > s1 ? s2 : s1; - olc::vf2d n; - if (s3 & SEG_T) { n.x = p1.x + (p2.x - p1.x) * (vMax.y - p1.y) / (p2.y - p1.y); n.y = vMax.y; } - else if (s3 & SEG_B) { n.x = p1.x + (p2.x - p1.x) * (vMin.y - p1.y) / (p2.y - p1.y); n.y = vMin.y; } - else if (s3 & SEG_R) { n.x = vMax.x; n.y = p1.y + (p2.y - p1.y) * (vMax.x - p1.x) / (p2.x - p1.x); } - else if (s3 & SEG_L) { n.x = vMin.x; n.y = p1.y + (p2.y - p1.y) * (vMin.x - p1.x) / (p2.x - p1.x); } - if (s3 == s1) { p1 = n; s1 = Segment(p1); } - else { p2 = n; s2 = Segment(p2); } - } - } - return true; -} - -bool olc::Draw2D::swClipWeightedLine(olc::vf2d& v0, olc::vf2d& v1, const olc::vf2d& vMin, const olc::vf2d& vMax, float& w0, float& w1) -{ - // Liang-Barsky line clipping algorithm adapted for weighted lines - // https://en.wikipedia.org/wiki/Liang%E2%80%93Barsky_algorithm - - olc::vf2d diff = v1 - v0; - - float p[4] = { -diff.x, diff.x, -diff.y, diff.y }; - - float q[4] = - { - v0.x - vMin.x, - vMax.x - v0.x, - v0.y - vMin.y, - vMax.y - v0.y - }; - - // Weights are ideal to start, we'll contact them as we clip - w0 = 0.0f; - w1 = 1.0f; - - for (int i = 0; i < 4; i++) - { - if (p[i] == 0.0f) - { - if (q[i] < 0.0f) - return false; // Line is parallel and outside the clipping boundary - } - else - { - float t = float(q[i]) / float(p[i]); - if (p[i] < 0.0f) - { - if (t > w1) - return false; // Line is outside the clipping boundary - else if (t > w0) - w0 = t; - } - else - { - if (t < w0) - return false; // Line is outside the clipping boundary - else if (t < w1) - w1 = t; - } - } - } - - if (w1 < w0) - return false; // Line is outside the clipping boundary - - // Return new line segment ends - olc::vf2d v = v0; - v0 = v + (diff * w0); - v1 = v + (diff * w1); - - // Line has visible pixels inside clipping boundary - return true; -} - -std::pair olc::Draw2D::swBaryFillTriangle(const olc::vi2d& v1, const olc::vi2d& v2, const olc::vi2d& v3) -{ - // Get height of triangle in whole pixels - int32_t nMinY = std::min({ v1.y, v2.y, v3.y }); - int32_t nMaxY = std::max({ v1.y, v2.y, v3.y }); - int32_t nHeight = nMaxY - nMinY; - - if (nHeight <= 0) - return { 0, 0 }; // Degenerate triangle - - // Scanline buffer is already allocated to be the max vertical size - // of the draw target. Obviously it only represents visible scanlines - // that are to be filled for the current triangle. - - // Get visible height of triangle - int32_t y_min = std::max(0, nMinY); - int32_t y_max = std::min(nMaxY, pTarget->Size().y); - - // Zero out scanline buffer (by resetting min and max values) - for (int32_t y = y_min; y < y_max; y++) - { - vScanlines[y].nMin = std::numeric_limits::max(); - vScanlines[y].nMax = std::numeric_limits::min(); - } - - // This function scans an edge of the triangle, updating - // the scanline buffer with min/max extents and barycentric coords. - // It returns the number of scanlines updated. - auto scanEdge = [&](olc::vi2d p0, olc::vi2d p1, int id1, int id2) -> size_t - { - if (p0.y == p1.y) - return 0; - - // Ensure p0.y < p1.y - bool swapped = false; - if (p0.y > p1.y) - { - std::swap(p0, p1); - swapped = true; - } - - // Cache edge step deltas - int dy = p1.y - p0.y; - float dx_step = (p1.x - p0.x) / float(dy); - float dy_step = 1.0f / float(dy); - float x = float(p0.x); - - // Rasterise edge - if pixel lies on visible scanline then - // update the scanline bounds and barycentric coords - size_t nScanline = 0; - for (int y = p0.y; y <= p1.y; y++) - { - // If this pixel row is visible - if (y >= 0 && y < vScanlines.size()) - { - int ix = int(std::round(x)); - - // interpolation along edge - // Note: We may need to do this differently when clipping - float t = (y - p0.y) * dy_step; - - std::array bary = { 0.0f, 0.0f, 0.0f }; - - // Set barycentric coords depending on edge direction - if (swapped) - { - bary[id1] = t; - bary[id2] = 1.0f - t; - } - else - { - bary[id1] = 1.0f - t; - bary[id2] = t; - } - - // Update scanline extents and barycentric coords - if (ix < vScanlines[y].nMin) - { - vScanlines[y].nMin = ix; - vScanlines[y].fBaryMin = bary; - } - - if (ix > vScanlines[y].nMax) - { - vScanlines[y].nMax = ix; - vScanlines[y].fBaryMax = bary; - } - - nScanline++; - } - - x += dx_step; - } - - return nScanline; - }; - - // Rasterise triangle edges into scanline buffer - scanEdge(v1, v2, 0, 1); - scanEdge(v1, v3, 0, 2); - scanEdge(v2, v3, 1, 2); - - return { y_min, y_max }; -} - -void olc::Draw2D::swRasterShadedTriangle(const olc::vi2d& v1, const olc::vi2d& v2, const olc::vi2d& v3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3) -{ - // We are writing to the target image, so make sure its memory resident (and up to date) - PrepareTargetForSW(); - - auto [y_min, y_max] = swBaryFillTriangle(v1, v2, v3); - - // Now draw the scanlines - for (int32_t y = y_min; y < y_max; y++) - { - const auto& scanline = vScanlines[y]; - - int32_t xStart = scanline.nMin; - int32_t xEnd = scanline.nMax; - - int32_t x_min = std::max(0, xStart); - int32_t x_max = std::min(xEnd, pTarget->Size().x); - - float fSpan = float(xEnd - xStart); - float fSpanStep = fSpan > 0.0f ? 1.0f / fSpan : 0.0f; - - float b0_step = fSpanStep * (scanline.fBaryMax[0] - scanline.fBaryMin[0]); - float b1_step = fSpanStep * (scanline.fBaryMax[1] - scanline.fBaryMin[1]); - float b2_step = fSpanStep * (scanline.fBaryMax[2] - scanline.fBaryMin[2]); - - float b0 = scanline.fBaryMin[0]; - float b1 = scanline.fBaryMin[1]; - float b2 = scanline.fBaryMin[2]; - - if (xStart < 0) - { - b0 = scanline.fBaryMin[0] + (-xStart * b0_step); - b1 = scanline.fBaryMin[1] + (-xStart * b1_step); - b2 = scanline.fBaryMin[2] + (-xStart * b2_step); - } - - for (int32_t x = x_min; x < x_max; x++) - { - olc::Pixel col = olc::Pixel( - uint8_t(c1.r * b0 + c2.r * b1 + c3.r * b2), - uint8_t(c1.g * b0 + c2.g * b1 + c3.g * b2), - uint8_t(c1.b * b0 + c2.b * b1 + c3.b * b2), - uint8_t(c1.a * b0 + c2.a * b1 + c3.a * b2)); - - // In theory, target (x,y) is always valid here due to clipping above - pTarget->Pixel({ x, y }) = col; - - b0 += b0_step; - b1 += b1_step; - b2 += b2_step; - } - } - - - return; -} - -void olc::Draw2D::swRasterTexturedTriangle(const olc::vi2d& v1, const olc::vi2d& v2, const olc::vi2d& v3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3, const olc::vf2d& t1, const olc::vf2d& t2, const olc::vf2d& t3, olc::Image& texture) -{ - // We are writing to the target image, so make sure its memory resident (and up to date) - PrepareTargetForSW(); - PrepareImageForSW(texture); - - auto [y_min, y_max] = swBaryFillTriangle(v1, v2, v3); - - // Now draw the scanlines - for (int32_t y = y_min; y < y_max; y++) - { - const auto& scanline = vScanlines[y]; - - int32_t xStart = scanline.nMin; - int32_t xEnd = scanline.nMax; - - int32_t x_min = std::max(0, xStart); - int32_t x_max = std::min(xEnd, pTarget->Size().x); - - float fSpan = float(xEnd - xStart); - float fSpanStep = fSpan > 0.0f ? 1.0f / fSpan : 0.0f; - - float b0_step = fSpanStep * (scanline.fBaryMax[0] - scanline.fBaryMin[0]); - float b1_step = fSpanStep * (scanline.fBaryMax[1] - scanline.fBaryMin[1]); - float b2_step = fSpanStep * (scanline.fBaryMax[2] - scanline.fBaryMin[2]); - - float b0 = scanline.fBaryMin[0]; - float b1 = scanline.fBaryMin[1]; - float b2 = scanline.fBaryMin[2]; - - if(xStart < 0) - { - b0 = scanline.fBaryMin[0] + (-xStart * b0_step); - b1 = scanline.fBaryMin[1] + (-xStart * b1_step); - b2 = scanline.fBaryMin[2] + (-xStart * b2_step); - } - - for (int32_t x = x_min; x < x_max; x++) - { - olc::Pixel col = olc::Pixel( - uint8_t(c1.r * b0 + c2.r * b1 + c3.r * b2), - uint8_t(c1.g * b0 + c2.g * b1 + c3.g * b2), - uint8_t(c1.b * b0 + c2.b * b1 + c3.b * b2), - uint8_t(c1.a * b0 + c2.a * b1 + c3.a * b2)); - - olc::vf2d uv = olc::vf2d( - b0 * t1.x + b1 * t2.x + b2 * t3.x, - b0 * t1.y + b1 * t2.y + b2 * t3.y); - - - // In theory, target (x,y) is always valid here due to clipping above - pTarget->Pixel({ x, y }) = col.blend(texture.Sample(uv)); - - b0 += b0_step; - b1 += b1_step; - b2 += b2_step; - } - } - - return; -} - - - -void olc::Draw2D::swRasterShadedLine(const olc::vi2d& v1, const olc::vi2d& v2, const olc::Pixel c1, const olc::Pixel c2) -{ - PrepareTargetForSW(); - - // Lambda to draw a pixel gated by a pattern bit - uint32_t pattern = 0xFFFFFFFF; - auto rol = [&](void) - { - pattern = (pattern << 1) | (pattern >> 31); - return pattern & 1; - }; - - // Lambda to draw a pixel at integer location - auto Plot = [&](int32_t x, int32_t y, const olc::Pixel& p) - { - if (x >= 0 && x < pTarget->Size().x && y >= 0 && y < pTarget->Size().y) - pTarget->Pixel({ x, y }) = p; - }; - - // Clip line to draw target - olc::vf2d clipped_p1 = v1; - olc::vf2d clipped_p2 = v2; - - // If line is completely outside bounds, exit - //if (!swClipLine(clipped_p1, clipped_p2, { 0,0 }, pTarget->Size())) - //return; - - float w0=0, w1=1; - if (!swClipWeightedLine(clipped_p1, clipped_p2, { 0,0 }, pTarget->Size(), w0, w1)) - return; - - // Move to integer space - olc::vi2d ip1 = clipped_p1; - olc::vi2d ip2 = clipped_p2; - olc::vi2d pixel; - - // Calculate deltas - int dx = ip2.x - ip1.x; - int dy = ip2.y - ip1.y; - int absDx = std::abs(dx); - int absDy = std::abs(dy); - - // Determine dominant axis - bool xMajor = absDx >= absDy; - int steps = xMajor ? absDx : absDy; - - // Handle degenerate case (single pixel) - if (steps == 0) - { - Plot(ip1.x, ip1.y, c1); - return; - } - - // Calculate step increments - float xStep = float(dx) / float(steps); - float yStep = float(dy) / float(steps); - float colorStep = 1.0f / float(steps) * (w1 - w0); - - olc::Pixel cStart = olc::PixelLerp(c1, c2, w0); - olc::Pixel cEnd = olc::PixelLerp(c1, c2, w1); - - // Starting position and color interpolation parameter - float x = float(ip1.x); - float y = float(ip1.y); - float t = 0.0f; - - // Draw line pixel by pixel - for (int i = 0; i <= steps; i++) - { - // Interpolate color - //olc::Pixel col = olc::PixelLerp(c1, c2, t); - olc::Pixel col = olc::PixelLerp(cStart, cEnd, t); - - // Plot pixel - if(rol()) - Plot((int)std::round(x), (int)std::round(y), col); - - // Step to next pixel - x += xStep; - y += yStep; - t += colorStep; - } - - - - return; -} - - - -#define PGE_DRAW2D_IMPLEMENTED 1 -#endif - -#if defined(OLC_PGE3_APPLICATION) && !defined(PGE_DRAW3D_IMPLEMENTED) -thread_local Draw3D::buffer Draw3D::buffPoints; -thread_local Draw3D::buffer Draw3D::buffColours; -thread_local Draw3D::buffer Draw3D::vecGPUTasks; - -olc::Draw3D::Draw3D(olc::Draw2D& d2d) : draw2d(d2d) -{ - MatrixReset(); -} - -void olc::Draw3D::SetGPU(olc::gpu::Renderer* const renderer) -{ - draw2d.SetGPU(renderer); -} - -void olc::Draw3D::ProcessGPUTasks() -{ - draw2d.ProcessGPUTasks(); -} - -void olc::Draw3D::SetTarget(olc::Image& image) -{ - draw2d.SetTarget(image); -} - -olc::Image& olc::Draw3D::GetTarget() -{ - return draw2d.GetTarget(); -} - -olc::vi2d olc::Draw3D::GetTargetSize() -{ - return draw2d.GetTargetSize(); -} - -void olc::Draw3D::SetViewport(const olc::vi2d& pos, const olc::vi2d& size) -{ - draw2d.pRenderer->SetViewport(pos, size); -} - -bool olc::Draw3D::SetShader(const olc::gpu::Shader& shader) -{ - return draw2d.SetShader(shader); -} - -bool olc::Draw3D::ResetShader() -{ - return draw2d.ResetShader(); -} - -bool olc::Draw3D::SetShaderUniform(const std::string& name, const float value) -{ - return draw2d.SetShaderUniform(name, value); -} - -bool olc::Draw3D::SetShaderUniform(const std::string& name, const olc::vf2d& value) -{ - return draw2d.SetShaderUniform(name, value); -} - -bool olc::Draw3D::SetShaderUniform(const std::string& name, const olc::Pixel value) -{ - return draw2d.SetShaderUniform(name, value); -} - -bool olc::Draw3D::SetShaderTexture(const uint32_t nSlot, olc::Image& image) -{ - return draw2d.SetShaderTexture(nSlot, image); -} - -void olc::Draw3D::PrepareTargetForSW() -{ - draw2d.PrepareTargetForSW(); -} - -void olc::Draw3D::PrepareTargetForHW() -{ - draw2d.PrepareTargetForHW(); + cullMode = mode; } -void olc::Draw3D::PrepareImageForSW(olc::Image& image) +void olc::Draw::EnableDepth(const bool bEnable) { - draw2d.PrepareImageForSW(image); + bDepth = bEnable; } -void olc::Draw3D::PrepareImageForHW(olc::Image& image) +void olc::Draw::SetViewport(const olc::vi2d& pos, const olc::vi2d& size) { - draw2d.PrepareImageForHW(image); + pRenderer->SetViewport(pos, size); } - - - - -void olc::Draw3D::MatrixReset() +void olc::Draw::MatrixReset() { matMVP.identity(); - matModel.identity(); + matModel.identity(); matView.identity(); matProjection.identity(); } -void olc::Draw3D::SetModelMatrix(const olc::mf4d& mat) +void olc::Draw::SetModelMatrix(const olc::mf4d& mat) { - matModel = mat; + matModel = mat; matMVP = matVP * matModel; } -const olc::mf4d& olc::Draw3D::GetModelMatrix() const +const olc::mf4d& olc::Draw::GetModelMatrix() const { return matModel; } -void olc::Draw3D::SetViewMatrix(const olc::mf4d& mat) +void olc::Draw::SetViewMatrix(const olc::mf4d& mat) { matView = mat; - matVP = matProjection * matView; + matVP = matProjection * matView; matMVP = matVP * matModel; } -const olc::mf4d& olc::Draw3D::GetViewMatrix() const +const olc::mf4d& olc::Draw::GetViewMatrix() const { return matView; } -void olc::Draw3D::SetProjectionMatrix(const olc::mf4d& mat) +void olc::Draw::SetProjectionMatrix(const olc::mf4d& mat) { matProjection = mat; matVP = matProjection * matView; matMVP = matVP * matModel; } -const olc::mf4d& olc::Draw3D::GetProjectionMatrix() const +const olc::mf4d& olc::Draw::GetProjectionMatrix() const { return matProjection; } -void olc::Draw3D::SetMVPMatrix(const olc::mf4d& mat) +void olc::Draw::SetMVPMatrix(const olc::mf4d& mat) { matMVP = mat; } -const olc::mf4d& olc::Draw3D::GetMVPMatrix() const +const olc::mf4d& olc::Draw::GetMVPMatrix() const { return matMVP; } -void olc::Draw3D::SetCullMode(const olc::GPUTask::CullMode mode) -{ - cullMode = mode; -} - -void olc::Draw3D::EnableDepth(const bool bEnable) -{ - bDepth = bEnable; -} - - - -GPUTask olc::Draw3D::TaskWireMesh(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) -{ - GPUTask task; - task.structure = structure; - task.tint = tint; - task.vertexBuffer.resize(vPoints.size()); - task.bWireframe = true; - task.bDepth = bDepth; - task.cullmode = cullMode; - task.bIs3D = true; - task.mvpMatrix = matMVP.m; - - for (size_t i = 0; i < vPoints.size(); i++) - task.vertexBuffer[i] = { {vPoints[i].x, vPoints[i].y, vPoints[i].z, vPoints[i].w}, vColours[i], {0, 0}, {0, 0}, {0, 0}, {0, 0}}; - return task; -} - -GPUTask olc::Draw3D::TaskFillMesh(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) -{ - GPUTask task; - task.structure = structure; - task.tint = tint; - task.vertexBuffer.resize(vPoints.size()); - task.bDepth = bDepth; - task.cullmode = cullMode; - task.bIs3D = true; - task.mvpMatrix = matMVP.m; - - for (size_t i = 0; i < vPoints.size(); i++) - task.vertexBuffer[i] = { {vPoints[i].x, vPoints[i].y, vPoints[i].z, vPoints[i].w}, vColours[i], {0, 0}, {0, 0}, {0, 0}, {0, 0} }; - return task; -} - -GPUTask olc::Draw3D::TaskTexturedMesh(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const std::vector& vTexCoords, olc::Image* const image, const olc::Pixel tint) -{ - GPUTask task; - task.structure = structure; - task.tint = tint; - task.vertexBuffer.resize(vPoints.size()); - task.pImage = image; - task.bIs3D = true; - task.bDepth = bDepth; - task.cullmode = cullMode; - task.mvpMatrix = matMVP.m; - for (size_t i = 0; i < vPoints.size(); i++) - task.vertexBuffer[i] = { {vPoints[i].x, vPoints[i].y, vPoints[i].z, vPoints[i].w}, vColours[i], {vTexCoords[i].x, vTexCoords[i].y}, {0, 0}, {0, 0}, {0, 0} }; - return task; -} - -void olc::Draw3D::Clear(const olc::Pixel& col) -{ - draw2d.Clear(col); -} - -GPUTask& olc::Draw3D::Line(const olc::vf4d& vStart, const olc::vf4d& vEnd, const olc::Pixel& col, const olc::Pixel tint) -{ - PrepareTargetForHW(); - - return draw2d.vecGPUTasks.data.emplace_back(std::move( - TaskWireMesh( - olc::Structure::Line, - { vStart, vEnd }, - { col, col }, - tint - ))); -} - -GPUTask& olc::Draw3D::Mesh(const olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) -{ - PrepareTargetForHW(); - - return draw2d.vecGPUTasks.data.emplace_back(std::move( - TaskWireMesh( - structure, - vPoints, - vColours, - tint - ))); - -} - -GPUTask& olc::Draw3D::Mesh(const olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const std::vector& vUVs, olc::Image& texture, const olc::Pixel tint) -{ - PrepareImageForHW(texture); - PrepareTargetForHW(); - return draw2d.vecGPUTasks.data.emplace_back(std::move( - TaskTexturedMesh( - structure, - vPoints, - vColours, - vUVs, - &texture, - tint - ))); -} - - - - - - -#define PGE_DRAW3D_IMPLEMENTED 1 +#define PGE_DRAW_IMPLEMENTED 1 #endif #if defined(OLC_PGE3_APPLICATION) && !defined(PGE_CORE_IMPLEMENTED) namespace olc { - PGEWindow::PGEWindow() : Window(), draw(), draw3d(draw) + PGEWindow::PGEWindow() : Window(), draw() { } @@ -16617,7 +15712,7 @@ namespace olc return imgPrimary; } - olc::Draw2D& PGEWindow::GetDraw() + olc::Draw& PGEWindow::GetDraw() { return draw; } From 6fe4a1314e10b8296047e392e836a0d4092bfafc Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Mon, 9 Feb 2026 23:47:26 +0000 Subject: [PATCH 30/58] very rough first pass at extensions --- dev/msvc/olcPGE3.vcxproj | 1 + dev/msvc/olcPGE3.vcxproj.filters | 101 ++++---- .../olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj | 8 +- .../olcPGE3_BuildSH.vcxproj.filters | 3 + dev/src/core.cpp | 223 +++++++++--------- dev/src/core.h | 13 + dev/src/extension.h | 75 ++++++ dev/src/sh_template.h | 3 + examples/olcPGE3_Extensions.cpp | 142 +++++++++++ olcPixelGameEngine3.h | 209 ++++++++++++++-- 10 files changed, 598 insertions(+), 180 deletions(-) create mode 100644 dev/src/extension.h create mode 100644 examples/olcPGE3_Extensions.cpp diff --git a/dev/msvc/olcPGE3.vcxproj b/dev/msvc/olcPGE3.vcxproj index 78ff7d73..0a987d69 100644 --- a/dev/msvc/olcPGE3.vcxproj +++ b/dev/msvc/olcPGE3.vcxproj @@ -196,6 +196,7 @@ + diff --git a/dev/msvc/olcPGE3.vcxproj.filters b/dev/msvc/olcPGE3.vcxproj.filters index 4ecd69df..27ae7899 100644 --- a/dev/msvc/olcPGE3.vcxproj.filters +++ b/dev/msvc/olcPGE3.vcxproj.filters @@ -40,6 +40,12 @@ {0020e19e-b8ce-4218-a5ee-8a5716c9f01a} + + {a89dec37-58b8-40e6-b475-c62b7e443d0f} + + + {b5b02d3b-7b70-4ef1-a2e2-516cbaa9da51} + @@ -75,39 +81,9 @@ Header Files - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - Header Files - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - Header Files @@ -165,6 +141,39 @@ Header Files + + Hosts + + + GPUs + + + GPUs + + + GPUs + + + GPUs + + + Hardware + + + Hardware + + + Hardware + + + Hardware + + + Hosts + + + Header Files + @@ -173,12 +182,6 @@ Source Files - - Source Files - - - Source Files - Source Files @@ -188,12 +191,6 @@ Source Files - - Source Files - - - Source Files - Source Files @@ -224,17 +221,29 @@ Hosts\Wayland Specific - - Source Files - Hosts\Android Specific Hosts\Android Specific + + GPUs + + + GPUs + + + GPUs + + + Hardware + + + Hardware + - Source Files + Hosts \ No newline at end of file diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj index 081d8eb6..a66647f3 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj @@ -19,7 +19,12 @@ - + + true + true + true + true + true true @@ -44,6 +49,7 @@ true true + true true diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters index ed7a464d..c6f04276 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters @@ -87,6 +87,9 @@ Source Files + + Source Files + diff --git a/dev/src/core.cpp b/dev/src/core.cpp index 98958906..794fec0a 100644 --- a/dev/src/core.cpp +++ b/dev/src/core.cpp @@ -85,6 +85,8 @@ namespace olc bool PGEWindow::olc_WindowUpdate(const float fElapsedTime, const float fTotalElapsedTime) { + float fDT = fElapsedTime; + // Input Changes mouse.UpdateState(); keyboard.UpdateState(); @@ -92,42 +94,63 @@ namespace olc draw.SetGPU(pRenderer); draw.SetTarget(GetScreen()); - pRenderer->DisplayPrepare(fElapsedTime, fTotalElapsedTime); + pRenderer->DisplayPrepare(fDT, fTotalElapsedTime); #if OLC_MULTIWINDOW == OLC_MULTIWINDOW_YES pRenderer->RetargetDevice(pHost->GetHostWindowDescriptor(this)); #endif pRenderer->ApplyDefaultShader(); + bool bBlockUserUpdate = false; + for (const auto& pgex : vecWindowExtensions) + { + bBlockUserUpdate |= pgex->OnBeforeUserUpdate(this, fDT); + } - - // User Update - if (!OnUserUpdate(fElapsedTime) || bRequestToClose) + if (!bBlockUserUpdate) { - // User has requested termination of window by returning false - if (OnUserDestroy()) + // User Update + if (!OnUserUpdate(fDT) || bRequestToClose) { - // User has confirmed window destruction by returning true - bShouldRemove = true; + // User has requested termination of window by returning false + if (OnUserDestroy()) + { + // User has confirmed window destruction by returning true + bShouldRemove = true; + } + else + bRequestToClose = false; // User vetoed closure } - else - bRequestToClose = false; // User vetoed closure - } + + // Finialise any outstanding tasks + draw.ProcessGPUTasks(); + if (GetScreen().GetConfig().MSAA) + { + pRenderer->ResolveMSAA(uint32_t(GetScreen().GetGPUID())); + } - // Finialise any outstanding tasks - draw.ProcessGPUTasks(); + draw.ResetShader(); + draw.WorldReset(); + } - if (GetScreen().GetConfig().MSAA) + for (const auto& pgex : vecWindowExtensions) { - pRenderer->ResolveMSAA(uint32_t(GetScreen().GetGPUID())); - } + if (pgex->OnAfterUserUpdate(this, fDT)) + { + // Finialise any outstanding tasks + draw.ProcessGPUTasks(); - draw.ResetShader(); - draw.WorldReset(); + if (GetScreen().GetConfig().MSAA) + { + pRenderer->ResolveMSAA(uint32_t(GetScreen().GetGPUID())); + } + + draw.ResetShader(); + draw.WorldReset(); + } + } - // Take the window's completed "screen" and draw it as a textured quad to the backbuffer - pRenderer->AssignTextureTarget(0, 0); // === Viewport Handling === @@ -158,6 +181,9 @@ namespace olc // know for scaling } + // Take the window's completed "screen" and draw it as a textured quad to the backbuffer + pRenderer->AssignTextureTarget(0, 0); + // Present final composite pRenderer->SetViewport(vViewPos, vViewSize); pRenderer->ClearViewport(config.colClear, true, true); @@ -171,6 +197,12 @@ namespace olc return true; } + bool PGEWindow::InstallWindowExtension(olc::PGEWindowExtension* pgex) + { + vecWindowExtensions.push_back(pgex); + return pgex->OnInstall(this); + } + bool PGEWindow::CreateImage(olc::Image& image, const olc::vi2d& size, const ImageConfig& cfg) { // Create CPU Image @@ -395,6 +427,23 @@ namespace olc gpu->ApplyDefaultShader(); draw.SetTarget(GetScreen()); + for (const auto& pgex : vecSystemExtensions) + { + if(!pgex->OnBeforeUserCreate(this)) + { + std::cout << "PGE OnContextStart(): User aborted in extension OnBeforeUserCreate()\n"; + return false; + } + } + + // It's possible to draw things in create so flush any pending GPU tasks + draw.ProcessGPUTasks(); + + // Set to known default state + draw.SetTarget(GetScreen()); + draw.WorldReset(); + gpu->ApplyDefaultShader(); + // User Create GOOOOOOOOO!!!! if (!OnUserCreate()) { @@ -410,6 +459,23 @@ namespace olc draw.WorldReset(); gpu->ApplyDefaultShader(); + for (const auto& pgex : vecSystemExtensions) + { + if (!pgex->OnAfterUserCreate(this)) + { + std::cout << "PGE OnContextStart(): User aborted in extension OnAfterUserCreate()\n"; + return false; + } + } + + // It's possible to draw things in create so flush any pending GPU tasks + draw.ProcessGPUTasks(); + + // Set to known default state + draw.SetTarget(GetScreen()); + draw.WorldReset(); + gpu->ApplyDefaultShader(); + // Fire up the engine! using namespace std::chrono_literals; durationFrameCount = 0s; @@ -472,9 +538,27 @@ namespace olc } else { + for (const auto& pgex : vecSystemExtensions) + { + if (!pgex->OnBeforeSystemUpdate(this, fDT)) + { + std::cout << "PGE OnContextTick(): User aborted in extension OnAfterUserCreate()\n"; + return false; + } + } + // Update Primary Window olc_WindowUpdate(fDT, fTT); + for (const auto& pgex : vecSystemExtensions) + { + if (!pgex->OnAfterSystemUpdate(this, fDT)) + { + std::cout << "PGE OnContextTick(): User aborted in extension OnAfterSystemUpdate()\n"; + return false; + } + } + // Wait for vertical sync with desktop compositor if required. // Note: Child windows will never vsync as waiting for each buffer swap with vsync @@ -536,39 +620,15 @@ namespace olc #endif } + bool PixelGameEngine::InstallSystemExtension(olc::PGESystemExtension* pgex) + { + vecSystemExtensions.push_back(pgex); + return pgex->OnInstall(this); + } + } //! END IMPLEMENTATION -// DEVS!! All your old stuff is below here for reference, but will be removed later - -// // Johnnyg63: Added MacOS Host Initialisation -// -//#if OLC_MULTIWINDOW == OLC_MULTIWINDOW_NO -// // Create OS window on this thread -// host->AddWindowFrame(this, { 30,30 }, config.vPixelSize * config.vScreenSize, false); -// // Create EngineThread - no more windows will be created now. We needed one window -// // at least to initialise teh rendering subsystem... sigh. -// coreActive = true; -// -//#if OLC_HOST != OLC_HOST_EMSCRIPTEN -// coreThread = std::thread(&PixelGameEngine::EngineThread, this); -// // Handle window events on this thread (and block) -// host->StartSystemEventLoop(true); -// // Window has closed its event handler, so shut down gracefully -// coreActive = false; -// // Wait for engine thread to terminate -// coreThread.join(); -//#else -// EngineThread(); -//#endif -// -//#else -// -//#endif - // - //return true; - - // void PixelGameEngine::CoreUpdate(void* userdata) // { @@ -596,69 +656,6 @@ namespace olc //#endif // // -// -// // Initialise ImageLoader Interface -// -// -// -// -// // Link this windows devices -// LinkToHost(host.get()); -// LinkToRenderer(gpu.get()); -// LinkToImageLoader(imageloader.get()); -// -// // The GPU device can be based upon the primary window configuration. This -// // gives us completed gpu and host objects to pass to other windows as and -// // when required -// gpu->CreateDevice(host->GetHostWindowDescriptor(this), cfgRenderer); -// if (gpu->GetLastError() != olc::gpu::RendererError::NoError) -// { -// //const auto e = gpu->GetLastError(); // For debug visibility -// std::cout << "Error: Could not create Renderer\n"; -// return; -// } -// -// -// -// olc::ImageConfig cfg; -// cfg.MSAA = config.bAntiAliasMainScreen; -// CreateImage(GetDefaultImage(), config.vScreenSize, cfg); -// -// -// // Initialise Font System -// olc::pgeguts::CreateClassicFont(this); -// -// -// draw.SetGPU(gpu.get()); -// gpu->ApplyDefaultShader(); -// draw.SetTarget(GetDefaultImage()); -// -// if (!OnUserCreate()) -// { -// // Creation process signalled abort -// return; -// } -// -// -// -// draw.ProcessGPUTasks(); -// draw.SetTarget(GetDefaultImage()); -// -// // Initialise Input Devices -// -// -// -// -// #if OLC_HOST == OLC_HOST_EMSCRIPTEN -// emscripten_set_main_loop_arg(PixelGameEngine::CoreUpdate, reinterpret_cast(this), 0, 1); -// #else -// while (coreActive) -// { -// PixelGameEngine::CoreUpdate(this); -// } -// #endif -// } - diff --git a/dev/src/core.h b/dev/src/core.h index 7c512a7a..3c538557 100644 --- a/dev/src/core.h +++ b/dev/src/core.h @@ -22,6 +22,7 @@ #include "imload_iface.h" #include "font.h" #include "draw.h" +#include "extension.h" //! END CUSTOMHEADER GLOBAL //! START DECLARATION @@ -122,6 +123,10 @@ namespace olc olc::vi2d vViewPos = { 0,0 }; olc::vi2d vViewSize = { 0,0 }; + protected: // Extensions + bool InstallWindowExtension(olc::PGEWindowExtension* pgex); + std::vector vecWindowExtensions; + protected: // PGE Configuration PGEConfig config; @@ -157,6 +162,10 @@ namespace olc public: // Child Windows bool AddChildWindow(std::shared_ptr window, const olc::vi2d& vScreenSize, const olc::vi2d& vPixelSize); + + protected: + bool InstallSystemExtension(olc::PGESystemExtension* pgex); + private: // Called from Host @@ -175,6 +184,10 @@ namespace olc // Window Management std::deque> deqChildWindows; + // Extensions + std::vector vecSystemExtensions; + + // Frame Timing & Overall Clocking std::chrono::steady_clock::time_point timeFrame1; std::chrono::steady_clock::time_point timeFrame2; diff --git a/dev/src/extension.h b/dev/src/extension.h new file mode 100644 index 00000000..51f1e230 --- /dev/null +++ b/dev/src/extension.h @@ -0,0 +1,75 @@ +#pragma once + +//! START STDHEADER GLOBAL +//! END STDHEADER + +//! START CUSTOMHEADER +//! END CUSTOMHEADER + +//! START DECLARATION +#if !defined(PGE_EXTENSION_DECLARED) +namespace olc +{ + class PixelGameEngine; + class PGEWindow; + + namespace hw + { + class Mouse; + class Keyboard; + } + + // System level extension + class PGESystemExtension + { + friend class olc::PixelGameEngine; + friend class olc::PGEWindow; + friend class olc::hw::Mouse; + friend class olc::hw::Keyboard; + + private: + // Called when extension is installed, usually in PGE Constructor + // Return true to continue application + virtual bool OnInstall([[maybe_unused]] olc::PixelGameEngine* pge) { return true; } + // Called after PGE is established, but before OnUserCreate() + // Return true to continue application + virtual bool OnBeforeUserCreate([[maybe_unused]] olc::PixelGameEngine* pge) { return true; } + // Called after OnUserCreate(), but before the first call to OnUserUpdate() + // Return true to continue application + virtual bool OnAfterUserCreate([[maybe_unused]] olc::PixelGameEngine* pge) { return true; } + // Called at the start of each frame, before OnUserUpdate() + // Return true if you wish to block OnUserUpdate() from being called this frame + virtual bool OnBeforeSystemUpdate([[maybe_unused]] olc::PixelGameEngine* pge, [[maybe_unused]] float fElapsedTime) { return false; } + // Called at the end of each frame, after OnUserUpdate() + // Return true to continue application + virtual bool OnAfterSystemUpdate([[maybe_unused]] olc::PixelGameEngine* pge, [[maybe_unused]] float fElapsedTime) { return true; } + }; + + // Window level extension + class PGEWindowExtension + { + friend class olc::PGEWindow; + friend class olc::hw::Mouse; + friend class olc::hw::Keyboard; + + private: + // Called when extension is installed, usually in PGE Constructor + // Return true to continue application + virtual bool OnInstall([[maybe_unused]] olc::PGEWindow* pge) { return true; } + // Called after PGE is established, but before OnUserCreate() + // Return true to continue application + virtual bool OnBeforeUserCreate([[maybe_unused]] olc::PGEWindow* pge) { return true; } + // Called after OnUserCreate(), but before the first call to OnUserUpdate() + // Return true to continue application + virtual bool OnAfterUserCreate([[maybe_unused]] olc::PGEWindow* pge) { return true; } + // Called at the start of each frame, before OnUserUpdate() + // Return true if you wish to block OnUserUpdate() from being called this frame + virtual bool OnBeforeUserUpdate([[maybe_unused]] olc::PGEWindow* pge, [[maybe_unused]] float &fElapsedTime) { return false; } + // Called at the end of each frame, after OnUserUpdate() + // Return true to indicate you have modified something + virtual bool OnAfterUserUpdate([[maybe_unused]] olc::PGEWindow* pge, [[maybe_unused]] float fElapsedTime) { return false; } + }; +} +#define PGE_EXTENSION_DECLARED 1 +#endif +//! END DECLARATION \ No newline at end of file diff --git a/dev/src/sh_template.h b/dev/src/sh_template.h index 19b11159..6b8cc639 100644 --- a/dev/src/sh_template.h +++ b/dev/src/sh_template.h @@ -147,6 +147,8 @@ //! GRAB host_iface.h DECLARATION +//! GRAB extension.h DECLARATION + //! GRAB core.h DECLARATION @@ -158,6 +160,7 @@ + #if OLC_HOST == OLC_HOST_WINDOWS //! GRAB host_win_winapi.h WINAPI_CONFIG diff --git a/examples/olcPGE3_Extensions.cpp b/examples/olcPGE3_Extensions.cpp new file mode 100644 index 00000000..d3b1856b --- /dev/null +++ b/examples/olcPGE3_Extensions.cpp @@ -0,0 +1,142 @@ +/* + olc::PixelGameEngine3 Example - Extensions + + Creates a simple Window Level PGEX to illustrate an external object + being hooked at various points in the rendering and update cycle. + + Not shown is the accompanying System Level PGEX, which could be used + for things like sound engines, resource managers, etc. + + Licenced under the OLC-3 License +*/ + + +// Define OLC_PGE3_APPLICATION to include the implementation of +// the Pixel Game Engine as part of this translation unit +#define OLC_PGE3_APPLICATION +#include "../olcPixelGameEngine3.h" + + +// This is a "Window Extension" class. These are used on a "per window" basis to allow you to modify +// the behaviour of the window and application at various stages of the cycle. Typically these would +// be used to implement things like UI layers, or to inject update functionality into objects +class ExamplePGEX : public olc::PGEWindowExtension +{ + // Called when extension is installed, usually in PGEWindow Constructor + // Return true to continue application + virtual bool OnInstall([[maybe_unused]] olc::PGEWindow* pge) + { + return true; + } + + // Called after PGE is established, but before OnUserCreate() + // Return true to continue application + bool OnBeforeUserCreate([[maybe_unused]] olc::PGEWindow* pge) override + { + return true; + } + + // Called after OnUserCreate(), but before the first call to OnUserUpdate() + // Return true to continue application + bool OnAfterUserCreate([[maybe_unused]] olc::PGEWindow* pge) override + { + return true; + } + + // Called at the start of each frame, before OnUserUpdate() + // Return true if you wish to block OnUserUpdate() from being called this frame + // Note 'fElapsedTime' is passed by reference, so you could modify it to speed up or slow down the update cycle + bool OnBeforeUserUpdate([[maybe_unused]] olc::PGEWindow* pge, float &fElapsedTime) override + { + float fModfier = pge->GetMouse().GetPosition().x / float(pge->GetScreen().Size().x); + + // Warp Time depending on mouse position, just to illustrate the point that we can modify behaviour here. Note that this will affect all + fElapsedTime *= fModfier; + + // If we returned true, we indicate that the update cycle should be blocked, so OnUserUpdate() will not be + // called this frame. This could be used to implement a pause menu for example. Note that the extension is + // still active, so OnAfterUserUpdate() will still be called, allowing you to draw a menu or something + return false; + } + + // Called at the end of each frame, after OnUserUpdate() + // Return true to continue application + bool OnAfterUserUpdate([[maybe_unused]] olc::PGEWindow* pge, float fElapsedTime) override + { + pge->GetDraw().String({ 10.0f, 200.0f }, "This text is drawn from a\nWindow extension!", olc::Colour::YELLOW); + return true; + } + +}; + + +// We also have olc::PGESystemExtension for system level extensions, these are called +// at various stages of the context lifecycle. For example, a sound engine would +// likely be a system extension. + + + +// Example application demonstrating "PGEX". This class +// overrides the olc::PixelGameEngine base class by implementing +// the OnUserCreate() and OnUserUpdate() functions +class Example_Extensions : public olc::PixelGameEngine +{ +public: + Example_Extensions() + { + + } + +protected: + + // We need to create an instance of our extension class, and then install it in the PGE constructor + ExamplePGEX pgex; + + float fTotalTime = 0.0f; + +public: + // Called once at the start, so create things here + bool OnUserCreate() override + { + // Installing an extension allows it to receive callbacks at various stages + // of the application and window lifecycle, and to modify behaviour if necessary. + // You can install as many extensions as you like, and they will be called in + // the order they were installed + InstallWindowExtension(&pgex); + return true; + } + + // Called every frame, so update things here + bool OnUserUpdate(float fElapsedTime) override + { + // Clear whole screen + draw.Clear(olc::Colour::VERY_DARK_BLUE); + + draw.String({ 10.0f, 10.0f }, "This text is drawn from the\nmain application!\n\nMouse Move in X To Warp Time", olc::Colour::WHITE); + + // Draw a clocking line to illustrate the passage of time... + fTotalTime += fElapsedTime; + draw.Line(GetScreen().Size() / 2, olc::vf2d(cos(fTotalTime), sin(fTotalTime)) * 50.0f + GetScreen().Size() / 2, olc::Colour::GREEN); + + // Successful frame + return true; + } +}; + + +// Main entry point for the application +int main() +{ + // Construct demo application + Example_Extensions demo; + + // Create "screen" of 256x240 "pixels" + // with a pixel size of 4x4 actual screen pixels + if (demo.Construct({ 256, 240 }, { 4, 4 })) + { + // Start the application + demo.Start(); + } + + return 0; +} \ No newline at end of file diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 194e5e9a..670ecfc6 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -3765,6 +3765,72 @@ namespace olc #define PGE_HOST_IFACE_DECLARED 1 #endif +#if !defined(PGE_EXTENSION_DECLARED) +namespace olc +{ + class PixelGameEngine; + class PGEWindow; + + namespace hw + { + class Mouse; + class Keyboard; + } + + // System level extension + class PGESystemExtension + { + friend class olc::PixelGameEngine; + friend class olc::PGEWindow; + friend class olc::hw::Mouse; + friend class olc::hw::Keyboard; + + private: + // Called when extension is installed, usually in PGE Constructor + // Return true to continue application + virtual bool OnInstall([[maybe_unused]] olc::PixelGameEngine* pge) { return true; } + // Called after PGE is established, but before OnUserCreate() + // Return true to continue application + virtual bool OnBeforeUserCreate([[maybe_unused]] olc::PixelGameEngine* pge) { return true; } + // Called after OnUserCreate(), but before the first call to OnUserUpdate() + // Return true to continue application + virtual bool OnAfterUserCreate([[maybe_unused]] olc::PixelGameEngine* pge) { return true; } + // Called at the start of each frame, before OnUserUpdate() + // Return true if you wish to block OnUserUpdate() from being called this frame + virtual bool OnBeforeSystemUpdate([[maybe_unused]] olc::PixelGameEngine* pge, [[maybe_unused]] float fElapsedTime) { return false; } + // Called at the end of each frame, after OnUserUpdate() + // Return true to continue application + virtual bool OnAfterSystemUpdate([[maybe_unused]] olc::PixelGameEngine* pge, [[maybe_unused]] float fElapsedTime) { return true; } + }; + + // Window level extension + class PGEWindowExtension + { + friend class olc::PGEWindow; + friend class olc::hw::Mouse; + friend class olc::hw::Keyboard; + + private: + // Called when extension is installed, usually in PGE Constructor + // Return true to continue application + virtual bool OnInstall([[maybe_unused]] olc::PGEWindow* pge) { return true; } + // Called after PGE is established, but before OnUserCreate() + // Return true to continue application + virtual bool OnBeforeUserCreate([[maybe_unused]] olc::PGEWindow* pge) { return true; } + // Called after OnUserCreate(), but before the first call to OnUserUpdate() + // Return true to continue application + virtual bool OnAfterUserCreate([[maybe_unused]] olc::PGEWindow* pge) { return true; } + // Called at the start of each frame, before OnUserUpdate() + // Return true if you wish to block OnUserUpdate() from being called this frame + virtual bool OnBeforeUserUpdate([[maybe_unused]] olc::PGEWindow* pge, [[maybe_unused]] float &fElapsedTime) { return false; } + // Called at the end of each frame, after OnUserUpdate() + // Return true to indicate you have modified something + virtual bool OnAfterUserUpdate([[maybe_unused]] olc::PGEWindow* pge, [[maybe_unused]] float fElapsedTime) { return false; } + }; +} +#define PGE_EXTENSION_DECLARED 1 +#endif + #if !defined(PGE_CORE_DECLARED) namespace olc @@ -3862,6 +3928,10 @@ namespace olc olc::vi2d vViewPos = { 0,0 }; olc::vi2d vViewSize = { 0,0 }; + protected: // Extensions + bool InstallWindowExtension(olc::PGEWindowExtension* pgex); + std::vector vecWindowExtensions; + protected: // PGE Configuration PGEConfig config; @@ -3897,6 +3967,10 @@ namespace olc public: // Child Windows bool AddChildWindow(std::shared_ptr window, const olc::vi2d& vScreenSize, const olc::vi2d& vPixelSize); + + protected: + bool InstallSystemExtension(olc::PGESystemExtension* pgex); + private: // Called from Host @@ -3915,6 +3989,10 @@ namespace olc // Window Management std::deque> deqChildWindows; + // Extensions + std::vector vecSystemExtensions; + + // Frame Timing & Overall Clocking std::chrono::steady_clock::time_point timeFrame1; std::chrono::steady_clock::time_point timeFrame2; @@ -3944,6 +4022,7 @@ namespace olc + #if OLC_HOST == OLC_HOST_WINDOWS #if defined(UNICODE) || defined(_UNICODE) #define olcT(s) L##s @@ -15545,6 +15624,8 @@ namespace olc bool PGEWindow::olc_WindowUpdate(const float fElapsedTime, const float fTotalElapsedTime) { + float fDT = fElapsedTime; + // Input Changes mouse.UpdateState(); keyboard.UpdateState(); @@ -15552,42 +15633,63 @@ namespace olc draw.SetGPU(pRenderer); draw.SetTarget(GetScreen()); - pRenderer->DisplayPrepare(fElapsedTime, fTotalElapsedTime); + pRenderer->DisplayPrepare(fDT, fTotalElapsedTime); #if OLC_MULTIWINDOW == OLC_MULTIWINDOW_YES pRenderer->RetargetDevice(pHost->GetHostWindowDescriptor(this)); #endif pRenderer->ApplyDefaultShader(); + bool bBlockUserUpdate = false; + for (const auto& pgex : vecWindowExtensions) + { + bBlockUserUpdate |= pgex->OnBeforeUserUpdate(this, fDT); + } - - // User Update - if (!OnUserUpdate(fElapsedTime) || bRequestToClose) + if (!bBlockUserUpdate) { - // User has requested termination of window by returning false - if (OnUserDestroy()) + // User Update + if (!OnUserUpdate(fDT) || bRequestToClose) { - // User has confirmed window destruction by returning true - bShouldRemove = true; + // User has requested termination of window by returning false + if (OnUserDestroy()) + { + // User has confirmed window destruction by returning true + bShouldRemove = true; + } + else + bRequestToClose = false; // User vetoed closure } - else - bRequestToClose = false; // User vetoed closure - } + + // Finialise any outstanding tasks + draw.ProcessGPUTasks(); + if (GetScreen().GetConfig().MSAA) + { + pRenderer->ResolveMSAA(uint32_t(GetScreen().GetGPUID())); + } - // Finialise any outstanding tasks - draw.ProcessGPUTasks(); + draw.ResetShader(); + draw.WorldReset(); + } - if (GetScreen().GetConfig().MSAA) + for (const auto& pgex : vecWindowExtensions) { - pRenderer->ResolveMSAA(uint32_t(GetScreen().GetGPUID())); - } + if (pgex->OnAfterUserUpdate(this, fDT)) + { + // Finialise any outstanding tasks + draw.ProcessGPUTasks(); - draw.ResetShader(); - draw.WorldReset(); + if (GetScreen().GetConfig().MSAA) + { + pRenderer->ResolveMSAA(uint32_t(GetScreen().GetGPUID())); + } + + draw.ResetShader(); + draw.WorldReset(); + } + } - // Take the window's completed "screen" and draw it as a textured quad to the backbuffer - pRenderer->AssignTextureTarget(0, 0); // === Viewport Handling === @@ -15618,6 +15720,9 @@ namespace olc // know for scaling } + // Take the window's completed "screen" and draw it as a textured quad to the backbuffer + pRenderer->AssignTextureTarget(0, 0); + // Present final composite pRenderer->SetViewport(vViewPos, vViewSize); pRenderer->ClearViewport(config.colClear, true, true); @@ -15631,6 +15736,12 @@ namespace olc return true; } + bool PGEWindow::InstallWindowExtension(olc::PGEWindowExtension* pgex) + { + vecWindowExtensions.push_back(pgex); + return pgex->OnInstall(this); + } + bool PGEWindow::CreateImage(olc::Image& image, const olc::vi2d& size, const ImageConfig& cfg) { // Create CPU Image @@ -15855,6 +15966,23 @@ namespace olc gpu->ApplyDefaultShader(); draw.SetTarget(GetScreen()); + for (const auto& pgex : vecSystemExtensions) + { + if(!pgex->OnBeforeUserCreate(this)) + { + std::cout << "PGE OnContextStart(): User aborted in extension OnBeforeUserCreate()\n"; + return false; + } + } + + // It's possible to draw things in create so flush any pending GPU tasks + draw.ProcessGPUTasks(); + + // Set to known default state + draw.SetTarget(GetScreen()); + draw.WorldReset(); + gpu->ApplyDefaultShader(); + // User Create GOOOOOOOOO!!!! if (!OnUserCreate()) { @@ -15870,6 +15998,23 @@ namespace olc draw.WorldReset(); gpu->ApplyDefaultShader(); + for (const auto& pgex : vecSystemExtensions) + { + if (!pgex->OnAfterUserCreate(this)) + { + std::cout << "PGE OnContextStart(): User aborted in extension OnAfterUserCreate()\n"; + return false; + } + } + + // It's possible to draw things in create so flush any pending GPU tasks + draw.ProcessGPUTasks(); + + // Set to known default state + draw.SetTarget(GetScreen()); + draw.WorldReset(); + gpu->ApplyDefaultShader(); + // Fire up the engine! using namespace std::chrono_literals; durationFrameCount = 0s; @@ -15932,9 +16077,27 @@ namespace olc } else { + for (const auto& pgex : vecSystemExtensions) + { + if (!pgex->OnBeforeSystemUpdate(this, fDT)) + { + std::cout << "PGE OnContextTick(): User aborted in extension OnAfterUserCreate()\n"; + return false; + } + } + // Update Primary Window olc_WindowUpdate(fDT, fTT); + for (const auto& pgex : vecSystemExtensions) + { + if (!pgex->OnAfterSystemUpdate(this, fDT)) + { + std::cout << "PGE OnContextTick(): User aborted in extension OnAfterSystemUpdate()\n"; + return false; + } + } + // Wait for vertical sync with desktop compositor if required. // Note: Child windows will never vsync as waiting for each buffer swap with vsync @@ -15996,6 +16159,12 @@ namespace olc #endif } + bool PixelGameEngine::InstallSystemExtension(olc::PGESystemExtension* pgex) + { + vecSystemExtensions.push_back(pgex); + return pgex->OnInstall(this); + } + } #define PGE_CORE_IMPLEMENTED 1 #endif From 1adf30dd811bc56e3dfe78e62e35faf4a433ce86 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Mon, 9 Feb 2026 18:51:52 -0500 Subject: [PATCH 31/58] [wayland] add forward and back buttons to the mouse handler --- dev/src/host_lin_wayland.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dev/src/host_lin_wayland.cpp b/dev/src/host_lin_wayland.cpp index f2352d78..ca30678c 100644 --- a/dev/src/host_lin_wayland.cpp +++ b/dev/src/host_lin_wayland.cpp @@ -529,6 +529,8 @@ namespace olc::host case BTN_LEFT: pge_window->olc_OnMouseButton(0, pointer_state.state == WL_POINTER_BUTTON_STATE_PRESSED); break; case BTN_MIDDLE: pge_window->olc_OnMouseButton(2, pointer_state.state == WL_POINTER_BUTTON_STATE_PRESSED); break; case BTN_RIGHT: pge_window->olc_OnMouseButton(1, pointer_state.state == WL_POINTER_BUTTON_STATE_PRESSED); break; + case BTN_BACK: pge_window->olc_OnMouseButton(3, pointer_state.state == WL_POINTER_BUTTON_STATE_PRESSED); break; + case BTN_FORWARD: pge_window->olc_OnMouseButton(4, pointer_state.state == WL_POINTER_BUTTON_STATE_PRESSED); break; default: break; } } From baa516fe40d98b125c6039a200c584eb01516287 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Mon, 9 Feb 2026 19:01:26 -0500 Subject: [PATCH 32/58] [wayland] apparently back and forward are side and extra respectively... --- dev/src/host_lin_wayland.cpp | 4 ++-- olcPixelGameEngine3.h | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/dev/src/host_lin_wayland.cpp b/dev/src/host_lin_wayland.cpp index ca30678c..1a5c34f5 100644 --- a/dev/src/host_lin_wayland.cpp +++ b/dev/src/host_lin_wayland.cpp @@ -529,8 +529,8 @@ namespace olc::host case BTN_LEFT: pge_window->olc_OnMouseButton(0, pointer_state.state == WL_POINTER_BUTTON_STATE_PRESSED); break; case BTN_MIDDLE: pge_window->olc_OnMouseButton(2, pointer_state.state == WL_POINTER_BUTTON_STATE_PRESSED); break; case BTN_RIGHT: pge_window->olc_OnMouseButton(1, pointer_state.state == WL_POINTER_BUTTON_STATE_PRESSED); break; - case BTN_BACK: pge_window->olc_OnMouseButton(3, pointer_state.state == WL_POINTER_BUTTON_STATE_PRESSED); break; - case BTN_FORWARD: pge_window->olc_OnMouseButton(4, pointer_state.state == WL_POINTER_BUTTON_STATE_PRESSED); break; + case BTN_SIDE: pge_window->olc_OnMouseButton(3, pointer_state.state == WL_POINTER_BUTTON_STATE_PRESSED); break; + case BTN_EXTRA: pge_window->olc_OnMouseButton(4, pointer_state.state == WL_POINTER_BUTTON_STATE_PRESSED); break; default: break; } } diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 194e5e9a..487692c6 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -10653,6 +10653,8 @@ namespace olc::host case BTN_LEFT: pge_window->olc_OnMouseButton(0, pointer_state.state == WL_POINTER_BUTTON_STATE_PRESSED); break; case BTN_MIDDLE: pge_window->olc_OnMouseButton(2, pointer_state.state == WL_POINTER_BUTTON_STATE_PRESSED); break; case BTN_RIGHT: pge_window->olc_OnMouseButton(1, pointer_state.state == WL_POINTER_BUTTON_STATE_PRESSED); break; + case BTN_SIDE: pge_window->olc_OnMouseButton(3, pointer_state.state == WL_POINTER_BUTTON_STATE_PRESSED); break; + case BTN_EXTRA: pge_window->olc_OnMouseButton(4, pointer_state.state == WL_POINTER_BUTTON_STATE_PRESSED); break; default: break; } } From 3fb0b115e542cd7ed542fb7b209d2de84cc843d7 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Sun, 8 Feb 2026 15:44:26 -0500 Subject: [PATCH 33/58] [mac] add flagsChanged event handler boiler plate and refactored keyboard handler --- dev/src/api_macos.cpp | 23 ++++- dev/src/api_macos.h | 1 + dev/src/api_macos_wrapper.hpp | 31 ++++++ dev/src/host_apple_macos.cpp | 117 +++++++++++++---------- dev/src/host_apple_macos.h | 2 - olcPixelGameEngine3.h | 172 ++++++++++++++++++++++++---------- 6 files changed, 246 insertions(+), 100 deletions(-) diff --git a/dev/src/api_macos.cpp b/dev/src/api_macos.cpp index 2017ebea..31744b5f 100644 --- a/dev/src/api_macos.cpp +++ b/dev/src/api_macos.cpp @@ -62,6 +62,7 @@ static constexpr const char* kWindowDidDeminiaturizeSel = "windowDidDemi // NSResponder keyboard and mouse event methods selectors static constexpr const char* kKeyDownSel = "keyDown:"; static constexpr const char* kKeyUpSel = "keyUp:"; +static constexpr const char* kFlagsChangedSel = "flagsChanged:"; static constexpr const char* kMouseDownSel = "mouseDown:"; static constexpr const char* kMouseUpSel = "mouseUp:"; static constexpr const char* kMouseDraggedSel = "mouseDragged:"; @@ -203,6 +204,7 @@ namespace ObjectiveCSEL { // NSResponder keyboard and mouse event methods selectors static SEL keyDownSel = nullptr; static SEL keyUpSel = nullptr; + static SEL flagsChangedSel = nullptr; static SEL mouseDownSel = nullptr; static SEL mouseUpSel = nullptr; static SEL mouseDraggedSel = nullptr; @@ -317,6 +319,7 @@ namespace ObjectiveCSEL { // NSResponder keyboard and mouse event methods selectors keyDownSel = sel_registerName(kKeyDownSel); keyUpSel = sel_registerName(kKeyUpSel); + flagsChangedSel = sel_registerName(kFlagsChangedSel); mouseDownSel = sel_registerName(kMouseDownSel); mouseUpSel = sel_registerName(kMouseUpSel); mouseDraggedSel = sel_registerName(kMouseDraggedSel); @@ -658,6 +661,7 @@ struct Window { // Event callback function pointers with nullptr initialization void (*keyDownCallback) (unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData){nullptr}; void (*keyUpCallback) (unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData){nullptr}; + void (*flagsChangedCallback) (unsigned int modifierFlags, void* userData){nullptr}; void (*mouseDownCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; void (*mouseUpCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; void (*mouseMovedCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; @@ -857,6 +861,15 @@ void view_keyUp(id self, SEL _cmd, id event) { } } +void view_flagsChanged(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; + + unsigned int modifierFlags = ((unsigned int(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); + + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->flagsChangedCallback) [[likely]] { + gptrNSWindowEvents->flagsChangedCallback(modifierFlags, gptrNSWindowEvents->eventUserData); + } +} //====================================================================// // Mouse Event Handling @@ -1092,7 +1105,10 @@ Class createCustomOpenGLViewClass() { // Keyboard event handler methods class_addMethod(CustomViewClass, ObjectiveCSEL::keyDownSel, (IMP)view_keyDown, kEventHandlerMethodTypeEncoding); class_addMethod(CustomViewClass, ObjectiveCSEL::keyUpSel, (IMP)view_keyUp, kEventHandlerMethodTypeEncoding); - + + // Flags Changed event handler + class_addMethod(CustomViewClass, ObjectiveCSEL::flagsChangedSel, (IMP)view_flagsChanged, kEventHandlerMethodTypeEncoding); + // Mouse event handler methods class_addMethod(CustomViewClass, ObjectiveCSEL::mouseDownSel, (IMP)view_mouseDown, kEventHandlerMethodTypeEncoding); class_addMethod(CustomViewClass, ObjectiveCSEL::mouseUpSel, (IMP)view_mouseUp, kEventHandlerMethodTypeEncoding); @@ -1994,6 +2010,11 @@ extern "C" { self->keyUpCallback = callback; self->eventUserData = userData; } + + void window_setFlagsChangedCallback(Window* self, void (*callback)(unsigned int, void*), void* userData) { + self->flagsChangedCallback = callback; + self->eventUserData = userData; + } void window_setMouseDownCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { self->mouseDownCallback = callback; diff --git a/dev/src/api_macos.h b/dev/src/api_macos.h index f8b6c0e9..a3e76de0 100644 --- a/dev/src/api_macos.h +++ b/dev/src/api_macos.h @@ -106,6 +106,7 @@ extern "C" { // Event handler setup void window_setKeyDownCallback (struct Window* self, KeyEventCallback callback, void* userData); void window_setKeyUpCallback (struct Window* self, KeyEventCallback callback, void* userData); + void window_setFlagsChangedCallback (Window* self, void (*callback)(unsigned int, void*), void* userData); void window_setMouseDownCallback (struct Window* self, MouseEventCallback callback, void* userData); void window_setMouseUpCallback (struct Window* self, MouseEventCallback callback, void* userData); void window_setMouseMovedCallback (struct Window* self, MouseEventCallback callback, void* userData); diff --git a/dev/src/api_macos_wrapper.hpp b/dev/src/api_macos_wrapper.hpp index ce561d64..f72f5d04 100644 --- a/dev/src/api_macos_wrapper.hpp +++ b/dev/src/api_macos_wrapper.hpp @@ -672,6 +672,19 @@ namespace olc { KeyEvent& operator=(const KeyEvent&) = default; }; + struct FlagsChangedEvent { + unsigned int modifierFlags; + + FlagsChangedEvent(unsigned int mods) noexcept + : modifierFlags(mods) {} + + // Move constructor and assignment for better performance + FlagsChangedEvent(FlagsChangedEvent&&) noexcept = default; + FlagsChangedEvent& operator=(FlagsChangedEvent&&) noexcept = default; + FlagsChangedEvent(const FlagsChangedEvent&) = default; + FlagsChangedEvent& operator=(const FlagsChangedEvent&) = default; + }; + // Mouse event data structure struct MouseEvent { double x, y; @@ -704,6 +717,7 @@ namespace olc { Window& window_; std::function keyDownHandler_; std::function keyUpHandler_; + std::function flagsChangedHandler_; std::function mouseDownHandler_; std::function mouseUpHandler_; std::function mouseMovedHandler_; @@ -724,6 +738,14 @@ namespace olc { (eventHandler->*handler)(KeyEvent(keyCode, characters, modifierFlags)); } } + + template + static void flagsChangedCallback(unsigned int modifierFlags, void* userData, HandlerType EventHandler::*handler) { + auto* eventHandler = static_cast(userData); + if (eventHandler && (eventHandler->*handler)) { + (eventHandler->*handler)(FlagsChangedEvent(modifierFlags)); + } + } template static void mouseCallback(double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData, HandlerType EventHandler::*handler) { @@ -742,6 +764,10 @@ namespace olc { keyCallback(keyCode, characters, modifierFlags, userData, &EventHandler::keyUpHandler_); } + static void flagsChangedCallback(unsigned int modifierFlags, void* userData) { + flagsChangedCallback(modifierFlags, userData, &EventHandler::flagsChangedHandler_); + } + static void mouseDownCallback(double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData) { mouseCallback(x, y, buttonNumber, modifierFlags, userData, &EventHandler::mouseDownHandler_); } @@ -826,6 +852,11 @@ namespace olc { window_setKeyUpCallback(window_.getCHandle(), keyUpCallback, this); } + void onFlagsChanged(std::function handler) { + flagsChangedHandler_ = std::move(handler); + window_setFlagsChangedCallback(window_.getCHandle(), flagsChangedCallback, this); + } + void onMouseDown(std::function handler) { mouseDownHandler_ = std::move(handler); window_setMouseDownCallback(window_.getCHandle(), mouseDownCallback, this); diff --git a/dev/src/host_apple_macos.cpp b/dev/src/host_apple_macos.cpp index ed4d3464..be804655 100644 --- a/dev/src/host_apple_macos.cpp +++ b/dev/src/host_apple_macos.cpp @@ -546,68 +546,89 @@ bool Host_Apple_MacOS::SyncWithDesktopComposite() } - bool Host_Apple_MacOS::ModifiersFlagsHandler(const olc::apis::macos::KeyEvent& event, bool pressed) { - - bool bisHandled = false; - if (event.modifierFlags & NSEventModifierFlagCapsLock) { - pPGEwindow->olc_OnKeyPress(Key::CAPS_LOCK, pressed); - } - if (event.modifierFlags & NSEventModifierFlagShift) { - pPGEwindow->olc_OnKeyPress(Key::SHIFT, pressed); - if(event.keyCode == 39) - { - // The @ symbol does not change position from US - UK keyboards on MacOS, so we handle it here - pPGEwindow->olc_OnKeyPress(mapKeys[50], pressed); - return true; - } - - } - if (event.modifierFlags & NSEventModifierFlagControl) { - pPGEwindow->olc_OnKeyPress(Key::CTRL, pressed); - } - - if(event.modifierFlags & NSEventModifierFlagNumericPad) { - if(event.keyCode == 71 && pressed) // NumLock keycode + void Host_Apple_MacOS::MacEventsHandler() + { + // General MacOS key event handling code here + // Reference: https://eastmanreference.com/complete-list-of-applescript-key-codes + + // Set up keyboard event handlers + pMacOSEventHandler->onKeyDown([&](const olc::apis::macos::KeyEvent& event) { + unsigned short keyCode = event.keyCode; + + if(!bNumLockActive) { - // We only tottle the NumLock state on key press to minic the latching of the key - bNumLockActive = !bNumLockActive; + // 84 down, 86 left, 88 right, 91 up >>> 125 down, 123 left, 124 right, 126 up + switch(keyCode) + { + case 84: keyCode = 125; break; + case 86: keyCode = 123; break; + case 88: keyCode = 124; break; + case 91: keyCode = 126; break; + default: break; + } } + + // The @ symbol does not change position from US - UK keyboards on MacOS, so we handle it here + if(event.modifierFlags & NSEventModifierFlagShift && event.keyCode == 39) + keyCode = 50; + pPGEwindow->olc_OnKeyPress(mapKeys[keyCode], true); + }); + + pMacOSEventHandler->onKeyUp([&](const olc::apis::macos::KeyEvent& event) { + unsigned short keyCode = event.keyCode; + if(!bNumLockActive) { // 84 down, 86 left, 88 right, 91 up >>> 125 down, 123 left, 124 right, 126 up - if(event.keyCode == 84) pPGEwindow->olc_OnKeyPress(mapKeys[125], pressed); - if(event.keyCode == 86) pPGEwindow->olc_OnKeyPress(mapKeys[123], pressed); - if(event.keyCode == 88) pPGEwindow->olc_OnKeyPress(mapKeys[124], pressed); - if(event.keyCode == 91) pPGEwindow->olc_OnKeyPress(mapKeys[126], pressed); - return true; + switch(keyCode) + { + case 84: keyCode = 125; break; + case 86: keyCode = 123; break; + case 88: keyCode = 124; break; + case 91: keyCode = 126; break; + default: break; + } } - - } + // The @ symbol does not change position from US - UK keyboards on MacOS, so we handle it here + if(event.modifierFlags & NSEventModifierFlagShift && event.keyCode == 39) + keyCode = 50; - return bisHandled; - - } + pPGEwindow->olc_OnKeyPress(mapKeys[keyCode], false); + }); + // Set up keyboard flag event handlers + pMacOSEventHandler->onFlagsChanged([&](const olc::apis::macos::FlagsChangedEvent& event) { - void Host_Apple_MacOS::MacEventsHandler() - { - // General MacOS key event handling code here - // Reference: https://eastmanreference.com/complete-list-of-applescript-key-codes + static unsigned int prevFlags = 0; + unsigned int changedFlags = event.modifierFlags ^ prevFlags; + + // Check For Shift key + if (changedFlags & NSEventModifierFlagShift) { + bool isPressed = event.modifierFlags & NSEventModifierFlagShift; + pPGEwindow->olc_OnKeyPress(Key::SHIFT, isPressed); + } + + // Check for Control key + if (changedFlags & NSEventModifierFlagControl) { + bool isPressed = event.modifierFlags & NSEventModifierFlagControl; + pPGEwindow->olc_OnKeyPress(Key::CTRL, isPressed); + } + + // Check for numeric pad modifier + if (changedFlags & NSEventModifierFlagNumericPad) { + bool isPressed = event.modifierFlags & NSEventModifierFlagNumericPad; + if(isPressed) + bNumLockActive = !bNumLockActive; - // Set up keyboard event handlers - pMacOSEventHandler->onKeyDown([&](const olc::apis::macos::KeyEvent& event) { - if(!ModifiersFlagsHandler(event, true)) - pPGEwindow->olc_OnKeyPress(mapKeys[event.keyCode], true); + std::cout << "NumLockActive " << bNumLockActive << "\n"; + } + + // caps lock doesn't appear to trigger any event + prevFlags = event.modifierFlags; }); - - pMacOSEventHandler->onKeyUp([&](const olc::apis::macos::KeyEvent& event) { - if(!ModifiersFlagsHandler(event, false)) - pPGEwindow->olc_OnKeyPress(mapKeys[event.keyCode], false); - }); - // Set up mouse event handlers pMacOSEventHandler->onMouseDown([&](const olc::apis::macos::MouseEvent& event) { pPGEwindow->olc_OnMouseButton(event.buttonNumber, true); diff --git a/dev/src/host_apple_macos.h b/dev/src/host_apple_macos.h index 7aa4aa02..62c6eeb1 100644 --- a/dev/src/host_apple_macos.h +++ b/dev/src/host_apple_macos.h @@ -139,8 +139,6 @@ namespace olc void MacEventsHandler(); void MacOpenGLContextEventsHandler(); - // When modifier flag changes the keycode it will return true, else false - bool ModifiersFlagsHandler(const olc::apis::macos::KeyEvent& data, bool pressed); bool bNumLockActive = true; // Num Lock state, we assume it's active at start }; diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 194e5e9a..e7b9b191 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -4133,6 +4133,7 @@ extern "C" { // Event handler setup void window_setKeyDownCallback (struct Window* self, KeyEventCallback callback, void* userData); void window_setKeyUpCallback (struct Window* self, KeyEventCallback callback, void* userData); + void window_setFlagsChangedCallback (Window* self, void (*callback)(unsigned int, void*), void* userData); void window_setMouseDownCallback (struct Window* self, MouseEventCallback callback, void* userData); void window_setMouseUpCallback (struct Window* self, MouseEventCallback callback, void* userData); void window_setMouseMovedCallback (struct Window* self, MouseEventCallback callback, void* userData); @@ -4829,6 +4830,19 @@ namespace olc { KeyEvent& operator=(const KeyEvent&) = default; }; + struct FlagsChangedEvent { + unsigned int modifierFlags; + + FlagsChangedEvent(unsigned int mods) noexcept + : modifierFlags(mods) {} + + // Move constructor and assignment for better performance + FlagsChangedEvent(FlagsChangedEvent&&) noexcept = default; + FlagsChangedEvent& operator=(FlagsChangedEvent&&) noexcept = default; + FlagsChangedEvent(const FlagsChangedEvent&) = default; + FlagsChangedEvent& operator=(const FlagsChangedEvent&) = default; + }; + // Mouse event data structure struct MouseEvent { double x, y; @@ -4861,6 +4875,7 @@ namespace olc { Window& window_; std::function keyDownHandler_; std::function keyUpHandler_; + std::function flagsChangedHandler_; std::function mouseDownHandler_; std::function mouseUpHandler_; std::function mouseMovedHandler_; @@ -4881,6 +4896,14 @@ namespace olc { (eventHandler->*handler)(KeyEvent(keyCode, characters, modifierFlags)); } } + + template + static void flagsChangedCallback(unsigned int modifierFlags, void* userData, HandlerType EventHandler::*handler) { + auto* eventHandler = static_cast(userData); + if (eventHandler && (eventHandler->*handler)) { + (eventHandler->*handler)(FlagsChangedEvent(modifierFlags)); + } + } template static void mouseCallback(double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData, HandlerType EventHandler::*handler) { @@ -4899,6 +4922,10 @@ namespace olc { keyCallback(keyCode, characters, modifierFlags, userData, &EventHandler::keyUpHandler_); } + static void flagsChangedCallback(unsigned int modifierFlags, void* userData) { + flagsChangedCallback(modifierFlags, userData, &EventHandler::flagsChangedHandler_); + } + static void mouseDownCallback(double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData) { mouseCallback(x, y, buttonNumber, modifierFlags, userData, &EventHandler::mouseDownHandler_); } @@ -4983,6 +5010,11 @@ namespace olc { window_setKeyUpCallback(window_.getCHandle(), keyUpCallback, this); } + void onFlagsChanged(std::function handler) { + flagsChangedHandler_ = std::move(handler); + window_setFlagsChangedCallback(window_.getCHandle(), flagsChangedCallback, this); + } + void onMouseDown(std::function handler) { mouseDownHandler_ = std::move(handler); window_setMouseDownCallback(window_.getCHandle(), mouseDownCallback, this); @@ -7487,68 +7519,89 @@ bool Host_Apple_MacOS::SyncWithDesktopComposite() } - bool Host_Apple_MacOS::ModifiersFlagsHandler(const olc::apis::macos::KeyEvent& event, bool pressed) { - - bool bisHandled = false; - if (event.modifierFlags & NSEventModifierFlagCapsLock) { - pPGEwindow->olc_OnKeyPress(Key::CAPS_LOCK, pressed); - } - if (event.modifierFlags & NSEventModifierFlagShift) { - pPGEwindow->olc_OnKeyPress(Key::SHIFT, pressed); - if(event.keyCode == 39) - { - // The @ symbol does not change position from US - UK keyboards on MacOS, so we handle it here - pPGEwindow->olc_OnKeyPress(mapKeys[50], pressed); - return true; - } - - } - if (event.modifierFlags & NSEventModifierFlagControl) { - pPGEwindow->olc_OnKeyPress(Key::CTRL, pressed); - } - - if(event.modifierFlags & NSEventModifierFlagNumericPad) { - if(event.keyCode == 71 && pressed) // NumLock keycode + void Host_Apple_MacOS::MacEventsHandler() + { + // General MacOS key event handling code here + // Reference: https://eastmanreference.com/complete-list-of-applescript-key-codes + + // Set up keyboard event handlers + pMacOSEventHandler->onKeyDown([&](const olc::apis::macos::KeyEvent& event) { + unsigned short keyCode = event.keyCode; + + if(!bNumLockActive) { - // We only tottle the NumLock state on key press to minic the latching of the key - bNumLockActive = !bNumLockActive; + // 84 down, 86 left, 88 right, 91 up >>> 125 down, 123 left, 124 right, 126 up + switch(keyCode) + { + case 84: keyCode = 125; break; + case 86: keyCode = 123; break; + case 88: keyCode = 124; break; + case 91: keyCode = 126; break; + default: break; + } } + + // The @ symbol does not change position from US - UK keyboards on MacOS, so we handle it here + if(event.modifierFlags & NSEventModifierFlagShift && event.keyCode == 39) + keyCode = 50; + pPGEwindow->olc_OnKeyPress(mapKeys[keyCode], true); + }); + + pMacOSEventHandler->onKeyUp([&](const olc::apis::macos::KeyEvent& event) { + unsigned short keyCode = event.keyCode; + if(!bNumLockActive) { // 84 down, 86 left, 88 right, 91 up >>> 125 down, 123 left, 124 right, 126 up - if(event.keyCode == 84) pPGEwindow->olc_OnKeyPress(mapKeys[125], pressed); - if(event.keyCode == 86) pPGEwindow->olc_OnKeyPress(mapKeys[123], pressed); - if(event.keyCode == 88) pPGEwindow->olc_OnKeyPress(mapKeys[124], pressed); - if(event.keyCode == 91) pPGEwindow->olc_OnKeyPress(mapKeys[126], pressed); - return true; + switch(keyCode) + { + case 84: keyCode = 125; break; + case 86: keyCode = 123; break; + case 88: keyCode = 124; break; + case 91: keyCode = 126; break; + default: break; + } } - - } + // The @ symbol does not change position from US - UK keyboards on MacOS, so we handle it here + if(event.modifierFlags & NSEventModifierFlagShift && event.keyCode == 39) + keyCode = 50; - return bisHandled; - - } + pPGEwindow->olc_OnKeyPress(mapKeys[keyCode], false); + }); + // Set up keyboard flag event handlers + pMacOSEventHandler->onFlagsChanged([&](const olc::apis::macos::FlagsChangedEvent& event) { - void Host_Apple_MacOS::MacEventsHandler() - { - // General MacOS key event handling code here - // Reference: https://eastmanreference.com/complete-list-of-applescript-key-codes + static unsigned int prevFlags = 0; + unsigned int changedFlags = event.modifierFlags ^ prevFlags; + + // Check For Shift key + if (changedFlags & NSEventModifierFlagShift) { + bool isPressed = event.modifierFlags & NSEventModifierFlagShift; + pPGEwindow->olc_OnKeyPress(Key::SHIFT, isPressed); + } + + // Check for Control key + if (changedFlags & NSEventModifierFlagControl) { + bool isPressed = event.modifierFlags & NSEventModifierFlagControl; + pPGEwindow->olc_OnKeyPress(Key::CTRL, isPressed); + } + + // Check for numeric pad modifier + if (changedFlags & NSEventModifierFlagNumericPad) { + bool isPressed = event.modifierFlags & NSEventModifierFlagNumericPad; + if(isPressed) + bNumLockActive = !bNumLockActive; - // Set up keyboard event handlers - pMacOSEventHandler->onKeyDown([&](const olc::apis::macos::KeyEvent& event) { - if(!ModifiersFlagsHandler(event, true)) - pPGEwindow->olc_OnKeyPress(mapKeys[event.keyCode], true); + std::cout << "NumLockActive " << bNumLockActive << "\n"; + } + + // caps lock doesn't appear to trigger any event + prevFlags = event.modifierFlags; }); - - pMacOSEventHandler->onKeyUp([&](const olc::apis::macos::KeyEvent& event) { - if(!ModifiersFlagsHandler(event, false)) - pPGEwindow->olc_OnKeyPress(mapKeys[event.keyCode], false); - }); - // Set up mouse event handlers pMacOSEventHandler->onMouseDown([&](const olc::apis::macos::MouseEvent& event) { pPGEwindow->olc_OnMouseButton(event.buttonNumber, true); @@ -7664,6 +7717,7 @@ static constexpr const char* kWindowDidDeminiaturizeSel = "windowDidDemi // NSResponder keyboard and mouse event methods selectors static constexpr const char* kKeyDownSel = "keyDown:"; static constexpr const char* kKeyUpSel = "keyUp:"; +static constexpr const char* kFlagsChangedSel = "flagsChanged:"; static constexpr const char* kMouseDownSel = "mouseDown:"; static constexpr const char* kMouseUpSel = "mouseUp:"; static constexpr const char* kMouseDraggedSel = "mouseDragged:"; @@ -7805,6 +7859,7 @@ namespace ObjectiveCSEL { // NSResponder keyboard and mouse event methods selectors static SEL keyDownSel = nullptr; static SEL keyUpSel = nullptr; + static SEL flagsChangedSel = nullptr; static SEL mouseDownSel = nullptr; static SEL mouseUpSel = nullptr; static SEL mouseDraggedSel = nullptr; @@ -7919,6 +7974,7 @@ namespace ObjectiveCSEL { // NSResponder keyboard and mouse event methods selectors keyDownSel = sel_registerName(kKeyDownSel); keyUpSel = sel_registerName(kKeyUpSel); + flagsChangedSel = sel_registerName(kFlagsChangedSel); mouseDownSel = sel_registerName(kMouseDownSel); mouseUpSel = sel_registerName(kMouseUpSel); mouseDraggedSel = sel_registerName(kMouseDraggedSel); @@ -8260,6 +8316,7 @@ struct Window { // Event callback function pointers with nullptr initialization void (*keyDownCallback) (unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData){nullptr}; void (*keyUpCallback) (unsigned short keyCode, const char* characters, unsigned int modifierFlags, void* userData){nullptr}; + void (*flagsChangedCallback) (unsigned int modifierFlags, void* userData){nullptr}; void (*mouseDownCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; void (*mouseUpCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; void (*mouseMovedCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; @@ -8459,6 +8516,15 @@ void view_keyUp(id self, SEL _cmd, id event) { } } +void view_flagsChanged(id self, SEL _cmd, id event) { + (void)self;(void)_cmd; + + unsigned int modifierFlags = ((unsigned int(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); + + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->flagsChangedCallback) [[likely]] { + gptrNSWindowEvents->flagsChangedCallback(modifierFlags, gptrNSWindowEvents->eventUserData); + } +} //====================================================================// // Mouse Event Handling @@ -8694,7 +8760,10 @@ Class createCustomOpenGLViewClass() { // Keyboard event handler methods class_addMethod(CustomViewClass, ObjectiveCSEL::keyDownSel, (IMP)view_keyDown, kEventHandlerMethodTypeEncoding); class_addMethod(CustomViewClass, ObjectiveCSEL::keyUpSel, (IMP)view_keyUp, kEventHandlerMethodTypeEncoding); - + + // Flags Changed event handler + class_addMethod(CustomViewClass, ObjectiveCSEL::flagsChangedSel, (IMP)view_flagsChanged, kEventHandlerMethodTypeEncoding); + // Mouse event handler methods class_addMethod(CustomViewClass, ObjectiveCSEL::mouseDownSel, (IMP)view_mouseDown, kEventHandlerMethodTypeEncoding); class_addMethod(CustomViewClass, ObjectiveCSEL::mouseUpSel, (IMP)view_mouseUp, kEventHandlerMethodTypeEncoding); @@ -9596,6 +9665,11 @@ extern "C" { self->keyUpCallback = callback; self->eventUserData = userData; } + + void window_setFlagsChangedCallback(Window* self, void (*callback)(unsigned int, void*), void* userData) { + self->flagsChangedCallback = callback; + self->eventUserData = userData; + } void window_setMouseDownCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { self->mouseDownCallback = callback; From 7ec650ad33e476dfd8303f36f8db6023e0998338 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Sun, 8 Feb 2026 23:10:13 -0500 Subject: [PATCH 34/58] [mac] make a dedicated keyboard event handler, support num clear/lock key --- dev/src/api_macos.h | 2 +- dev/src/host_apple_macos.cpp | 89 ++++++++++++++++----------------- dev/src/host_apple_macos.h | 2 +- olcPixelGameEngine3.h | 95 ++++++++++++++++-------------------- 4 files changed, 86 insertions(+), 102 deletions(-) diff --git a/dev/src/api_macos.h b/dev/src/api_macos.h index a3e76de0..66e561d1 100644 --- a/dev/src/api_macos.h +++ b/dev/src/api_macos.h @@ -106,7 +106,7 @@ extern "C" { // Event handler setup void window_setKeyDownCallback (struct Window* self, KeyEventCallback callback, void* userData); void window_setKeyUpCallback (struct Window* self, KeyEventCallback callback, void* userData); - void window_setFlagsChangedCallback (Window* self, void (*callback)(unsigned int, void*), void* userData); + void window_setFlagsChangedCallback (struct Window* self, void (*callback)(unsigned int, void*), void* userData); void window_setMouseDownCallback (struct Window* self, MouseEventCallback callback, void* userData); void window_setMouseUpCallback (struct Window* self, MouseEventCallback callback, void* userData); void window_setMouseMovedCallback (struct Window* self, MouseEventCallback callback, void* userData); diff --git a/dev/src/host_apple_macos.cpp b/dev/src/host_apple_macos.cpp index be804655..40434483 100644 --- a/dev/src/host_apple_macos.cpp +++ b/dev/src/host_apple_macos.cpp @@ -545,6 +545,38 @@ bool Host_Apple_MacOS::SyncWithDesktopComposite() }); } + + // handles both down and up strokes for every supported key that isn't a modifier + void Host_Apple_MacOS::KeyboardEventHandler(const olc::apis::macos::KeyEvent& event, bool isPressed) + { + unsigned short keyCode = event.keyCode; + + // handle num clear/lock key only on the down stroke. + if(isPressed && keyCode == 71) + { + bNumLockActive = !bNumLockActive; + return; + } + + if(!bNumLockActive) + { + // 84 down, 86 left, 88 right, 91 up >>> 125 down, 123 left, 124 right, 126 up + switch(keyCode) + { + case 84: keyCode = 125; break; + case 86: keyCode = 123; break; + case 88: keyCode = 124; break; + case 91: keyCode = 126; break; + default: break; + } + } + + // The @ symbol does not change position from US - UK keyboards on MacOS, so we handle it here + if(event.modifierFlags & NSEventModifierFlagShift && event.keyCode == 39) + keyCode = 50; + + pPGEwindow->olc_OnKeyPress(mapKeys[keyCode], isPressed); + } void Host_Apple_MacOS::MacEventsHandler() { @@ -553,49 +585,11 @@ bool Host_Apple_MacOS::SyncWithDesktopComposite() // Set up keyboard event handlers pMacOSEventHandler->onKeyDown([&](const olc::apis::macos::KeyEvent& event) { - unsigned short keyCode = event.keyCode; - - if(!bNumLockActive) - { - // 84 down, 86 left, 88 right, 91 up >>> 125 down, 123 left, 124 right, 126 up - switch(keyCode) - { - case 84: keyCode = 125; break; - case 86: keyCode = 123; break; - case 88: keyCode = 124; break; - case 91: keyCode = 126; break; - default: break; - } - } - - // The @ symbol does not change position from US - UK keyboards on MacOS, so we handle it here - if(event.modifierFlags & NSEventModifierFlagShift && event.keyCode == 39) - keyCode = 50; - - pPGEwindow->olc_OnKeyPress(mapKeys[keyCode], true); + KeyboardEventHandler(event, true); }); pMacOSEventHandler->onKeyUp([&](const olc::apis::macos::KeyEvent& event) { - unsigned short keyCode = event.keyCode; - - if(!bNumLockActive) - { - // 84 down, 86 left, 88 right, 91 up >>> 125 down, 123 left, 124 right, 126 up - switch(keyCode) - { - case 84: keyCode = 125; break; - case 86: keyCode = 123; break; - case 88: keyCode = 124; break; - case 91: keyCode = 126; break; - default: break; - } - } - - // The @ symbol does not change position from US - UK keyboards on MacOS, so we handle it here - if(event.modifierFlags & NSEventModifierFlagShift && event.keyCode == 39) - keyCode = 50; - - pPGEwindow->olc_OnKeyPress(mapKeys[keyCode], false); + KeyboardEventHandler(event, false); }); // Set up keyboard flag event handlers @@ -615,16 +609,15 @@ bool Host_Apple_MacOS::SyncWithDesktopComposite() bool isPressed = event.modifierFlags & NSEventModifierFlagControl; pPGEwindow->olc_OnKeyPress(Key::CTRL, isPressed); } - - // Check for numeric pad modifier - if (changedFlags & NSEventModifierFlagNumericPad) { - bool isPressed = event.modifierFlags & NSEventModifierFlagNumericPad; - if(isPressed) - bNumLockActive = !bNumLockActive; - std::cout << "NumLockActive " << bNumLockActive << "\n"; + if (changedFlags & NSEventModifierFlagCommand) { + bool isPressed = event.modifierFlags & NSEventModifierFlagCommand; + if(isPressed) + std::cout << "PGE3 doesn't currently support ALT/Command keys but it should.\n"; + + // pPGEwindow->olc_OnKeyPress(Key::ALT, isPressed); } - + // caps lock doesn't appear to trigger any event prevFlags = event.modifierFlags; }); diff --git a/dev/src/host_apple_macos.h b/dev/src/host_apple_macos.h index 62c6eeb1..38bdf8cb 100644 --- a/dev/src/host_apple_macos.h +++ b/dev/src/host_apple_macos.h @@ -138,7 +138,7 @@ namespace olc void MacWindowEventsHandler(); void MacEventsHandler(); void MacOpenGLContextEventsHandler(); - + void KeyboardEventHandler(const olc::apis::macos::KeyEvent& event, bool isPressed); bool bNumLockActive = true; // Num Lock state, we assume it's active at start }; diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index e7b9b191..242ee99e 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -4133,7 +4133,7 @@ extern "C" { // Event handler setup void window_setKeyDownCallback (struct Window* self, KeyEventCallback callback, void* userData); void window_setKeyUpCallback (struct Window* self, KeyEventCallback callback, void* userData); - void window_setFlagsChangedCallback (Window* self, void (*callback)(unsigned int, void*), void* userData); + void window_setFlagsChangedCallback (struct Window* self, void (*callback)(unsigned int, void*), void* userData); void window_setMouseDownCallback (struct Window* self, MouseEventCallback callback, void* userData); void window_setMouseUpCallback (struct Window* self, MouseEventCallback callback, void* userData); void window_setMouseMovedCallback (struct Window* self, MouseEventCallback callback, void* userData); @@ -5203,9 +5203,7 @@ namespace olc void MacWindowEventsHandler(); void MacEventsHandler(); void MacOpenGLContextEventsHandler(); - - // When modifier flag changes the keycode it will return true, else false - bool ModifiersFlagsHandler(const olc::apis::macos::KeyEvent& data, bool pressed); + void KeyboardEventHandler(const olc::apis::macos::KeyEvent& event, bool isPressed); bool bNumLockActive = true; // Num Lock state, we assume it's active at start }; @@ -7518,6 +7516,38 @@ bool Host_Apple_MacOS::SyncWithDesktopComposite() }); } + + // handles both down and up strokes for every supported key that isn't a modifier + void Host_Apple_MacOS::KeyboardEventHandler(const olc::apis::macos::KeyEvent& event, bool isPressed) + { + unsigned short keyCode = event.keyCode; + + // handle num clear/lock key only on the down stroke. + if(isPressed && keyCode == 71) + { + bNumLockActive = !bNumLockActive; + return; + } + + if(!bNumLockActive) + { + // 84 down, 86 left, 88 right, 91 up >>> 125 down, 123 left, 124 right, 126 up + switch(keyCode) + { + case 84: keyCode = 125; break; + case 86: keyCode = 123; break; + case 88: keyCode = 124; break; + case 91: keyCode = 126; break; + default: break; + } + } + + // The @ symbol does not change position from US - UK keyboards on MacOS, so we handle it here + if(event.modifierFlags & NSEventModifierFlagShift && event.keyCode == 39) + keyCode = 50; + + pPGEwindow->olc_OnKeyPress(mapKeys[keyCode], isPressed); + } void Host_Apple_MacOS::MacEventsHandler() { @@ -7526,49 +7556,11 @@ bool Host_Apple_MacOS::SyncWithDesktopComposite() // Set up keyboard event handlers pMacOSEventHandler->onKeyDown([&](const olc::apis::macos::KeyEvent& event) { - unsigned short keyCode = event.keyCode; - - if(!bNumLockActive) - { - // 84 down, 86 left, 88 right, 91 up >>> 125 down, 123 left, 124 right, 126 up - switch(keyCode) - { - case 84: keyCode = 125; break; - case 86: keyCode = 123; break; - case 88: keyCode = 124; break; - case 91: keyCode = 126; break; - default: break; - } - } - - // The @ symbol does not change position from US - UK keyboards on MacOS, so we handle it here - if(event.modifierFlags & NSEventModifierFlagShift && event.keyCode == 39) - keyCode = 50; - - pPGEwindow->olc_OnKeyPress(mapKeys[keyCode], true); + KeyboardEventHandler(event, true); }); pMacOSEventHandler->onKeyUp([&](const olc::apis::macos::KeyEvent& event) { - unsigned short keyCode = event.keyCode; - - if(!bNumLockActive) - { - // 84 down, 86 left, 88 right, 91 up >>> 125 down, 123 left, 124 right, 126 up - switch(keyCode) - { - case 84: keyCode = 125; break; - case 86: keyCode = 123; break; - case 88: keyCode = 124; break; - case 91: keyCode = 126; break; - default: break; - } - } - - // The @ symbol does not change position from US - UK keyboards on MacOS, so we handle it here - if(event.modifierFlags & NSEventModifierFlagShift && event.keyCode == 39) - keyCode = 50; - - pPGEwindow->olc_OnKeyPress(mapKeys[keyCode], false); + KeyboardEventHandler(event, false); }); // Set up keyboard flag event handlers @@ -7588,16 +7580,15 @@ bool Host_Apple_MacOS::SyncWithDesktopComposite() bool isPressed = event.modifierFlags & NSEventModifierFlagControl; pPGEwindow->olc_OnKeyPress(Key::CTRL, isPressed); } - - // Check for numeric pad modifier - if (changedFlags & NSEventModifierFlagNumericPad) { - bool isPressed = event.modifierFlags & NSEventModifierFlagNumericPad; - if(isPressed) - bNumLockActive = !bNumLockActive; - std::cout << "NumLockActive " << bNumLockActive << "\n"; + if (changedFlags & NSEventModifierFlagCommand) { + bool isPressed = event.modifierFlags & NSEventModifierFlagCommand; + if(isPressed) + std::cout << "PGE3 doesn't currently support ALT/Command keys but it should.\n"; + + // pPGEwindow->olc_OnKeyPress(Key::ALT, isPressed); } - + // caps lock doesn't appear to trigger any event prevFlags = event.modifierFlags; }); From c803f80160fac6c5d8fa675c9ea11525142bd0b8 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Tue, 10 Feb 2026 12:37:54 -0500 Subject: [PATCH 35/58] [mac] update header --- olcPixelGameEngine3.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 242ee99e..b4c46d1c 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -7850,7 +7850,7 @@ namespace ObjectiveCSEL { // NSResponder keyboard and mouse event methods selectors static SEL keyDownSel = nullptr; static SEL keyUpSel = nullptr; - static SEL flagsChangedSel = nullptr; + static SEL flagsChangedSel = nullptr; static SEL mouseDownSel = nullptr; static SEL mouseUpSel = nullptr; static SEL mouseDraggedSel = nullptr; From f3f710b216623c7c842de5c2b828190d06c10da0 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Tue, 10 Feb 2026 12:38:12 -0500 Subject: [PATCH 36/58] [examples] add inidcators for when shift and control are held --- examples/olcPGE3_Keyboard.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/examples/olcPGE3_Keyboard.cpp b/examples/olcPGE3_Keyboard.cpp index d8467dfb..565ce6af 100644 --- a/examples/olcPGE3_Keyboard.cpp +++ b/examples/olcPGE3_Keyboard.cpp @@ -58,7 +58,15 @@ class Example_Keyboard : public olc::PixelGameEngine if (keyboard.GetKey(olc::Key::RIGHT).bHeld) vPosition.x += 50.0f * fElapsedTime; - draw.FilledCircle(vPosition.round(), 10.0f); + olc::Pixel col = olc::Colour::WHITE; + + if(keyboard.GetKey(olc::Key::SHIFT).bHeld) + col = olc::Colour::MAGENTA; + + if(keyboard.GetKey(olc::Key::CTRL).bHeld) + col = olc::Colour::TANGERINE; + + draw.FilledCircle(vPosition.round(), 10.0f, col); // Capture text input from keyboard From 899d8805ac2cde7c980525ca06f95957b852d0e2 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Tue, 10 Feb 2026 13:33:40 -0500 Subject: [PATCH 37/58] remove misleading comment --- dev/src/host_web_emscripten.cpp | 2 +- olcPixelGameEngine3.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dev/src/host_web_emscripten.cpp b/dev/src/host_web_emscripten.cpp index f7408a1b..772c2c1c 100644 --- a/dev/src/host_web_emscripten.cpp +++ b/dev/src/host_web_emscripten.cpp @@ -412,7 +412,7 @@ namespace olc::host return EM_TRUE; } break; - case EMSCRIPTEN_EVENT_MOUSEUP: // deliberate fallthrough + case EMSCRIPTEN_EVENT_MOUSEUP: { auto it = pCallbackData->pHost->mapMouseButtons.find(e->button); if(it != pCallbackData->pHost->mapMouseButtons.end()) diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 7123d0d9..02d53747 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -11584,7 +11584,7 @@ namespace olc::host return EM_TRUE; } break; - case EMSCRIPTEN_EVENT_MOUSEUP: // deliberate fallthrough + case EMSCRIPTEN_EVENT_MOUSEUP: { auto it = pCallbackData->pHost->mapMouseButtons.find(e->button); if(it != pCallbackData->pHost->mapMouseButtons.end()) From a3ffcf3922c87d3dca3ec529db2fd435cf63cba8 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Tue, 10 Feb 2026 22:17:13 -0500 Subject: [PATCH 38/58] [examples] olc::Draw2D was removed, using olc::Draw instead --- examples/olcPGE3_Fish.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/olcPGE3_Fish.cpp b/examples/olcPGE3_Fish.cpp index 7b86eb18..de4e04e5 100644 --- a/examples/olcPGE3_Fish.cpp +++ b/examples/olcPGE3_Fish.cpp @@ -108,7 +108,7 @@ class Fish { return total_curve; } - void Draw(olc::Draw2D& pge) { + void Draw(olc::Draw& pge) { const auto world_transform = pge.GetWorldTransform(); const auto origin = olc::vf2d{0.0f, 0.0f}; const auto half_pi = std::numbers::pi_v / 2.0f; @@ -175,7 +175,7 @@ class Fish { } // Draw a simple tail triangle - void DrawTail(olc::Draw2D& pge) { + void DrawTail(olc::Draw& pge) { const auto world_transform = pge.GetWorldTransform(); const auto& tail_start = segments[segments.size() - 5]; const auto& end = segments[segments.size() - 1]; @@ -200,7 +200,7 @@ class Fish { } // Draw some fins on the body of the fish - void DrawFin(olc::Draw2D& pge, int segment_index, float size) { + void DrawFin(olc::Draw& pge, int segment_index, float size) { const auto world_transform = pge.GetWorldTransform(); const auto& segment = segments[segment_index]; const auto& prev_segment = segments[segment_index - 1]; @@ -244,7 +244,7 @@ class Fish { } // Draw the eyes on the head of the fish - void DrawEyes(olc::Draw2D& pge) { + void DrawEyes(olc::Draw& pge) { const auto world_transform = pge.GetWorldTransform(); const auto& head = segments[0]; olc::tf2d transform; @@ -268,7 +268,7 @@ class Fish { } // Draw a fin on the back of the fish - void DrawDorsalFin(olc::Draw2D& pge, int fin_start, size_t length, float size) { + void DrawDorsalFin(olc::Draw& pge, int fin_start, size_t length, float size) { const int fin_end = fin_start + length; const auto world_transform = pge.GetWorldTransform(); std::vector fin_points {length * 2}; From 23f6f3d7a5b1598eb566e7e140721e466e859413 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Tue, 10 Feb 2026 22:35:28 -0500 Subject: [PATCH 39/58] [emscripten][mouse] mouse event should only be consumed for middle/next/back buttons, not left/right --- dev/src/host_web_emscripten.cpp | 8 ++++---- olcPixelGameEngine3.h | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/dev/src/host_web_emscripten.cpp b/dev/src/host_web_emscripten.cpp index 772c2c1c..2a0a5ccd 100644 --- a/dev/src/host_web_emscripten.cpp +++ b/dev/src/host_web_emscripten.cpp @@ -408,8 +408,9 @@ namespace olc::host if(it != pCallbackData->pHost->mapMouseButtons.end()) { olc_OnMouseButton(pCallbackData->pWindow, it->second, true); + // middle/next/back buttons require the event to be consumed to prevent browser behavior + if(it->second >= 2) return EM_TRUE; } - return EM_TRUE; } break; case EMSCRIPTEN_EVENT_MOUSEUP: @@ -418,8 +419,9 @@ namespace olc::host if(it != pCallbackData->pHost->mapMouseButtons.end()) { olc_OnMouseButton(pCallbackData->pWindow, it->second, false); + // middle/next/back buttons require the event to be consumed to prevent browser behavior + if(it->second >= 2) return EM_TRUE; } - return EM_TRUE; } break; default: break; @@ -464,12 +466,10 @@ namespace olc::host if (eventType == EMSCRIPTEN_EVENT_BLUR) { - // ptrPGE->olc_UpdateKeyFocus(false); olc_OnMouseFocus(pCallbackData->pWindow, false); } else if (eventType == EMSCRIPTEN_EVENT_FOCUS) { - // ptrPGE->olc_UpdateKeyFocus(true); olc_OnMouseFocus(pCallbackData->pWindow, true); } diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 02d53747..a1b2ed3a 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -11580,8 +11580,9 @@ namespace olc::host if(it != pCallbackData->pHost->mapMouseButtons.end()) { olc_OnMouseButton(pCallbackData->pWindow, it->second, true); + // middle/next/back buttons require the event to be consumed to prevent browser behavior + if(it->second >= 2) return EM_TRUE; } - return EM_TRUE; } break; case EMSCRIPTEN_EVENT_MOUSEUP: @@ -11590,8 +11591,9 @@ namespace olc::host if(it != pCallbackData->pHost->mapMouseButtons.end()) { olc_OnMouseButton(pCallbackData->pWindow, it->second, false); + // middle/next/back buttons require the event to be consumed to prevent browser behavior + if(it->second >= 2) return EM_TRUE; } - return EM_TRUE; } break; default: break; @@ -11636,12 +11638,10 @@ namespace olc::host if (eventType == EMSCRIPTEN_EVENT_BLUR) { - // ptrPGE->olc_UpdateKeyFocus(false); olc_OnMouseFocus(pCallbackData->pWindow, false); } else if (eventType == EMSCRIPTEN_EVENT_FOCUS) { - // ptrPGE->olc_UpdateKeyFocus(true); olc_OnMouseFocus(pCallbackData->pWindow, true); } From 8b9902ea75c47b759415456d8de49d30c544cae0 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Mon, 9 Feb 2026 16:34:15 -0500 Subject: [PATCH 40/58] [winapi] add prev/next button support to mouse on windows --- dev/src/host_win_winapi.cpp | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/dev/src/host_win_winapi.cpp b/dev/src/host_win_winapi.cpp index b233244b..68dc16a3 100644 --- a/dev/src/host_win_winapi.cpp +++ b/dev/src/host_win_winapi.cpp @@ -479,6 +479,34 @@ namespace olc::host window->olc_OnMouseButton(2, false); break; } + case WM_XBUTTONDOWN: + { + UINT button = GET_XBUTTON_WPARAM(wParam); + if(button == XBUTTON1) + { + window->olc_OnMouseButton(3, true); + } + else if(button == XBUTTON2) + { + window->olc_OnMouseButton(4, true); + } + + break; + } + case WM_XBUTTONUP: + { + UINT button = GET_XBUTTON_WPARAM(wParam); + if(button == XBUTTON1) + { + window->olc_OnMouseButton(3, false); + } + else if(button == XBUTTON2) + { + window->olc_OnMouseButton(4, false); + } + + break; + } // case WM_DROPFILES: // { // // This is all eww... From f70679d3a8dbd25a64cbbbee59ea11d2de6108ac Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Wed, 11 Feb 2026 21:23:08 -0500 Subject: [PATCH 41/58] update header --- olcPixelGameEngine3.h | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 58c767af..6d16e678 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -6904,6 +6904,34 @@ namespace olc::host window->olc_OnMouseButton(2, false); break; } + case WM_XBUTTONDOWN: + { + UINT button = GET_XBUTTON_WPARAM(wParam); + if(button == XBUTTON1) + { + window->olc_OnMouseButton(3, true); + } + else if(button == XBUTTON2) + { + window->olc_OnMouseButton(4, true); + } + + break; + } + case WM_XBUTTONUP: + { + UINT button = GET_XBUTTON_WPARAM(wParam); + if(button == XBUTTON1) + { + window->olc_OnMouseButton(3, false); + } + else if(button == XBUTTON2) + { + window->olc_OnMouseButton(4, false); + } + + break; + } // case WM_DROPFILES: // { // // This is all eww... From 43139cc6d14436d37bb485620a760c33e239a94e Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Thu, 12 Feb 2026 13:44:37 -0500 Subject: [PATCH 42/58] [sh] fix typo --- dev/src/sh_template.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/src/sh_template.h b/dev/src/sh_template.h index 19b11159..9d2e89fa 100644 --- a/dev/src/sh_template.h +++ b/dev/src/sh_template.h @@ -94,7 +94,7 @@ Primary Contributors ~~~~~~~~~~~~~~~~~~~~ @javidx9 (aka David Barr, OneLoneCoder) - @Moros1198, @dandistine, @johnnyg63, @iCiaran, @DCubix + @Moros1138, @dandistine, @johnnyg63, @iCiaran, @DCubix With assistance from all of the developers of olc::PixelGameEngine 2 over the years, and the many community contributors that have provided bug fixes, suggestions, From 1e87cd7e0f732225f1b3442c1c3df9b84d9f2aee Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Thu, 12 Feb 2026 13:45:56 -0500 Subject: [PATCH 43/58] [sh] update header --- olcPixelGameEngine3.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 58c767af..15217f35 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -94,7 +94,7 @@ Primary Contributors ~~~~~~~~~~~~~~~~~~~~ @javidx9 (aka David Barr, OneLoneCoder) - @Moros1198, @dandistine, @johnnyg63, @iCiaran, @DCubix + @Moros1138, @dandistine, @johnnyg63, @iCiaran, @DCubix With assistance from all of the developers of olc::PixelGameEngine 2 over the years, and the many community contributors that have provided bug fixes, suggestions, From 61439641b88e10b80c6b1d30794ef12134c77af9 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Fri, 13 Feb 2026 14:17:26 -0500 Subject: [PATCH 44/58] [windows] add support for mingw cross compiling --- CMakeLists.txt | 4 ++-- dev/src/api_opengl.h | 6 +++++- dev/src/imload_wingdi.h | 2 ++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 935b0ba1..fba11b01 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,10 +26,10 @@ file(GLOB sources if(NOT EMSCRIPTEN) add_custom_command( OUTPUT olcPixelGameEngine3.h - COMMAND ${olcGimmeHead_BINARY_DIR}/${CMAKE_CFG_INTDIR}/gimme-head ${CMAKE_CURRENT_SOURCE_DIR}/dev/src/sh_template.h ${CMAKE_BINARY_DIR}/olcPixelGameEngine3.h + COMMAND ${olcGimmeHead_BINARY_DIR}/${CMAKE_CFG_INTDIR}/gimme-head${CMAKE_EXECUTABLE_SUFFIX} ${CMAKE_CURRENT_SOURCE_DIR}/dev/src/sh_template.h ${CMAKE_BINARY_DIR}/olcPixelGameEngine3.h WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/dev/src DEPENDS ${sources} - DEPENDS ${olcGimmeHead_BINARY_DIR}/${CMAKE_CFG_INTDIR}/gimme-head + DEPENDS ${olcGimmeHead_BINARY_DIR}/${CMAKE_CFG_INTDIR}/gimme-head${CMAKE_EXECUTABLE_SUFFIX} ) else() add_custom_command( diff --git a/dev/src/api_opengl.h b/dev/src/api_opengl.h index 31da05f5..4ae88951 100644 --- a/dev/src/api_opengl.h +++ b/dev/src/api_opengl.h @@ -16,10 +16,14 @@ //! START OPENGL_CONFIG #if OLC_HOST == OLC_HOST_WINDOWS - #include + #include #pragma comment(lib, "gdi32.lib") #pragma comment(lib, "opengl32.lib") +#if defined(__MINGW32__) || defined(__MINGW64__) + #include +#else #include +#endif #define CALLSTYLE __stdcall // ooof... was getting a bunch of spurious C4191 from MSVC 17.14.9, so round trip via void-town #define OGL_LOAD(t) reinterpret_cast(reinterpret_cast(wglGetProcAddress(#t))) diff --git a/dev/src/imload_wingdi.h b/dev/src/imload_wingdi.h index 86f87d0e..4f22450a 100644 --- a/dev/src/imload_wingdi.h +++ b/dev/src/imload_wingdi.h @@ -39,7 +39,9 @@ #pragma comment(lib, "Shlwapi.lib") #include #include +#if !defined(__MINGW32__) && !defined(__MINGW64__) #include +#endif #include #undef _WINSOCKAPI_ //! END WINAPI_CONFIG From cc55db15d4df57d472de54cca088e797919c4a23 Mon Sep 17 00:00:00 2001 From: dandistine <34634876+dandistine@users.noreply.github.com> Date: Fri, 13 Feb 2026 19:35:57 -0600 Subject: [PATCH 45/58] Implement window focus logic and hope javid doesn't change the window api. --- dev/src/host_lin_wayland.cpp | 23 +++++++++++++++++------ olcPixelGameEngine3.h | 23 +++++++++++++++++------ 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/dev/src/host_lin_wayland.cpp b/dev/src/host_lin_wayland.cpp index 1a5c34f5..cc67dcf2 100644 --- a/dev/src/host_lin_wayland.cpp +++ b/dev/src/host_lin_wayland.cpp @@ -506,16 +506,18 @@ namespace olc::host void Host_Linux_Wayland::pointer_frame(wl_pointer* pointer) { wayland::PointerState *event = &pointer_state; - + auto pointer_window = active_window_id; + // Since PGE does not distinguish between "mouse hover" and "window focus" we won't actually trigger a Window Focus + // for the mouse hovering over the window. We'll just send this mouse event to that window without actually marking it as focused. if (pointer_state.event_mask & wayland::PointerEventMask::PointerEventEnter) { for(auto& itr : mapUID2Window) { if (itr.second.surface == event->surface) { - active_window_id = itr.first; + pointer_window = itr.first; } } } - - auto* pge_window = mapUID2OlcWindow[active_window_id]; + + auto* pge_window = mapUID2OlcWindow[pointer_window]; if (pointer_state.event_mask & wayland::PointerEventMask::PointerEventMotion) { pge_window->olc_OnMouseMove(olc::vi2d{ @@ -639,7 +641,15 @@ namespace olc::host void Host_Linux_Wayland::keyboard_enter(wl_keyboard* keyboard, uint32_t serial, wl_surface* surface, wl_array* keys) { - // Currently do nothing + // Find the window that the keyboard is active on and mark it active + for(auto& i : mapUID2Window) { + if(i.second.surface == surface) { + active_window_id = i.first; + } + } + + auto* pge_window = mapUID2OlcWindow[active_window_id]; + pge_window->olc_OnMouseFocus(true); } void Host_Linux_Wayland::keyboard_leave_callback(void* data, wl_keyboard* keyboard, uint32_t serial, wl_surface* surface) @@ -650,7 +660,8 @@ namespace olc::host void Host_Linux_Wayland::keyboard_leave(wl_keyboard* keyboard, uint32_t serial, wl_surface* surface) { - // Currently do nothing + auto* pge_window = mapUID2OlcWindow[active_window_id]; + pge_window->olc_OnMouseFocus(false); } void Host_Linux_Wayland::keyboard_key_callback(void* data, wl_keyboard* keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 58c767af..7a872cd1 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -10707,16 +10707,18 @@ namespace olc::host void Host_Linux_Wayland::pointer_frame(wl_pointer* pointer) { wayland::PointerState *event = &pointer_state; - + auto pointer_window = active_window_id; + // Since PGE does not distinguish between "mouse hover" and "window focus" we won't actually trigger a Window Focus + // for the mouse hovering over the window. We'll just send this mouse event to that window without actually marking it as focused. if (pointer_state.event_mask & wayland::PointerEventMask::PointerEventEnter) { for(auto& itr : mapUID2Window) { if (itr.second.surface == event->surface) { - active_window_id = itr.first; + pointer_window = itr.first; } } } - - auto* pge_window = mapUID2OlcWindow[active_window_id]; + + auto* pge_window = mapUID2OlcWindow[pointer_window]; if (pointer_state.event_mask & wayland::PointerEventMask::PointerEventMotion) { pge_window->olc_OnMouseMove(olc::vi2d{ @@ -10840,7 +10842,15 @@ namespace olc::host void Host_Linux_Wayland::keyboard_enter(wl_keyboard* keyboard, uint32_t serial, wl_surface* surface, wl_array* keys) { - // Currently do nothing + // Find the window that the keyboard is active on and mark it active + for(auto& i : mapUID2Window) { + if(i.second.surface == surface) { + active_window_id = i.first; + } + } + + auto* pge_window = mapUID2OlcWindow[active_window_id]; + pge_window->olc_OnMouseFocus(true); } void Host_Linux_Wayland::keyboard_leave_callback(void* data, wl_keyboard* keyboard, uint32_t serial, wl_surface* surface) @@ -10851,7 +10861,8 @@ namespace olc::host void Host_Linux_Wayland::keyboard_leave(wl_keyboard* keyboard, uint32_t serial, wl_surface* surface) { - // Currently do nothing + auto* pge_window = mapUID2OlcWindow[active_window_id]; + pge_window->olc_OnMouseFocus(false); } void Host_Linux_Wayland::keyboard_key_callback(void* data, wl_keyboard* keyboard, uint32_t serial, uint32_t time, uint32_t key, uint32_t state) From 8f2a1d2791f8a8bbf875e243acb197d920c313a9 Mon Sep 17 00:00:00 2001 From: dandistine <34634876+dandistine@users.noreply.github.com> Date: Fri, 13 Feb 2026 19:42:42 -0600 Subject: [PATCH 46/58] Implement window focus logic --- dev/src/host_lin_x11.cpp | 20 ++++++++++++-------- olcPixelGameEngine3.h | 20 ++++++++++++-------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/dev/src/host_lin_x11.cpp b/dev/src/host_lin_x11.cpp index f777d9b3..2ad72944 100644 --- a/dev/src/host_lin_x11.cpp +++ b/dev/src/host_lin_x11.cpp @@ -224,14 +224,18 @@ namespace olc::host } } - // else if (xev.type == FocusIn) - // { - // ptrPGE->olc_UpdateKeyFocus(true); - // } - // else if (xev.type == FocusOut) - // { - // ptrPGE->olc_UpdateKeyFocus(false); - // } + else if (xev.type == FocusIn) + { + if(auto* pge_window = get_pge_window(xev.xbutton.window); pge_window) { + pge_window->olc_OnMouseFocus(true); + } + } + else if (xev.type == FocusOut) + { + if(auto* pge_window = get_pge_window(xev.xbutton.window); pge_window) { + pge_window->olc_OnMouseFocus(false); + } + } else if (xev.type == ClientMessage) { X11::XClientMessageEvent& xcme = xev.xclient; diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 58c767af..b4e3289b 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -10023,14 +10023,18 @@ namespace olc::host } } - // else if (xev.type == FocusIn) - // { - // ptrPGE->olc_UpdateKeyFocus(true); - // } - // else if (xev.type == FocusOut) - // { - // ptrPGE->olc_UpdateKeyFocus(false); - // } + else if (xev.type == FocusIn) + { + if(auto* pge_window = get_pge_window(xev.xbutton.window); pge_window) { + pge_window->olc_OnMouseFocus(true); + } + } + else if (xev.type == FocusOut) + { + if(auto* pge_window = get_pge_window(xev.xbutton.window); pge_window) { + pge_window->olc_OnMouseFocus(false); + } + } else if (xev.type == ClientMessage) { X11::XClientMessageEvent& xcme = xev.xclient; From f5a9191931c792cd3e2869fcfd6ed65b05147212 Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Sat, 14 Feb 2026 21:56:35 +0000 Subject: [PATCH 47/58] testing batched FilledPolygon - its slower! Yay! however, its given me a pattern for retaining individual tints in batches --- dev/src/draw.cpp | 71 +++++++++++++++++++++++++++++++++++++ dev/src/draw.h | 11 ++++-- olcPixelGameEngine3.h | 82 +++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 158 insertions(+), 6 deletions(-) diff --git a/dev/src/draw.cpp b/dev/src/draw.cpp index caf19ccf..51a32df9 100644 --- a/dev/src/draw.cpp +++ b/dev/src/draw.cpp @@ -184,6 +184,7 @@ olc::Draw::sDrawMetrics olc::Draw::GetDrawMetrics() const return drawMetrics; } + void olc::Draw::WorldReset() { transformAffine = olc::tf2d(); @@ -981,6 +982,76 @@ const GPUTask& olc::Draw::FilledPolygon(const olc::Structure structure, const st ))); } +const FilledBatch& olc::Draw::FilledPolygon(FilledBatch& batch, const olc::Structure structure, const std::vector& vecPoints, const std::vector& vecColours, const olc::Pixel tint) +{ + // TODO: Curiously, this approach is considerably slower than the naive approach of just + // calling FilledTriangle for each triangle in the polygon. I suspect this is due to the + // overhead of copying verts into the temporary buffer and then into the batch buffer, + // but it is worth investigating further. + // + // The challenge here is olc::Structure changes. Perhaps its worth flushing the batch + // when the structure changes, but this would prevent retaining composites for future + // reuse. + + auto pushTriangle = [&](const size_t idx, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel& c1, const olc::Pixel& c2, const olc::Pixel& c3) + { + batch.task.vertexBuffer[idx + 0] = { {p1.x, p1.y, 1.0f, 1.0f}, c1, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + batch.task.vertexBuffer[idx + 1] = { {p2.x, p2.y, 1.0f, 1.0f}, c2, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + batch.task.vertexBuffer[idx + 2] = { {p3.x, p3.y, 1.0f, 1.0f}, c3, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + }; + + // Transform unique verts into temporary buffer + buffPoints.data.clear(); + buffColours.data.clear(); + buffPoints.reserve(vecPoints.size()); + buffColours.reserve(vecColours.size()); + for (size_t i = 0; i < vecPoints.size(); i++) + { + buffPoints.data[i] = transformAffine.forwardRound(vecPoints[i]); + buffColours.data[i] = vecColours[i].blend(tint); + } + + switch (structure) + { + case olc::Structure::Fan: + { + size_t idx = batch.task.vertexBuffer.size(); + batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + (vecPoints.size() - 2) * 3); + + for (size_t i = 1; i < vecPoints.size() - 1; i++) + pushTriangle(idx + (i-1) * 3, + buffPoints.data[0], buffPoints.data[i], buffPoints.data[i + 1], + buffColours.data[0], buffColours.data[i], buffColours.data[i + 1]); + } + break; + + case olc::Structure::Strip: + { + size_t idx = batch.task.vertexBuffer.size(); + batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + (vecPoints.size() - 2) * 3); + + for (size_t i = 0; i < vecPoints.size() - 2; i++) + pushTriangle(idx + (i*3), + buffPoints.data[i], buffPoints.data[i + 1], buffPoints.data[i + 2], + buffColours.data[i], buffColours.data[i + 1], buffColours.data[i + 2]); + } + break; + + case olc::Structure::List: + { + size_t idx = batch.task.vertexBuffer.size(); + batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + (vecPoints.size() / 3)); + + for (size_t i = 0; i < vecPoints.size(); i += 3) + pushTriangle(idx + i, + buffPoints.data[i], buffPoints.data[i + 1], buffPoints.data[i + 2], + buffColours.data[i], buffColours.data[i + 1], buffColours.data[i + 2]); + } + } + + return batch; +} + const GPUTask& olc::Draw::TexturedPolygon(const olc::Structure structure, const std::vector& vecPoints, const std::vector& vecColours, const std::vector& vecTexCoords, olc::Image& texture, const olc::Pixel tint) { PrepareTargetForHW(); diff --git a/dev/src/draw.h b/dev/src/draw.h index e7c748f3..a79809b1 100644 --- a/dev/src/draw.h +++ b/dev/src/draw.h @@ -625,6 +625,14 @@ namespace olc const std::vector& vecColours, const olc::Pixel tint = olc::Colour::WHITE); + // Draws a filled polygon with multiple colours into a btach + const FilledBatch& FilledPolygon( + FilledBatch& batch, + const olc::Structure structure, + const std::vector& vecPoints, + const std::vector& vecColours, + const olc::Pixel tint = olc::Colour::WHITE); + // Draws a textured polygon with per vertex colouring const GPUTask& TexturedPolygon( const olc::Structure structure, @@ -911,9 +919,6 @@ namespace olc private: sDrawMetrics drawMetrics; - - - protected: // Checks residency of image resource, and brings it to cpu RAM for r/w void PrepareTargetForSW(); diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 670ecfc6..81910f76 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -3049,6 +3049,14 @@ namespace olc const std::vector& vecColours, const olc::Pixel tint = olc::Colour::WHITE); + // Draws a filled polygon with multiple colours into a btach + const FilledBatch& FilledPolygon( + FilledBatch& batch, + const olc::Structure structure, + const std::vector& vecPoints, + const std::vector& vecColours, + const olc::Pixel tint = olc::Colour::WHITE); + // Draws a textured polygon with per vertex colouring const GPUTask& TexturedPolygon( const olc::Structure structure, @@ -3335,9 +3343,6 @@ namespace olc private: sDrawMetrics drawMetrics; - - - protected: // Checks residency of image resource, and brings it to cpu RAM for r/w void PrepareTargetForSW(); @@ -14277,6 +14282,7 @@ olc::Draw::sDrawMetrics olc::Draw::GetDrawMetrics() const return drawMetrics; } + void olc::Draw::WorldReset() { transformAffine = olc::tf2d(); @@ -15074,6 +15080,76 @@ const GPUTask& olc::Draw::FilledPolygon(const olc::Structure structure, const st ))); } +const FilledBatch& olc::Draw::FilledPolygon(FilledBatch& batch, const olc::Structure structure, const std::vector& vecPoints, const std::vector& vecColours, const olc::Pixel tint) +{ + // TODO: Curiously, this approach is considerably slower than the naive approach of just + // calling FilledTriangle for each triangle in the polygon. I suspect this is due to the + // overhead of copying verts into the temporary buffer and then into the batch buffer, + // but it is worth investigating further. + // + // The challenge here is olc::Structure changes. Perhaps its worth flushing the batch + // when the structure changes, but this would prevent retaining composites for future + // reuse. + + auto pushTriangle = [&](const size_t idx, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel& c1, const olc::Pixel& c2, const olc::Pixel& c3) + { + batch.task.vertexBuffer[idx + 0] = { {p1.x, p1.y, 1.0f, 1.0f}, c1, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + batch.task.vertexBuffer[idx + 1] = { {p2.x, p2.y, 1.0f, 1.0f}, c2, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + batch.task.vertexBuffer[idx + 2] = { {p3.x, p3.y, 1.0f, 1.0f}, c3, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + }; + + // Transform unique verts into temporary buffer + buffPoints.data.clear(); + buffColours.data.clear(); + buffPoints.reserve(vecPoints.size()); + buffColours.reserve(vecColours.size()); + for (size_t i = 0; i < vecPoints.size(); i++) + { + buffPoints.data[i] = transformAffine.forwardRound(vecPoints[i]); + buffColours.data[i] = vecColours[i].blend(tint); + } + + switch (structure) + { + case olc::Structure::Fan: + { + size_t idx = batch.task.vertexBuffer.size(); + batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + (vecPoints.size() - 2) * 3); + + for (size_t i = 1; i < vecPoints.size() - 1; i++) + pushTriangle(idx + (i-1) * 3, + buffPoints.data[0], buffPoints.data[i], buffPoints.data[i + 1], + buffColours.data[0], buffColours.data[i], buffColours.data[i + 1]); + } + break; + + case olc::Structure::Strip: + { + size_t idx = batch.task.vertexBuffer.size(); + batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + (vecPoints.size() - 2) * 3); + + for (size_t i = 0; i < vecPoints.size() - 2; i++) + pushTriangle(idx + (i*3), + buffPoints.data[i], buffPoints.data[i + 1], buffPoints.data[i + 2], + buffColours.data[i], buffColours.data[i + 1], buffColours.data[i + 2]); + } + break; + + case olc::Structure::List: + { + size_t idx = batch.task.vertexBuffer.size(); + batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + (vecPoints.size() / 3)); + + for (size_t i = 0; i < vecPoints.size(); i += 3) + pushTriangle(idx + i, + buffPoints.data[i], buffPoints.data[i + 1], buffPoints.data[i + 2], + buffColours.data[i], buffColours.data[i + 1], buffColours.data[i + 2]); + } + } + + return batch; +} + const GPUTask& olc::Draw::TexturedPolygon(const olc::Structure structure, const std::vector& vecPoints, const std::vector& vecColours, const std::vector& vecTexCoords, olc::Image& texture, const olc::Pixel tint) { PrepareTargetForHW(); From 36fdc43fe8db9798b26c518820580c466896f470 Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Sat, 14 Feb 2026 22:04:40 +0000 Subject: [PATCH 48/58] pulled batch routines into seperate cpp file --- dev/msvc/olcPGE3.vcxproj | 1 + dev/msvc/olcPGE3.vcxproj.filters | 3 + dev/src/draw.cpp | 395 +-------------- dev/src/draw_batch.cpp | 433 ++++++++++++++++ dev/src/sh_template.h | 1 + olcPixelGameEngine3.h | 836 ++++++++++++++++--------------- 6 files changed, 875 insertions(+), 794 deletions(-) create mode 100644 dev/src/draw_batch.cpp diff --git a/dev/msvc/olcPGE3.vcxproj b/dev/msvc/olcPGE3.vcxproj index 0a987d69..d7b7a70b 100644 --- a/dev/msvc/olcPGE3.vcxproj +++ b/dev/msvc/olcPGE3.vcxproj @@ -279,6 +279,7 @@ + diff --git a/dev/msvc/olcPGE3.vcxproj.filters b/dev/msvc/olcPGE3.vcxproj.filters index 27ae7899..58f63f12 100644 --- a/dev/msvc/olcPGE3.vcxproj.filters +++ b/dev/msvc/olcPGE3.vcxproj.filters @@ -245,5 +245,8 @@ Hosts + + Source Files + \ No newline at end of file diff --git a/dev/src/draw.cpp b/dev/src/draw.cpp index 51a32df9..91208319 100644 --- a/dev/src/draw.cpp +++ b/dev/src/draw.cpp @@ -403,10 +403,6 @@ const GPUTask& Draw::Line(const olc::vf2d& p1, const olc::vf2d& p2, const olc::P ))); } -const LineBatch& olc::Draw::Line(olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::Pixel col) -{ - return Line(batch, p1, col, p2, col); -} const GPUTask& Draw::Line(const olc::vf2d& p1, const olc::Pixel c1, const olc::vf2d& p2, const olc::Pixel c2, const olc::Pixel tint) { @@ -421,16 +417,6 @@ const GPUTask& Draw::Line(const olc::vf2d& p1, const olc::Pixel c1, const olc::v ))); } -const LineBatch& olc::Draw::Line(olc::LineBatch& batch, const olc::vf2d& p1, const olc::Pixel c1, const olc::vf2d& p2, const olc::Pixel c2) -{ - batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + 2); - size_t idx = batch.task.vertexBuffer.size() - 2; - const olc::vf2d a1 = transformAffine.forwardRound(p1); - const olc::vf2d a2 = transformAffine.forwardRound(p2); - batch.task.vertexBuffer[idx + 0] = { {a1.x, a1.y, 1.0f, 1.0f}, c1, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; - batch.task.vertexBuffer[idx + 1] = { {a2.x, a2.y, 1.0f, 1.0f}, c2, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; - return batch; -} const GPUTask& olc::Draw::Rect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col, const olc::Pixel tint) { @@ -453,10 +439,6 @@ const GPUTask& olc::Draw::Rect(const olc::vf2d& pos, const olc::vf2d& size, cons ))); } -const LineBatch& olc::Draw::Rect(olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) -{ - return Rect(batch, pos, size, col, col, col, col); -} const GPUTask& olc::Draw::Rect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR, const olc::Pixel tint) { @@ -476,21 +458,6 @@ const GPUTask& olc::Draw::Rect(const olc::vf2d& pos, const olc::vf2d& size, cons ))); } -const LineBatch& olc::Draw::Rect(olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR) -{ - const olc::vf2d pTL = olc::vf2d(pos.x, pos.y); - const olc::vf2d pTR = olc::vf2d(pos.x + size.x, pos.y); - const olc::vf2d pBR = olc::vf2d(pos.x + size.x, pos.y + size.y); - const olc::vf2d pBL = olc::vf2d(pos.x, pos.y + size.y); - - Line(batch, pTL, colTL, pTR, colTR); - Line(batch, pTR, colTR, pBR, colBR); - Line(batch, pBR, colBR, pBL, colBL); - Line(batch, pBL, colBL, pTL, colTL); - - return batch; - -} const GPUTask& olc::Draw::FilledRect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col, const olc::Pixel tint) @@ -510,12 +477,7 @@ const GPUTask& olc::Draw::FilledRect(const olc::vf2d& pos, const olc::vf2d& size ))); } -const FilledBatch& olc::Draw::FilledRect(olc::FilledBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) -{ - FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y), olc::vf2d(pos.x + size.x, pos.y + size.y), col, col, col); - FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y + size.y), olc::vf2d(pos.x, pos.y + size.y), col, col, col); - return batch; -} + const GPUTask& olc::Draw::FilledRect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR, const olc::Pixel tint) { @@ -533,12 +495,6 @@ const GPUTask& olc::Draw::FilledRect(const olc::vf2d& pos, const olc::vf2d& size ))); } -const FilledBatch& olc::Draw::FilledRect(olc::FilledBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR) -{ - FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y), olc::vf2d(pos.x + size.x, pos.y + size.y), colTL, colTR, colBR); - FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y + size.y), olc::vf2d(pos.x, pos.y + size.y), colTL, colBR, colBL); - return batch; -} void olc::Draw::RedefineUnitCircleBuffer(const int32_t nFacets) { @@ -555,30 +511,18 @@ const GPUTask& olc::Draw::Circle(const olc::vf2d& pos, const float& radius, cons return Ellipse(pos, radius, radius, col, tint, nFacets); } -const LineBatch& olc::Draw::Circle(olc::LineBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel col, int32_t nFacets) -{ - return Ellipse(batch, pos, radius, radius, col, nFacets); -} const GPUTask& olc::Draw::FilledCircle(const olc::vf2d& pos, const float& radius, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { return FilledEllipse(pos, radius, radius, col, tint, nFacets); } -const FilledBatch& olc::Draw::FilledCircle(olc::FilledBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel col, int32_t nFacets) -{ - return FilledEllipse(batch, pos, radius, radius, col, nFacets); -} const GPUTask& olc::Draw::FilledCircle(const olc::vf2d& pos, const float& radius, const olc::Pixel colInner, const olc::Pixel colOuter, const olc::Pixel tint, int32_t nFacets) { return FilledEllipse(pos, radius, radius, colInner, colOuter, tint, nFacets); } -const FilledBatch& olc::Draw::FilledCircle(olc::FilledBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel colInner, const olc::Pixel colOuter, int32_t nFacets) -{ - return FilledEllipse(batch, pos, radius, radius, colInner, colOuter, nFacets); -} const GPUTask& olc::Draw::Ellipse(const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { @@ -606,34 +550,12 @@ const GPUTask& olc::Draw::Ellipse(const olc::vf2d& pos, const float& rx, const f ))); } -const LineBatch& olc::Draw::Ellipse(olc::LineBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, int32_t nFacets) -{ - // Fundamental for batched outline circle/ellipse with colour solid/gradient - - if (nFacets != int32_t(buffUnitCirclePoints.data.size() - 1)) - RedefineUnitCircleBuffer(nFacets); - - for (int32_t i = 0; i <= nFacets; i++) - { - const olc::vf2d a1 = { buffUnitCirclePoints.data[i].x * rx + pos.x, buffUnitCirclePoints.data[i].y * ry + pos.y }; - const olc::vf2d a2 = { buffUnitCirclePoints.data[(i + 1) % buffUnitCirclePoints.data.size()].x * rx + pos.x, - buffUnitCirclePoints.data[(i + 1) % buffUnitCirclePoints.data.size()].y * ry + pos.y }; - - Line(batch, a1, col, a2, col); - } - - return batch; -} const GPUTask& olc::Draw::FilledEllipse(const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { return FilledEllipse(pos, rx, ry, col, col, tint, nFacets); } -const FilledBatch& olc::Draw::FilledEllipse(olc::FilledBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, int32_t nFacets) -{ - return FilledEllipse(batch, pos, rx, ry, col, col, nFacets); -} const GPUTask& olc::Draw::FilledEllipse(const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel colInner, const olc::Pixel colOuter, const olc::Pixel tint, int32_t nFacets) { @@ -664,26 +586,6 @@ const GPUTask& olc::Draw::FilledEllipse(const olc::vf2d& pos, const float& rx, c ))); } -const FilledBatch& olc::Draw::FilledEllipse(olc::FilledBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel colInner, const olc::Pixel colOuter, int32_t nFacets) -{ - // Fundamental for batch filled circle/ellipse with colour solid/gradient - - if (nFacets != int32_t(buffUnitCirclePoints.data.size() - 1)) - RedefineUnitCircleBuffer(nFacets); - - for (int32_t i = 0; i <= nFacets; i++) - { - olc::vf2d p1 = { pos.x + rx * buffUnitCirclePoints.data[i].x, - pos.y + ry * buffUnitCirclePoints.data[i].y }; - - olc::vf2d p2 = { pos.x + rx * buffUnitCirclePoints.data[(i + 1) % buffUnitCirclePoints.data.size()].x, - pos.y + ry * buffUnitCirclePoints.data[(i + 1) % buffUnitCirclePoints.data.size()].y }; - - FilledTriangle(batch, pos, p1, p2, colInner, colOuter, colOuter); - } - - return batch; -} const GPUTask& olc::Draw::RoundedRect(const olc::vf2d& pos, const olc::vf2d& size, const float& radius, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { @@ -735,51 +637,6 @@ const GPUTask& olc::Draw::RoundedRect(const olc::vf2d& pos, const olc::vf2d& siz ))); } -const LineBatch& olc::Draw::RoundedRect(olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const float& radius, const olc::Pixel col, int32_t nFacets) -{ - buffPoints.reserve((nFacets + 1) * 4 + 1); - buffPoints.data.clear(); - - olc::vf2d adjustedPos = pos + olc::vf2d(radius, radius); - olc::vf2d adjustedSize = size - olc::vf2d(2.0f * radius, 2.0f * radius); - - // Top Left - for (int32_t i = 0; i <= nFacets; i++) - { - float theta = (float(i) / float(nFacets)) * (0.5f * 3.14159265358979323846f) - (1.0f * 3.14159265358979323846f); - buffPoints.data.push_back({ adjustedPos.x + radius * cosf(theta), adjustedPos.y + radius * sinf(theta) }); - } - - // Top Right - for (int32_t i = 0; i <= nFacets; i++) - { - float theta = (float(i) / float(nFacets)) * (0.5f * 3.14159265358979323846f) - (0.5f * 3.14159265358979323846f); - buffPoints.data.push_back({ adjustedPos.x + adjustedSize.x + radius * cosf(theta), adjustedPos.y + radius * sinf(theta) }); - } - - // Bottom Right - for (int32_t i = 0; i <= nFacets; i++) - { - float theta = (float(i) / float(nFacets)) * (0.5f * 3.14159265358979323846f) + (0.0f * 3.14159265358979323846f); - buffPoints.data.push_back({ adjustedPos.x + adjustedSize.x + radius * cosf(theta), adjustedPos.y + adjustedSize.y + radius * sinf(theta) }); - } - - // Bottom Left - for (int32_t i = 0; i <= nFacets; i++) - { - float theta = (float(i) / float(nFacets)) * (0.5f * 3.14159265358979323846f) + (0.5f * 3.14159265358979323846f); - buffPoints.data.push_back({ adjustedPos.x + radius * cosf(theta), adjustedPos.y + adjustedSize.y + radius * sinf(theta) }); - } - - buffPoints.data.push_back({ adjustedPos.x - radius, adjustedPos.y }); - - for (size_t i = 0; i < buffPoints.data.size() - 1; i++) - { - Line(batch, buffPoints.data[i], col, buffPoints.data[i + 1], col); - } - - return batch; -} const GPUTask& olc::Draw::FilledRoundedRect(const olc::vf2d& pos, const olc::vf2d& size, const float& radius, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { @@ -838,10 +695,6 @@ const GPUTask& olc::Draw::Triangle(const olc::vf2d& p1, const olc::vf2d& p2, con return Triangle(p1, p2, p3, col, col, col, tint); } -const LineBatch& olc::Draw::Triangle(olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col) -{ - return Triangle(batch, p1, p2, p3, col, col, col); -} const GPUTask& olc::Draw::Triangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3, const olc::Pixel tint) { @@ -856,24 +709,12 @@ const GPUTask& olc::Draw::Triangle(const olc::vf2d& p1, const olc::vf2d& p2, con ))); } -const LineBatch& olc::Draw::Triangle(olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3) -{ - Line(batch, p1, c1, p2, c2); - Line(batch, p2, c2, p3, c3); - Line(batch, p3, c3, p1, c1); - return batch; -} const GPUTask& olc::Draw::FilledTriangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col, const olc::Pixel tint) { return FilledTriangle(p1, p2, p3, col, col, col, tint); } -const FilledBatch& olc::Draw::FilledTriangle(olc::FilledBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col) -{ - return FilledTriangle(batch, p1, p2, p3, col, col, col); -} - const GPUTask& olc::Draw::FilledTriangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3, const olc::Pixel tint) { PrepareTargetForHW(); @@ -887,18 +728,6 @@ const GPUTask& olc::Draw::FilledTriangle(const olc::vf2d& p1, const olc::vf2d& p ))); } -const FilledBatch& olc::Draw::FilledTriangle(olc::FilledBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3) -{ - batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + 3); - size_t idx = batch.task.vertexBuffer.size() - 3; - const olc::vf2d a1 = transformAffine.forwardRound(p1); - const olc::vf2d a2 = transformAffine.forwardRound(p2); - const olc::vf2d a3 = transformAffine.forwardRound(p3); - batch.task.vertexBuffer[idx + 0] = { {a1.x, a1.y, 1.0f, 1.0f}, c1, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; - batch.task.vertexBuffer[idx + 1] = { {a2.x, a2.y, 1.0f, 1.0f}, c2, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; - batch.task.vertexBuffer[idx + 2] = { {a3.x, a3.y, 1.0f, 1.0f}, c3, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; - return batch; -} const GPUTask& olc::Draw::TexturedTriangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3, const olc::vf2d& t1, const olc::vf2d& t2, const olc::vf2d& t3, olc::Image& texture, const olc::Pixel tint) { @@ -921,30 +750,12 @@ const GPUTask& olc::Draw::Polygon(const std::vector& vecPoints, const return Polygon(olc::Structure::LineLoop, vecPoints, std::vector(vecPoints.size(), col), tint); } -const LineBatch& olc::Draw::Polygon(olc::LineBatch& batch, const std::vector& vecPoints, const olc::Pixel col) -{ - for (size_t i = 0; i < vecPoints.size(); i++) - { - Line(batch, vecPoints[i], col, vecPoints[(i + 1) % vecPoints.size()], col); - } - - return batch; -} const GPUTask& olc::Draw::Polygon(const std::vector& vecPoints, const std::vector& vecColours, const olc::Pixel tint) { return Polygon(olc::Structure::LineLoop, vecPoints, vecColours, tint); } -const LineBatch& olc::Draw::Polygon(olc::LineBatch& batch, const std::vector& vecPoints, const std::vector& vecColours) -{ - for(size_t i = 0; i& vecPoints, const olc::Pixel col, const olc::Pixel tint) { @@ -982,75 +793,6 @@ const GPUTask& olc::Draw::FilledPolygon(const olc::Structure structure, const st ))); } -const FilledBatch& olc::Draw::FilledPolygon(FilledBatch& batch, const olc::Structure structure, const std::vector& vecPoints, const std::vector& vecColours, const olc::Pixel tint) -{ - // TODO: Curiously, this approach is considerably slower than the naive approach of just - // calling FilledTriangle for each triangle in the polygon. I suspect this is due to the - // overhead of copying verts into the temporary buffer and then into the batch buffer, - // but it is worth investigating further. - // - // The challenge here is olc::Structure changes. Perhaps its worth flushing the batch - // when the structure changes, but this would prevent retaining composites for future - // reuse. - - auto pushTriangle = [&](const size_t idx, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel& c1, const olc::Pixel& c2, const olc::Pixel& c3) - { - batch.task.vertexBuffer[idx + 0] = { {p1.x, p1.y, 1.0f, 1.0f}, c1, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; - batch.task.vertexBuffer[idx + 1] = { {p2.x, p2.y, 1.0f, 1.0f}, c2, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; - batch.task.vertexBuffer[idx + 2] = { {p3.x, p3.y, 1.0f, 1.0f}, c3, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; - }; - - // Transform unique verts into temporary buffer - buffPoints.data.clear(); - buffColours.data.clear(); - buffPoints.reserve(vecPoints.size()); - buffColours.reserve(vecColours.size()); - for (size_t i = 0; i < vecPoints.size(); i++) - { - buffPoints.data[i] = transformAffine.forwardRound(vecPoints[i]); - buffColours.data[i] = vecColours[i].blend(tint); - } - - switch (structure) - { - case olc::Structure::Fan: - { - size_t idx = batch.task.vertexBuffer.size(); - batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + (vecPoints.size() - 2) * 3); - - for (size_t i = 1; i < vecPoints.size() - 1; i++) - pushTriangle(idx + (i-1) * 3, - buffPoints.data[0], buffPoints.data[i], buffPoints.data[i + 1], - buffColours.data[0], buffColours.data[i], buffColours.data[i + 1]); - } - break; - - case olc::Structure::Strip: - { - size_t idx = batch.task.vertexBuffer.size(); - batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + (vecPoints.size() - 2) * 3); - - for (size_t i = 0; i < vecPoints.size() - 2; i++) - pushTriangle(idx + (i*3), - buffPoints.data[i], buffPoints.data[i + 1], buffPoints.data[i + 2], - buffColours.data[i], buffColours.data[i + 1], buffColours.data[i + 2]); - } - break; - - case olc::Structure::List: - { - size_t idx = batch.task.vertexBuffer.size(); - batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + (vecPoints.size() / 3)); - - for (size_t i = 0; i < vecPoints.size(); i += 3) - pushTriangle(idx + i, - buffPoints.data[i], buffPoints.data[i + 1], buffPoints.data[i + 2], - buffColours.data[i], buffColours.data[i + 1], buffColours.data[i + 2]); - } - } - - return batch; -} const GPUTask& olc::Draw::TexturedPolygon(const olc::Structure structure, const std::vector& vecPoints, const std::vector& vecColours, const std::vector& vecTexCoords, olc::Image& texture, const olc::Pixel tint) { @@ -1215,57 +957,6 @@ ImageBatch olc::Draw::CreateImageBatch(olc::Image &image) return b; } -const GPUTask& olc::Draw::Batch(olc::ImageBatch& batch, const olc::Pixel tint) -{ - batch.task.tint = tint; - return vecGPUTasks.data.emplace_back(batch.task); -} - -FilledBatch olc::Draw::CreateFilledBatch() -{ - FilledBatch b; - b.task.structure = olc::Structure::List; - return b; -} - -const GPUTask& olc::Draw::Batch(olc::FilledBatch& batch, const olc::Pixel tint) -{ - batch.task.tint = tint; - return vecGPUTasks.data.emplace_back(batch.task); -} - -LineBatch olc::Draw::CreateLineBatch() -{ - LineBatch b; - b.task.structure = olc::Structure::LineList; - return b; -} - -const GPUTask& olc::Draw::Batch(olc::LineBatch& batch, const olc::Pixel tint) -{ - batch.task.tint = tint; - return vecGPUTasks.data.emplace_back(batch.task); -} - -const ImageBatch& olc::Draw::Image(ImageBatch& batch, olc::ImageRegion image, const olc::vf2d& pos, const olc::vf2d& scale, const olc::Pixel tint) -{ - // Add quad to existing task - olc::vf2d size = image.regionsize * scale; - - olc::vf2d p0 = transformAffine.forward(olc::vf2d{ pos.x, pos.y }); - olc::vf2d p1 = transformAffine.forward(olc::vf2d{ pos.x + size.x, pos.y }); - olc::vf2d p2 = transformAffine.forward(olc::vf2d{ pos.x + size.x, pos.y + size.y }); - olc::vf2d p3 = transformAffine.forward(olc::vf2d{ pos.x, pos.y + size.y }); - - //batch.task.vertexBuffer.reserve(batch.task.vertexBuffer.size() + 6); // NOTE!! This tanked performance on large batches - batch.task.vertexBuffer.push_back({ {p0.x, p0.y, 1.0f, 1.0f}, tint, {image.coords[0].x, image.coords[0].y}, {0, 0}, {0, 0}, {0, 0} }); - batch.task.vertexBuffer.push_back({ {p1.x, p1.y, 1.0f, 1.0f}, tint, {image.coords[1].x, image.coords[1].y}, {0, 0}, {0, 0}, {0, 0} }); - batch.task.vertexBuffer.push_back({ {p2.x, p2.y, 1.0f, 1.0f}, tint, {image.coords[2].x, image.coords[2].y}, {0, 0}, {0, 0}, {0, 0} }); - batch.task.vertexBuffer.push_back({ {p0.x, p0.y, 1.0f, 1.0f}, tint, {image.coords[0].x, image.coords[0].y}, {0, 0}, {0, 0}, {0, 0} }); - batch.task.vertexBuffer.push_back({ {p2.x, p2.y, 1.0f, 1.0f}, tint, {image.coords[2].x, image.coords[2].y}, {0, 0}, {0, 0}, {0, 0} }); - batch.task.vertexBuffer.push_back({ {p3.x, p3.y, 1.0f, 1.0f}, tint, {image.coords[3].x, image.coords[3].y}, {0, 0}, {0, 0}, {0, 0} }); - return batch; -} const GPUTask& olc::Draw::Image(olc::ImageRegion image, const olc::vf2d& pos, const olc::vf2d& scale, const olc::Pixel tint) { @@ -1326,35 +1017,6 @@ const GPUTask& olc::Draw::ImageRotated(olc::ImageRegion image, const olc::vf2d& ))); } -const ImageBatch& olc::Draw::ImageRotated(olc::ImageBatch& batch, olc::ImageRegion image, const olc::vf2d& pos, const float theta, const olc::vf2d& center, const olc::vf2d& scale, const olc::Pixel tint) -{ - // Add quad to existing task - olc::vf2d size = image.regionsize * scale; - - std::array vPoints; - vPoints[0] = (olc::vf2d(0.0f, 0.0f) - center) * scale; - vPoints[1] = (olc::vf2d(size.x, 0.0f) - center) * scale; - vPoints[2] = (size - center) * scale; - vPoints[3] = (olc::vf2d(0.0f, size.y) - center) * scale; - - float c = cos(theta), s = sin(theta); - for (size_t i = 0; i < 4; i++) - vPoints[i] = pos + olc::vf2d(vPoints[i].x * c - vPoints[i].y * s, vPoints[i].x * s + vPoints[i].y * c); - - olc::vf2d p0 = transformAffine.forward(vPoints[0]); - olc::vf2d p1 = transformAffine.forward(vPoints[1]); - olc::vf2d p2 = transformAffine.forward(vPoints[2]); - olc::vf2d p3 = transformAffine.forward(vPoints[3]); - - //batch.task.vertexBuffer.reserve(batch.task.vertexBuffer.size() + 6); - batch.task.vertexBuffer.push_back({ {p0.x, p0.y, 1.0f, 1.0f}, tint, {image.coords[0].x, image.coords[0].y}, {0, 0}, {0, 0}, {0, 0} }); - batch.task.vertexBuffer.push_back({ {p1.x, p1.y, 1.0f, 1.0f}, tint, {image.coords[1].x, image.coords[1].y}, {0, 0}, {0, 0}, {0, 0} }); - batch.task.vertexBuffer.push_back({ {p2.x, p2.y, 1.0f, 1.0f}, tint, {image.coords[2].x, image.coords[2].y}, {0, 0}, {0, 0}, {0, 0} }); - batch.task.vertexBuffer.push_back({ {p0.x, p0.y, 1.0f, 1.0f}, tint, {image.coords[0].x, image.coords[0].y}, {0, 0}, {0, 0}, {0, 0} }); - batch.task.vertexBuffer.push_back({ {p2.x, p2.y, 1.0f, 1.0f}, tint, {image.coords[2].x, image.coords[2].y}, {0, 0}, {0, 0}, {0, 0} }); - batch.task.vertexBuffer.push_back({ {p3.x, p3.y, 1.0f, 1.0f}, tint, {image.coords[3].x, image.coords[3].y}, {0, 0}, {0, 0}, {0, 0} }); - return batch; -} const GPUTask& olc::Draw::ImageQuad(olc::ImageRegion image, const olc::vf2d& vTL, const olc::vf2d& vTR, const olc::vf2d& vBR, const olc::vf2d& vBL, const olc::Pixel tint) { @@ -1411,61 +1073,12 @@ const GPUTask& olc::Draw::ImageQuad(olc::ImageRegion image, const olc::vf2d& vTL ))); } -const ImageBatch& olc::Draw::ImageQuad(olc::ImageBatch& batch, olc::ImageRegion image, const olc::vf2d& vTL, const olc::vf2d& vTR, const olc::vf2d& vBR, const olc::vf2d& vBL, const olc::Pixel tint) -{ - float rd = ((vBR.x - vTL.x) * (vTR.y - vBL.y) - (vTR.x - vBL.x) * (vBR.y - vTL.y)); - if (rd != 0) - { - rd = 1.0f / rd; - float rn = ((vTR.x - vBL.x) * (vTL.y - vBL.y) - (vTR.y - vBL.y) * (vTL.x - vBL.x)) * rd; - float sn = ((vBR.x - vTL.x) * (vTL.y - vBL.y) - (vBR.y - vTL.y) * (vTL.x - vBL.x)) * rd; - - olc::vf2d center; - if (!(rn < 0.f || rn > 1.f || sn < 0.f || sn > 1.f)) - center = vTL + rn * (vBR - vTL); - - std::array d = { { - (vTL - center).mag(), - (vTR - center).mag(), - (vBR - center).mag(), - (vBL - center).mag(), - } }; - - std::array q = { { - d[0] == 0.0f ? 1.0f : (d[0] + d[2]) / d[2], - d[1] == 0.0f ? 1.0f : (d[1] + d[3]) / d[3], - d[2] == 0.0f ? 1.0f : (d[2] + d[0]) / d[0], - d[3] == 0.0f ? 1.0f : (d[3] + d[1]) / d[1], - } }; - - olc::vf2d p0 = transformAffine.forward(vTL); - olc::vf2d p1 = transformAffine.forward(vTR); - olc::vf2d p2 = transformAffine.forward(vBR); - olc::vf2d p3 = transformAffine.forward(vBL); - - //batch.task.vertexBuffer.reserve(batch.task.vertexBuffer.size() + 6); - batch.task.vertexBuffer.push_back({ {p0.x, p0.y, q[0], 1.0f}, tint, {q[0] * image.coords[0].x, q[0] * image.coords[0].y}, {0, 0}, {0, 0}, {0, 0}}); - batch.task.vertexBuffer.push_back({ {p1.x, p1.y, q[1], 1.0f}, tint, {q[1] * image.coords[1].x, q[1] * image.coords[1].y}, {0, 0}, {0, 0}, {0, 0}}); - batch.task.vertexBuffer.push_back({ {p2.x, p2.y, q[2], 1.0f}, tint, {q[2] * image.coords[2].x, q[2] * image.coords[2].y}, {0, 0}, {0, 0}, {0, 0}}); - batch.task.vertexBuffer.push_back({ {p0.x, p0.y, q[0], 1.0f}, tint, {q[0] * image.coords[0].x, q[0] * image.coords[0].y}, {0, 0}, {0, 0}, {0, 0}}); - batch.task.vertexBuffer.push_back({ {p2.x, p2.y, q[2], 1.0f}, tint, {q[2] * image.coords[2].x, q[2] * image.coords[2].y}, {0, 0}, {0, 0}, {0, 0}}); - batch.task.vertexBuffer.push_back({ {p3.x, p3.y, q[3], 1.0f}, tint, {q[3] * image.coords[3].x, q[3] * image.coords[3].y}, {0, 0}, {0, 0}, {0, 0}}); - return batch; - } - - // Default is just return a textured quad - return Draw::Image(batch, image, vTL, vBR - vTL, tint); -} const GPUTask& olc::Draw::ImageQuad(olc::ImageRegion image, const std::vector& vecPoints, const olc::Pixel tint) { return ImageQuad(image, vecPoints[0], vecPoints[1], vecPoints[2], vecPoints[3], tint); } -const ImageBatch& olc::Draw::ImageQuad(olc::ImageBatch& batch, olc::ImageRegion image, const std::vector& vecPoints, const olc::Pixel tint) -{ - return ImageQuad(batch, image, vecPoints[0], vecPoints[1], vecPoints[2], vecPoints[3], tint); -} const GPUTask& olc::Draw::ImageRect(olc::ImageRegion image, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel tint) @@ -1486,12 +1099,6 @@ const GPUTask& olc::Draw::ImageRect(olc::ImageRegion image, const olc::vf2d& pos ))); } -const ImageBatch& olc::Draw::ImageRect(olc::ImageBatch& batch, olc::ImageRegion image, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel tint) -{ - olc_IgnoreUnused(image, pos, size, tint); - // TODO: Implement this function - return batch; -} void olc::Draw::SetCullMode(const olc::GPUTask::CullMode mode) { diff --git a/dev/src/draw_batch.cpp b/dev/src/draw_batch.cpp new file mode 100644 index 00000000..3c9edb3d --- /dev/null +++ b/dev/src/draw_batch.cpp @@ -0,0 +1,433 @@ +#include "draw.h" + +//! START IMPLEMENTATION +using namespace olc; + +const GPUTask& olc::Draw::Batch(olc::ImageBatch& batch, const olc::Pixel tint) +{ + batch.task.tint = tint; + return vecGPUTasks.data.emplace_back(batch.task); +} + +FilledBatch olc::Draw::CreateFilledBatch() +{ + FilledBatch b; + b.task.structure = olc::Structure::List; + return b; +} + +const GPUTask& olc::Draw::Batch(olc::FilledBatch& batch, const olc::Pixel tint) +{ + batch.task.tint = tint; + return vecGPUTasks.data.emplace_back(batch.task); +} + +LineBatch olc::Draw::CreateLineBatch() +{ + LineBatch b; + b.task.structure = olc::Structure::LineList; + return b; +} + +const GPUTask& olc::Draw::Batch(olc::LineBatch& batch, const olc::Pixel tint) +{ + batch.task.tint = tint; + return vecGPUTasks.data.emplace_back(batch.task); +} + +const LineBatch& olc::Draw::Line(olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::Pixel col) +{ + return Line(batch, p1, col, p2, col); +} + +const LineBatch& olc::Draw::Line(olc::LineBatch& batch, const olc::vf2d& p1, const olc::Pixel c1, const olc::vf2d& p2, const olc::Pixel c2) +{ + batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + 2); + size_t idx = batch.task.vertexBuffer.size() - 2; + const olc::vf2d a1 = transformAffine.forwardRound(p1); + const olc::vf2d a2 = transformAffine.forwardRound(p2); + batch.task.vertexBuffer[idx + 0] = { {a1.x, a1.y, 1.0f, 1.0f}, c1, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + batch.task.vertexBuffer[idx + 1] = { {a2.x, a2.y, 1.0f, 1.0f}, c2, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + return batch; +} + +const LineBatch& olc::Draw::Rect(olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) +{ + return Rect(batch, pos, size, col, col, col, col); +} + +const LineBatch& olc::Draw::Rect(olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR) +{ + const olc::vf2d pTL = olc::vf2d(pos.x, pos.y); + const olc::vf2d pTR = olc::vf2d(pos.x + size.x, pos.y); + const olc::vf2d pBR = olc::vf2d(pos.x + size.x, pos.y + size.y); + const olc::vf2d pBL = olc::vf2d(pos.x, pos.y + size.y); + + Line(batch, pTL, colTL, pTR, colTR); + Line(batch, pTR, colTR, pBR, colBR); + Line(batch, pBR, colBR, pBL, colBL); + Line(batch, pBL, colBL, pTL, colTL); + + return batch; +} + +const FilledBatch& olc::Draw::FilledRect(olc::FilledBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) +{ + FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y), olc::vf2d(pos.x + size.x, pos.y + size.y), col, col, col); + FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y + size.y), olc::vf2d(pos.x, pos.y + size.y), col, col, col); + return batch; +} + +const FilledBatch& olc::Draw::FilledRect(olc::FilledBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR) +{ + FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y), olc::vf2d(pos.x + size.x, pos.y + size.y), colTL, colTR, colBR); + FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y + size.y), olc::vf2d(pos.x, pos.y + size.y), colTL, colBR, colBL); + return batch; +} + +const LineBatch& olc::Draw::Circle(olc::LineBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel col, int32_t nFacets) +{ + return Ellipse(batch, pos, radius, radius, col, nFacets); +} + +const FilledBatch& olc::Draw::FilledCircle(olc::FilledBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel col, int32_t nFacets) +{ + return FilledEllipse(batch, pos, radius, radius, col, nFacets); +} + +const FilledBatch& olc::Draw::FilledCircle(olc::FilledBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel colInner, const olc::Pixel colOuter, int32_t nFacets) +{ + return FilledEllipse(batch, pos, radius, radius, colInner, colOuter, nFacets); +} + +const LineBatch& olc::Draw::Ellipse(olc::LineBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, int32_t nFacets) +{ + // Fundamental for batched outline circle/ellipse with colour solid/gradient + + if (nFacets != int32_t(buffUnitCirclePoints.data.size() - 1)) + RedefineUnitCircleBuffer(nFacets); + + for (int32_t i = 0; i <= nFacets; i++) + { + const olc::vf2d a1 = { buffUnitCirclePoints.data[i].x * rx + pos.x, buffUnitCirclePoints.data[i].y * ry + pos.y }; + const olc::vf2d a2 = { buffUnitCirclePoints.data[(i + 1) % buffUnitCirclePoints.data.size()].x * rx + pos.x, + buffUnitCirclePoints.data[(i + 1) % buffUnitCirclePoints.data.size()].y * ry + pos.y }; + + Line(batch, a1, col, a2, col); + } + + return batch; +} + +const FilledBatch& olc::Draw::FilledEllipse(olc::FilledBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, int32_t nFacets) +{ + return FilledEllipse(batch, pos, rx, ry, col, col, nFacets); +} + +const FilledBatch& olc::Draw::FilledEllipse(olc::FilledBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel colInner, const olc::Pixel colOuter, int32_t nFacets) +{ + // Fundamental for batch filled circle/ellipse with colour solid/gradient + + if (nFacets != int32_t(buffUnitCirclePoints.data.size() - 1)) + RedefineUnitCircleBuffer(nFacets); + + for (int32_t i = 0; i <= nFacets; i++) + { + olc::vf2d p1 = { pos.x + rx * buffUnitCirclePoints.data[i].x, + pos.y + ry * buffUnitCirclePoints.data[i].y }; + + olc::vf2d p2 = { pos.x + rx * buffUnitCirclePoints.data[(i + 1) % buffUnitCirclePoints.data.size()].x, + pos.y + ry * buffUnitCirclePoints.data[(i + 1) % buffUnitCirclePoints.data.size()].y }; + + FilledTriangle(batch, pos, p1, p2, colInner, colOuter, colOuter); + } + + return batch; +} + + +const LineBatch& olc::Draw::RoundedRect(olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const float& radius, const olc::Pixel col, int32_t nFacets) +{ + buffPoints.reserve((nFacets + 1) * 4 + 1); + buffPoints.data.clear(); + + olc::vf2d adjustedPos = pos + olc::vf2d(radius, radius); + olc::vf2d adjustedSize = size - olc::vf2d(2.0f * radius, 2.0f * radius); + + // Top Left + for (int32_t i = 0; i <= nFacets; i++) + { + float theta = (float(i) / float(nFacets)) * (0.5f * 3.14159265358979323846f) - (1.0f * 3.14159265358979323846f); + buffPoints.data.push_back({ adjustedPos.x + radius * cosf(theta), adjustedPos.y + radius * sinf(theta) }); + } + + // Top Right + for (int32_t i = 0; i <= nFacets; i++) + { + float theta = (float(i) / float(nFacets)) * (0.5f * 3.14159265358979323846f) - (0.5f * 3.14159265358979323846f); + buffPoints.data.push_back({ adjustedPos.x + adjustedSize.x + radius * cosf(theta), adjustedPos.y + radius * sinf(theta) }); + } + + // Bottom Right + for (int32_t i = 0; i <= nFacets; i++) + { + float theta = (float(i) / float(nFacets)) * (0.5f * 3.14159265358979323846f) + (0.0f * 3.14159265358979323846f); + buffPoints.data.push_back({ adjustedPos.x + adjustedSize.x + radius * cosf(theta), adjustedPos.y + adjustedSize.y + radius * sinf(theta) }); + } + + // Bottom Left + for (int32_t i = 0; i <= nFacets; i++) + { + float theta = (float(i) / float(nFacets)) * (0.5f * 3.14159265358979323846f) + (0.5f * 3.14159265358979323846f); + buffPoints.data.push_back({ adjustedPos.x + radius * cosf(theta), adjustedPos.y + adjustedSize.y + radius * sinf(theta) }); + } + + buffPoints.data.push_back({ adjustedPos.x - radius, adjustedPos.y }); + + for (size_t i = 0; i < buffPoints.data.size() - 1; i++) + { + Line(batch, buffPoints.data[i], col, buffPoints.data[i + 1], col); + } + + return batch; +} + + +const LineBatch& olc::Draw::Triangle(olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col) +{ + return Triangle(batch, p1, p2, p3, col, col, col); +} + +const LineBatch& olc::Draw::Triangle(olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3) +{ + Line(batch, p1, c1, p2, c2); + Line(batch, p2, c2, p3, c3); + Line(batch, p3, c3, p1, c1); + return batch; +} + + + +const FilledBatch& olc::Draw::FilledTriangle(olc::FilledBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col) +{ + return FilledTriangle(batch, p1, p2, p3, col, col, col); +} + +const FilledBatch& olc::Draw::FilledTriangle(olc::FilledBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3) +{ + batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + 3); + size_t idx = batch.task.vertexBuffer.size() - 3; + const olc::vf2d a1 = transformAffine.forwardRound(p1); + const olc::vf2d a2 = transformAffine.forwardRound(p2); + const olc::vf2d a3 = transformAffine.forwardRound(p3); + batch.task.vertexBuffer[idx + 0] = { {a1.x, a1.y, 1.0f, 1.0f}, c1, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + batch.task.vertexBuffer[idx + 1] = { {a2.x, a2.y, 1.0f, 1.0f}, c2, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + batch.task.vertexBuffer[idx + 2] = { {a3.x, a3.y, 1.0f, 1.0f}, c3, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + return batch; +} + + +const LineBatch& olc::Draw::Polygon(olc::LineBatch& batch, const std::vector& vecPoints, const olc::Pixel col) +{ + for (size_t i = 0; i < vecPoints.size(); i++) + { + Line(batch, vecPoints[i], col, vecPoints[(i + 1) % vecPoints.size()], col); + } + + return batch; +} + +const LineBatch& olc::Draw::Polygon(olc::LineBatch& batch, const std::vector& vecPoints, const std::vector& vecColours) +{ + for (size_t i = 0; i < vecPoints.size(); i++) + { + Line(batch, vecPoints[i], vecColours[i], vecPoints[(i + 1) % vecPoints.size()], vecColours[(i + 1) % vecColours.size()]); + } + + return batch; +} + +const FilledBatch& olc::Draw::FilledPolygon(FilledBatch& batch, const olc::Structure structure, const std::vector& vecPoints, const std::vector& vecColours, const olc::Pixel tint) +{ + // TODO: Curiously, this approach is considerably slower than the naive approach of just + // calling FilledTriangle for each triangle in the polygon. I suspect this is due to the + // overhead of copying verts into the temporary buffer and then into the batch buffer, + // but it is worth investigating further. + // + // The challenge here is olc::Structure changes. Perhaps its worth flushing the batch + // when the structure changes, but this would prevent retaining composites for future + // reuse. + + auto pushTriangle = [&](const size_t idx, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel& c1, const olc::Pixel& c2, const olc::Pixel& c3) + { + batch.task.vertexBuffer[idx + 0] = { {p1.x, p1.y, 1.0f, 1.0f}, c1, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + batch.task.vertexBuffer[idx + 1] = { {p2.x, p2.y, 1.0f, 1.0f}, c2, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + batch.task.vertexBuffer[idx + 2] = { {p3.x, p3.y, 1.0f, 1.0f}, c3, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + }; + + // Transform unique verts into temporary buffer + buffPoints.data.clear(); + buffColours.data.clear(); + buffPoints.reserve(vecPoints.size()); + buffColours.reserve(vecColours.size()); + for (size_t i = 0; i < vecPoints.size(); i++) + { + buffPoints.data[i] = transformAffine.forwardRound(vecPoints[i]); + buffColours.data[i] = vecColours[i].blend(tint); + } + + switch (structure) + { + case olc::Structure::Fan: + { + size_t idx = batch.task.vertexBuffer.size(); + batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + (vecPoints.size() - 2) * 3); + + for (size_t i = 1; i < vecPoints.size() - 1; i++) + pushTriangle(idx + (i - 1) * 3, + buffPoints.data[0], buffPoints.data[i], buffPoints.data[i + 1], + buffColours.data[0], buffColours.data[i], buffColours.data[i + 1]); + } + break; + + case olc::Structure::Strip: + { + size_t idx = batch.task.vertexBuffer.size(); + batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + (vecPoints.size() - 2) * 3); + + for (size_t i = 0; i < vecPoints.size() - 2; i++) + pushTriangle(idx + (i * 3), + buffPoints.data[i], buffPoints.data[i + 1], buffPoints.data[i + 2], + buffColours.data[i], buffColours.data[i + 1], buffColours.data[i + 2]); + } + break; + + case olc::Structure::List: + { + size_t idx = batch.task.vertexBuffer.size(); + batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + (vecPoints.size() / 3)); + + for (size_t i = 0; i < vecPoints.size(); i += 3) + pushTriangle(idx + i, + buffPoints.data[i], buffPoints.data[i + 1], buffPoints.data[i + 2], + buffColours.data[i], buffColours.data[i + 1], buffColours.data[i + 2]); + } + } + + return batch; +} + + + +const ImageBatch& olc::Draw::Image(ImageBatch& batch, olc::ImageRegion image, const olc::vf2d& pos, const olc::vf2d& scale, const olc::Pixel tint) +{ + // Add quad to existing task + olc::vf2d size = image.regionsize * scale; + + olc::vf2d p0 = transformAffine.forward(olc::vf2d{ pos.x, pos.y }); + olc::vf2d p1 = transformAffine.forward(olc::vf2d{ pos.x + size.x, pos.y }); + olc::vf2d p2 = transformAffine.forward(olc::vf2d{ pos.x + size.x, pos.y + size.y }); + olc::vf2d p3 = transformAffine.forward(olc::vf2d{ pos.x, pos.y + size.y }); + + //batch.task.vertexBuffer.reserve(batch.task.vertexBuffer.size() + 6); // NOTE!! This tanked performance on large batches + batch.task.vertexBuffer.push_back({ {p0.x, p0.y, 1.0f, 1.0f}, tint, {image.coords[0].x, image.coords[0].y}, {0, 0}, {0, 0}, {0, 0} }); + batch.task.vertexBuffer.push_back({ {p1.x, p1.y, 1.0f, 1.0f}, tint, {image.coords[1].x, image.coords[1].y}, {0, 0}, {0, 0}, {0, 0} }); + batch.task.vertexBuffer.push_back({ {p2.x, p2.y, 1.0f, 1.0f}, tint, {image.coords[2].x, image.coords[2].y}, {0, 0}, {0, 0}, {0, 0} }); + batch.task.vertexBuffer.push_back({ {p0.x, p0.y, 1.0f, 1.0f}, tint, {image.coords[0].x, image.coords[0].y}, {0, 0}, {0, 0}, {0, 0} }); + batch.task.vertexBuffer.push_back({ {p2.x, p2.y, 1.0f, 1.0f}, tint, {image.coords[2].x, image.coords[2].y}, {0, 0}, {0, 0}, {0, 0} }); + batch.task.vertexBuffer.push_back({ {p3.x, p3.y, 1.0f, 1.0f}, tint, {image.coords[3].x, image.coords[3].y}, {0, 0}, {0, 0}, {0, 0} }); + return batch; +} + +const ImageBatch& olc::Draw::ImageRotated(olc::ImageBatch& batch, olc::ImageRegion image, const olc::vf2d& pos, const float theta, const olc::vf2d& center, const olc::vf2d& scale, const olc::Pixel tint) +{ + // Add quad to existing task + olc::vf2d size = image.regionsize * scale; + + std::array vPoints; + vPoints[0] = (olc::vf2d(0.0f, 0.0f) - center) * scale; + vPoints[1] = (olc::vf2d(size.x, 0.0f) - center) * scale; + vPoints[2] = (size - center) * scale; + vPoints[3] = (olc::vf2d(0.0f, size.y) - center) * scale; + + float c = cos(theta), s = sin(theta); + for (size_t i = 0; i < 4; i++) + vPoints[i] = pos + olc::vf2d(vPoints[i].x * c - vPoints[i].y * s, vPoints[i].x * s + vPoints[i].y * c); + + olc::vf2d p0 = transformAffine.forward(vPoints[0]); + olc::vf2d p1 = transformAffine.forward(vPoints[1]); + olc::vf2d p2 = transformAffine.forward(vPoints[2]); + olc::vf2d p3 = transformAffine.forward(vPoints[3]); + + //batch.task.vertexBuffer.reserve(batch.task.vertexBuffer.size() + 6); + batch.task.vertexBuffer.push_back({ {p0.x, p0.y, 1.0f, 1.0f}, tint, {image.coords[0].x, image.coords[0].y}, {0, 0}, {0, 0}, {0, 0} }); + batch.task.vertexBuffer.push_back({ {p1.x, p1.y, 1.0f, 1.0f}, tint, {image.coords[1].x, image.coords[1].y}, {0, 0}, {0, 0}, {0, 0} }); + batch.task.vertexBuffer.push_back({ {p2.x, p2.y, 1.0f, 1.0f}, tint, {image.coords[2].x, image.coords[2].y}, {0, 0}, {0, 0}, {0, 0} }); + batch.task.vertexBuffer.push_back({ {p0.x, p0.y, 1.0f, 1.0f}, tint, {image.coords[0].x, image.coords[0].y}, {0, 0}, {0, 0}, {0, 0} }); + batch.task.vertexBuffer.push_back({ {p2.x, p2.y, 1.0f, 1.0f}, tint, {image.coords[2].x, image.coords[2].y}, {0, 0}, {0, 0}, {0, 0} }); + batch.task.vertexBuffer.push_back({ {p3.x, p3.y, 1.0f, 1.0f}, tint, {image.coords[3].x, image.coords[3].y}, {0, 0}, {0, 0}, {0, 0} }); + return batch; +} + +const ImageBatch& olc::Draw::ImageQuad(olc::ImageBatch& batch, olc::ImageRegion image, const olc::vf2d& vTL, const olc::vf2d& vTR, const olc::vf2d& vBR, const olc::vf2d& vBL, const olc::Pixel tint) +{ + float rd = ((vBR.x - vTL.x) * (vTR.y - vBL.y) - (vTR.x - vBL.x) * (vBR.y - vTL.y)); + if (rd != 0) + { + rd = 1.0f / rd; + float rn = ((vTR.x - vBL.x) * (vTL.y - vBL.y) - (vTR.y - vBL.y) * (vTL.x - vBL.x)) * rd; + float sn = ((vBR.x - vTL.x) * (vTL.y - vBL.y) - (vBR.y - vTL.y) * (vTL.x - vBL.x)) * rd; + + olc::vf2d center; + if (!(rn < 0.f || rn > 1.f || sn < 0.f || sn > 1.f)) + center = vTL + rn * (vBR - vTL); + + std::array d = { { + (vTL - center).mag(), + (vTR - center).mag(), + (vBR - center).mag(), + (vBL - center).mag(), + } }; + + std::array q = { { + d[0] == 0.0f ? 1.0f : (d[0] + d[2]) / d[2], + d[1] == 0.0f ? 1.0f : (d[1] + d[3]) / d[3], + d[2] == 0.0f ? 1.0f : (d[2] + d[0]) / d[0], + d[3] == 0.0f ? 1.0f : (d[3] + d[1]) / d[1], + } }; + + olc::vf2d p0 = transformAffine.forward(vTL); + olc::vf2d p1 = transformAffine.forward(vTR); + olc::vf2d p2 = transformAffine.forward(vBR); + olc::vf2d p3 = transformAffine.forward(vBL); + + //batch.task.vertexBuffer.reserve(batch.task.vertexBuffer.size() + 6); + batch.task.vertexBuffer.push_back({ {p0.x, p0.y, q[0], 1.0f}, tint, {q[0] * image.coords[0].x, q[0] * image.coords[0].y}, {0, 0}, {0, 0}, {0, 0} }); + batch.task.vertexBuffer.push_back({ {p1.x, p1.y, q[1], 1.0f}, tint, {q[1] * image.coords[1].x, q[1] * image.coords[1].y}, {0, 0}, {0, 0}, {0, 0} }); + batch.task.vertexBuffer.push_back({ {p2.x, p2.y, q[2], 1.0f}, tint, {q[2] * image.coords[2].x, q[2] * image.coords[2].y}, {0, 0}, {0, 0}, {0, 0} }); + batch.task.vertexBuffer.push_back({ {p0.x, p0.y, q[0], 1.0f}, tint, {q[0] * image.coords[0].x, q[0] * image.coords[0].y}, {0, 0}, {0, 0}, {0, 0} }); + batch.task.vertexBuffer.push_back({ {p2.x, p2.y, q[2], 1.0f}, tint, {q[2] * image.coords[2].x, q[2] * image.coords[2].y}, {0, 0}, {0, 0}, {0, 0} }); + batch.task.vertexBuffer.push_back({ {p3.x, p3.y, q[3], 1.0f}, tint, {q[3] * image.coords[3].x, q[3] * image.coords[3].y}, {0, 0}, {0, 0}, {0, 0} }); + return batch; + } + + // Default is just return a textured quad + return Draw::Image(batch, image, vTL, vBR - vTL, tint); +} + +const ImageBatch& olc::Draw::ImageQuad(olc::ImageBatch& batch, olc::ImageRegion image, const std::vector& vecPoints, const olc::Pixel tint) +{ + return ImageQuad(batch, image, vecPoints[0], vecPoints[1], vecPoints[2], vecPoints[3], tint); +} + +const ImageBatch& olc::Draw::ImageRect(olc::ImageBatch& batch, olc::ImageRegion image, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel tint) +{ + olc_IgnoreUnused(image, pos, size, tint); + // TODO: Implement this function + return batch; +} + + + + +//! END IMPLEMENTATION \ No newline at end of file diff --git a/dev/src/sh_template.h b/dev/src/sh_template.h index 6b8cc639..744b318a 100644 --- a/dev/src/sh_template.h +++ b/dev/src/sh_template.h @@ -273,6 +273,7 @@ #if defined(OLC_PGE3_APPLICATION) && !defined(PGE_DRAW_IMPLEMENTED) //! GRAB draw.cpp IMPLEMENTATION +//! GRAB draw_batch.cpp IMPLEMENTATION #define PGE_DRAW_IMPLEMENTED 1 #endif diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 81910f76..8609e443 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -14501,10 +14501,6 @@ const GPUTask& Draw::Line(const olc::vf2d& p1, const olc::vf2d& p2, const olc::P ))); } -const LineBatch& olc::Draw::Line(olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::Pixel col) -{ - return Line(batch, p1, col, p2, col); -} const GPUTask& Draw::Line(const olc::vf2d& p1, const olc::Pixel c1, const olc::vf2d& p2, const olc::Pixel c2, const olc::Pixel tint) { @@ -14519,16 +14515,6 @@ const GPUTask& Draw::Line(const olc::vf2d& p1, const olc::Pixel c1, const olc::v ))); } -const LineBatch& olc::Draw::Line(olc::LineBatch& batch, const olc::vf2d& p1, const olc::Pixel c1, const olc::vf2d& p2, const olc::Pixel c2) -{ - batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + 2); - size_t idx = batch.task.vertexBuffer.size() - 2; - const olc::vf2d a1 = transformAffine.forwardRound(p1); - const olc::vf2d a2 = transformAffine.forwardRound(p2); - batch.task.vertexBuffer[idx + 0] = { {a1.x, a1.y, 1.0f, 1.0f}, c1, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; - batch.task.vertexBuffer[idx + 1] = { {a2.x, a2.y, 1.0f, 1.0f}, c2, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; - return batch; -} const GPUTask& olc::Draw::Rect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col, const olc::Pixel tint) { @@ -14551,10 +14537,6 @@ const GPUTask& olc::Draw::Rect(const olc::vf2d& pos, const olc::vf2d& size, cons ))); } -const LineBatch& olc::Draw::Rect(olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) -{ - return Rect(batch, pos, size, col, col, col, col); -} const GPUTask& olc::Draw::Rect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR, const olc::Pixel tint) { @@ -14574,21 +14556,6 @@ const GPUTask& olc::Draw::Rect(const olc::vf2d& pos, const olc::vf2d& size, cons ))); } -const LineBatch& olc::Draw::Rect(olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR) -{ - const olc::vf2d pTL = olc::vf2d(pos.x, pos.y); - const olc::vf2d pTR = olc::vf2d(pos.x + size.x, pos.y); - const olc::vf2d pBR = olc::vf2d(pos.x + size.x, pos.y + size.y); - const olc::vf2d pBL = olc::vf2d(pos.x, pos.y + size.y); - - Line(batch, pTL, colTL, pTR, colTR); - Line(batch, pTR, colTR, pBR, colBR); - Line(batch, pBR, colBR, pBL, colBL); - Line(batch, pBL, colBL, pTL, colTL); - - return batch; - -} const GPUTask& olc::Draw::FilledRect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col, const olc::Pixel tint) @@ -14608,12 +14575,7 @@ const GPUTask& olc::Draw::FilledRect(const olc::vf2d& pos, const olc::vf2d& size ))); } -const FilledBatch& olc::Draw::FilledRect(olc::FilledBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) -{ - FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y), olc::vf2d(pos.x + size.x, pos.y + size.y), col, col, col); - FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y + size.y), olc::vf2d(pos.x, pos.y + size.y), col, col, col); - return batch; -} + const GPUTask& olc::Draw::FilledRect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR, const olc::Pixel tint) { @@ -14631,12 +14593,6 @@ const GPUTask& olc::Draw::FilledRect(const olc::vf2d& pos, const olc::vf2d& size ))); } -const FilledBatch& olc::Draw::FilledRect(olc::FilledBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR) -{ - FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y), olc::vf2d(pos.x + size.x, pos.y + size.y), colTL, colTR, colBR); - FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y + size.y), olc::vf2d(pos.x, pos.y + size.y), colTL, colBR, colBL); - return batch; -} void olc::Draw::RedefineUnitCircleBuffer(const int32_t nFacets) { @@ -14653,30 +14609,18 @@ const GPUTask& olc::Draw::Circle(const olc::vf2d& pos, const float& radius, cons return Ellipse(pos, radius, radius, col, tint, nFacets); } -const LineBatch& olc::Draw::Circle(olc::LineBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel col, int32_t nFacets) -{ - return Ellipse(batch, pos, radius, radius, col, nFacets); -} const GPUTask& olc::Draw::FilledCircle(const olc::vf2d& pos, const float& radius, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { return FilledEllipse(pos, radius, radius, col, tint, nFacets); } -const FilledBatch& olc::Draw::FilledCircle(olc::FilledBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel col, int32_t nFacets) -{ - return FilledEllipse(batch, pos, radius, radius, col, nFacets); -} const GPUTask& olc::Draw::FilledCircle(const olc::vf2d& pos, const float& radius, const olc::Pixel colInner, const olc::Pixel colOuter, const olc::Pixel tint, int32_t nFacets) { return FilledEllipse(pos, radius, radius, colInner, colOuter, tint, nFacets); } -const FilledBatch& olc::Draw::FilledCircle(olc::FilledBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel colInner, const olc::Pixel colOuter, int32_t nFacets) -{ - return FilledEllipse(batch, pos, radius, radius, colInner, colOuter, nFacets); -} const GPUTask& olc::Draw::Ellipse(const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { @@ -14704,34 +14648,12 @@ const GPUTask& olc::Draw::Ellipse(const olc::vf2d& pos, const float& rx, const f ))); } -const LineBatch& olc::Draw::Ellipse(olc::LineBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, int32_t nFacets) -{ - // Fundamental for batched outline circle/ellipse with colour solid/gradient - - if (nFacets != int32_t(buffUnitCirclePoints.data.size() - 1)) - RedefineUnitCircleBuffer(nFacets); - - for (int32_t i = 0; i <= nFacets; i++) - { - const olc::vf2d a1 = { buffUnitCirclePoints.data[i].x * rx + pos.x, buffUnitCirclePoints.data[i].y * ry + pos.y }; - const olc::vf2d a2 = { buffUnitCirclePoints.data[(i + 1) % buffUnitCirclePoints.data.size()].x * rx + pos.x, - buffUnitCirclePoints.data[(i + 1) % buffUnitCirclePoints.data.size()].y * ry + pos.y }; - - Line(batch, a1, col, a2, col); - } - - return batch; -} const GPUTask& olc::Draw::FilledEllipse(const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { return FilledEllipse(pos, rx, ry, col, col, tint, nFacets); } -const FilledBatch& olc::Draw::FilledEllipse(olc::FilledBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, int32_t nFacets) -{ - return FilledEllipse(batch, pos, rx, ry, col, col, nFacets); -} const GPUTask& olc::Draw::FilledEllipse(const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel colInner, const olc::Pixel colOuter, const olc::Pixel tint, int32_t nFacets) { @@ -14762,26 +14684,6 @@ const GPUTask& olc::Draw::FilledEllipse(const olc::vf2d& pos, const float& rx, c ))); } -const FilledBatch& olc::Draw::FilledEllipse(olc::FilledBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel colInner, const olc::Pixel colOuter, int32_t nFacets) -{ - // Fundamental for batch filled circle/ellipse with colour solid/gradient - - if (nFacets != int32_t(buffUnitCirclePoints.data.size() - 1)) - RedefineUnitCircleBuffer(nFacets); - - for (int32_t i = 0; i <= nFacets; i++) - { - olc::vf2d p1 = { pos.x + rx * buffUnitCirclePoints.data[i].x, - pos.y + ry * buffUnitCirclePoints.data[i].y }; - - olc::vf2d p2 = { pos.x + rx * buffUnitCirclePoints.data[(i + 1) % buffUnitCirclePoints.data.size()].x, - pos.y + ry * buffUnitCirclePoints.data[(i + 1) % buffUnitCirclePoints.data.size()].y }; - - FilledTriangle(batch, pos, p1, p2, colInner, colOuter, colOuter); - } - - return batch; -} const GPUTask& olc::Draw::RoundedRect(const olc::vf2d& pos, const olc::vf2d& size, const float& radius, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { @@ -14833,51 +14735,6 @@ const GPUTask& olc::Draw::RoundedRect(const olc::vf2d& pos, const olc::vf2d& siz ))); } -const LineBatch& olc::Draw::RoundedRect(olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const float& radius, const olc::Pixel col, int32_t nFacets) -{ - buffPoints.reserve((nFacets + 1) * 4 + 1); - buffPoints.data.clear(); - - olc::vf2d adjustedPos = pos + olc::vf2d(radius, radius); - olc::vf2d adjustedSize = size - olc::vf2d(2.0f * radius, 2.0f * radius); - - // Top Left - for (int32_t i = 0; i <= nFacets; i++) - { - float theta = (float(i) / float(nFacets)) * (0.5f * 3.14159265358979323846f) - (1.0f * 3.14159265358979323846f); - buffPoints.data.push_back({ adjustedPos.x + radius * cosf(theta), adjustedPos.y + radius * sinf(theta) }); - } - - // Top Right - for (int32_t i = 0; i <= nFacets; i++) - { - float theta = (float(i) / float(nFacets)) * (0.5f * 3.14159265358979323846f) - (0.5f * 3.14159265358979323846f); - buffPoints.data.push_back({ adjustedPos.x + adjustedSize.x + radius * cosf(theta), adjustedPos.y + radius * sinf(theta) }); - } - - // Bottom Right - for (int32_t i = 0; i <= nFacets; i++) - { - float theta = (float(i) / float(nFacets)) * (0.5f * 3.14159265358979323846f) + (0.0f * 3.14159265358979323846f); - buffPoints.data.push_back({ adjustedPos.x + adjustedSize.x + radius * cosf(theta), adjustedPos.y + adjustedSize.y + radius * sinf(theta) }); - } - - // Bottom Left - for (int32_t i = 0; i <= nFacets; i++) - { - float theta = (float(i) / float(nFacets)) * (0.5f * 3.14159265358979323846f) + (0.5f * 3.14159265358979323846f); - buffPoints.data.push_back({ adjustedPos.x + radius * cosf(theta), adjustedPos.y + adjustedSize.y + radius * sinf(theta) }); - } - - buffPoints.data.push_back({ adjustedPos.x - radius, adjustedPos.y }); - - for (size_t i = 0; i < buffPoints.data.size() - 1; i++) - { - Line(batch, buffPoints.data[i], col, buffPoints.data[i + 1], col); - } - - return batch; -} const GPUTask& olc::Draw::FilledRoundedRect(const olc::vf2d& pos, const olc::vf2d& size, const float& radius, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { @@ -14936,10 +14793,6 @@ const GPUTask& olc::Draw::Triangle(const olc::vf2d& p1, const olc::vf2d& p2, con return Triangle(p1, p2, p3, col, col, col, tint); } -const LineBatch& olc::Draw::Triangle(olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col) -{ - return Triangle(batch, p1, p2, p3, col, col, col); -} const GPUTask& olc::Draw::Triangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3, const olc::Pixel tint) { @@ -14954,24 +14807,12 @@ const GPUTask& olc::Draw::Triangle(const olc::vf2d& p1, const olc::vf2d& p2, con ))); } -const LineBatch& olc::Draw::Triangle(olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3) -{ - Line(batch, p1, c1, p2, c2); - Line(batch, p2, c2, p3, c3); - Line(batch, p3, c3, p1, c1); - return batch; -} const GPUTask& olc::Draw::FilledTriangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col, const olc::Pixel tint) { return FilledTriangle(p1, p2, p3, col, col, col, tint); } -const FilledBatch& olc::Draw::FilledTriangle(olc::FilledBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col) -{ - return FilledTriangle(batch, p1, p2, p3, col, col, col); -} - const GPUTask& olc::Draw::FilledTriangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3, const olc::Pixel tint) { PrepareTargetForHW(); @@ -14985,18 +14826,6 @@ const GPUTask& olc::Draw::FilledTriangle(const olc::vf2d& p1, const olc::vf2d& p ))); } -const FilledBatch& olc::Draw::FilledTriangle(olc::FilledBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3) -{ - batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + 3); - size_t idx = batch.task.vertexBuffer.size() - 3; - const olc::vf2d a1 = transformAffine.forwardRound(p1); - const olc::vf2d a2 = transformAffine.forwardRound(p2); - const olc::vf2d a3 = transformAffine.forwardRound(p3); - batch.task.vertexBuffer[idx + 0] = { {a1.x, a1.y, 1.0f, 1.0f}, c1, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; - batch.task.vertexBuffer[idx + 1] = { {a2.x, a2.y, 1.0f, 1.0f}, c2, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; - batch.task.vertexBuffer[idx + 2] = { {a3.x, a3.y, 1.0f, 1.0f}, c3, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; - return batch; -} const GPUTask& olc::Draw::TexturedTriangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3, const olc::vf2d& t1, const olc::vf2d& t2, const olc::vf2d& t3, olc::Image& texture, const olc::Pixel tint) { @@ -15019,30 +14848,12 @@ const GPUTask& olc::Draw::Polygon(const std::vector& vecPoints, const return Polygon(olc::Structure::LineLoop, vecPoints, std::vector(vecPoints.size(), col), tint); } -const LineBatch& olc::Draw::Polygon(olc::LineBatch& batch, const std::vector& vecPoints, const olc::Pixel col) -{ - for (size_t i = 0; i < vecPoints.size(); i++) - { - Line(batch, vecPoints[i], col, vecPoints[(i + 1) % vecPoints.size()], col); - } - - return batch; -} const GPUTask& olc::Draw::Polygon(const std::vector& vecPoints, const std::vector& vecColours, const olc::Pixel tint) { return Polygon(olc::Structure::LineLoop, vecPoints, vecColours, tint); } -const LineBatch& olc::Draw::Polygon(olc::LineBatch& batch, const std::vector& vecPoints, const std::vector& vecColours) -{ - for(size_t i = 0; i& vecPoints, const olc::Pixel col, const olc::Pixel tint) { @@ -15080,75 +14891,6 @@ const GPUTask& olc::Draw::FilledPolygon(const olc::Structure structure, const st ))); } -const FilledBatch& olc::Draw::FilledPolygon(FilledBatch& batch, const olc::Structure structure, const std::vector& vecPoints, const std::vector& vecColours, const olc::Pixel tint) -{ - // TODO: Curiously, this approach is considerably slower than the naive approach of just - // calling FilledTriangle for each triangle in the polygon. I suspect this is due to the - // overhead of copying verts into the temporary buffer and then into the batch buffer, - // but it is worth investigating further. - // - // The challenge here is olc::Structure changes. Perhaps its worth flushing the batch - // when the structure changes, but this would prevent retaining composites for future - // reuse. - - auto pushTriangle = [&](const size_t idx, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel& c1, const olc::Pixel& c2, const olc::Pixel& c3) - { - batch.task.vertexBuffer[idx + 0] = { {p1.x, p1.y, 1.0f, 1.0f}, c1, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; - batch.task.vertexBuffer[idx + 1] = { {p2.x, p2.y, 1.0f, 1.0f}, c2, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; - batch.task.vertexBuffer[idx + 2] = { {p3.x, p3.y, 1.0f, 1.0f}, c3, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; - }; - - // Transform unique verts into temporary buffer - buffPoints.data.clear(); - buffColours.data.clear(); - buffPoints.reserve(vecPoints.size()); - buffColours.reserve(vecColours.size()); - for (size_t i = 0; i < vecPoints.size(); i++) - { - buffPoints.data[i] = transformAffine.forwardRound(vecPoints[i]); - buffColours.data[i] = vecColours[i].blend(tint); - } - - switch (structure) - { - case olc::Structure::Fan: - { - size_t idx = batch.task.vertexBuffer.size(); - batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + (vecPoints.size() - 2) * 3); - - for (size_t i = 1; i < vecPoints.size() - 1; i++) - pushTriangle(idx + (i-1) * 3, - buffPoints.data[0], buffPoints.data[i], buffPoints.data[i + 1], - buffColours.data[0], buffColours.data[i], buffColours.data[i + 1]); - } - break; - - case olc::Structure::Strip: - { - size_t idx = batch.task.vertexBuffer.size(); - batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + (vecPoints.size() - 2) * 3); - - for (size_t i = 0; i < vecPoints.size() - 2; i++) - pushTriangle(idx + (i*3), - buffPoints.data[i], buffPoints.data[i + 1], buffPoints.data[i + 2], - buffColours.data[i], buffColours.data[i + 1], buffColours.data[i + 2]); - } - break; - - case olc::Structure::List: - { - size_t idx = batch.task.vertexBuffer.size(); - batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + (vecPoints.size() / 3)); - - for (size_t i = 0; i < vecPoints.size(); i += 3) - pushTriangle(idx + i, - buffPoints.data[i], buffPoints.data[i + 1], buffPoints.data[i + 2], - buffColours.data[i], buffColours.data[i + 1], buffColours.data[i + 2]); - } - } - - return batch; -} const GPUTask& olc::Draw::TexturedPolygon(const olc::Structure structure, const std::vector& vecPoints, const std::vector& vecColours, const std::vector& vecTexCoords, olc::Image& texture, const olc::Pixel tint) { @@ -15313,66 +15055,15 @@ ImageBatch olc::Draw::CreateImageBatch(olc::Image &image) return b; } -const GPUTask& olc::Draw::Batch(olc::ImageBatch& batch, const olc::Pixel tint) -{ - batch.task.tint = tint; - return vecGPUTasks.data.emplace_back(batch.task); -} -FilledBatch olc::Draw::CreateFilledBatch() +const GPUTask& olc::Draw::Image(olc::ImageRegion image, const olc::vf2d& pos, const olc::vf2d& scale, const olc::Pixel tint) { - FilledBatch b; - b.task.structure = olc::Structure::List; - return b; -} + // Ensure source image is up to date in VRAM + PrepareImageForHW(image.image); + + PrepareTargetForHW(); -const GPUTask& olc::Draw::Batch(olc::FilledBatch& batch, const olc::Pixel tint) -{ - batch.task.tint = tint; - return vecGPUTasks.data.emplace_back(batch.task); -} - -LineBatch olc::Draw::CreateLineBatch() -{ - LineBatch b; - b.task.structure = olc::Structure::LineList; - return b; -} - -const GPUTask& olc::Draw::Batch(olc::LineBatch& batch, const olc::Pixel tint) -{ - batch.task.tint = tint; - return vecGPUTasks.data.emplace_back(batch.task); -} - -const ImageBatch& olc::Draw::Image(ImageBatch& batch, olc::ImageRegion image, const olc::vf2d& pos, const olc::vf2d& scale, const olc::Pixel tint) -{ - // Add quad to existing task - olc::vf2d size = image.regionsize * scale; - - olc::vf2d p0 = transformAffine.forward(olc::vf2d{ pos.x, pos.y }); - olc::vf2d p1 = transformAffine.forward(olc::vf2d{ pos.x + size.x, pos.y }); - olc::vf2d p2 = transformAffine.forward(olc::vf2d{ pos.x + size.x, pos.y + size.y }); - olc::vf2d p3 = transformAffine.forward(olc::vf2d{ pos.x, pos.y + size.y }); - - //batch.task.vertexBuffer.reserve(batch.task.vertexBuffer.size() + 6); // NOTE!! This tanked performance on large batches - batch.task.vertexBuffer.push_back({ {p0.x, p0.y, 1.0f, 1.0f}, tint, {image.coords[0].x, image.coords[0].y}, {0, 0}, {0, 0}, {0, 0} }); - batch.task.vertexBuffer.push_back({ {p1.x, p1.y, 1.0f, 1.0f}, tint, {image.coords[1].x, image.coords[1].y}, {0, 0}, {0, 0}, {0, 0} }); - batch.task.vertexBuffer.push_back({ {p2.x, p2.y, 1.0f, 1.0f}, tint, {image.coords[2].x, image.coords[2].y}, {0, 0}, {0, 0}, {0, 0} }); - batch.task.vertexBuffer.push_back({ {p0.x, p0.y, 1.0f, 1.0f}, tint, {image.coords[0].x, image.coords[0].y}, {0, 0}, {0, 0}, {0, 0} }); - batch.task.vertexBuffer.push_back({ {p2.x, p2.y, 1.0f, 1.0f}, tint, {image.coords[2].x, image.coords[2].y}, {0, 0}, {0, 0}, {0, 0} }); - batch.task.vertexBuffer.push_back({ {p3.x, p3.y, 1.0f, 1.0f}, tint, {image.coords[3].x, image.coords[3].y}, {0, 0}, {0, 0}, {0, 0} }); - return batch; -} - -const GPUTask& olc::Draw::Image(olc::ImageRegion image, const olc::vf2d& pos, const olc::vf2d& scale, const olc::Pixel tint) -{ - // Ensure source image is up to date in VRAM - PrepareImageForHW(image.image); - - PrepareTargetForHW(); - - olc::vf2d size = image.regionsize * scale; + olc::vf2d size = image.regionsize * scale; return vecGPUTasks.data.emplace_back(std::move( TaskTexturedPolygon( @@ -15424,35 +15115,6 @@ const GPUTask& olc::Draw::ImageRotated(olc::ImageRegion image, const olc::vf2d& ))); } -const ImageBatch& olc::Draw::ImageRotated(olc::ImageBatch& batch, olc::ImageRegion image, const olc::vf2d& pos, const float theta, const olc::vf2d& center, const olc::vf2d& scale, const olc::Pixel tint) -{ - // Add quad to existing task - olc::vf2d size = image.regionsize * scale; - - std::array vPoints; - vPoints[0] = (olc::vf2d(0.0f, 0.0f) - center) * scale; - vPoints[1] = (olc::vf2d(size.x, 0.0f) - center) * scale; - vPoints[2] = (size - center) * scale; - vPoints[3] = (olc::vf2d(0.0f, size.y) - center) * scale; - - float c = cos(theta), s = sin(theta); - for (size_t i = 0; i < 4; i++) - vPoints[i] = pos + olc::vf2d(vPoints[i].x * c - vPoints[i].y * s, vPoints[i].x * s + vPoints[i].y * c); - - olc::vf2d p0 = transformAffine.forward(vPoints[0]); - olc::vf2d p1 = transformAffine.forward(vPoints[1]); - olc::vf2d p2 = transformAffine.forward(vPoints[2]); - olc::vf2d p3 = transformAffine.forward(vPoints[3]); - - //batch.task.vertexBuffer.reserve(batch.task.vertexBuffer.size() + 6); - batch.task.vertexBuffer.push_back({ {p0.x, p0.y, 1.0f, 1.0f}, tint, {image.coords[0].x, image.coords[0].y}, {0, 0}, {0, 0}, {0, 0} }); - batch.task.vertexBuffer.push_back({ {p1.x, p1.y, 1.0f, 1.0f}, tint, {image.coords[1].x, image.coords[1].y}, {0, 0}, {0, 0}, {0, 0} }); - batch.task.vertexBuffer.push_back({ {p2.x, p2.y, 1.0f, 1.0f}, tint, {image.coords[2].x, image.coords[2].y}, {0, 0}, {0, 0}, {0, 0} }); - batch.task.vertexBuffer.push_back({ {p0.x, p0.y, 1.0f, 1.0f}, tint, {image.coords[0].x, image.coords[0].y}, {0, 0}, {0, 0}, {0, 0} }); - batch.task.vertexBuffer.push_back({ {p2.x, p2.y, 1.0f, 1.0f}, tint, {image.coords[2].x, image.coords[2].y}, {0, 0}, {0, 0}, {0, 0} }); - batch.task.vertexBuffer.push_back({ {p3.x, p3.y, 1.0f, 1.0f}, tint, {image.coords[3].x, image.coords[3].y}, {0, 0}, {0, 0}, {0, 0} }); - return batch; -} const GPUTask& olc::Draw::ImageQuad(olc::ImageRegion image, const olc::vf2d& vTL, const olc::vf2d& vTR, const olc::vf2d& vBR, const olc::vf2d& vBL, const olc::Pixel tint) { @@ -15509,61 +15171,12 @@ const GPUTask& olc::Draw::ImageQuad(olc::ImageRegion image, const olc::vf2d& vTL ))); } -const ImageBatch& olc::Draw::ImageQuad(olc::ImageBatch& batch, olc::ImageRegion image, const olc::vf2d& vTL, const olc::vf2d& vTR, const olc::vf2d& vBR, const olc::vf2d& vBL, const olc::Pixel tint) -{ - float rd = ((vBR.x - vTL.x) * (vTR.y - vBL.y) - (vTR.x - vBL.x) * (vBR.y - vTL.y)); - if (rd != 0) - { - rd = 1.0f / rd; - float rn = ((vTR.x - vBL.x) * (vTL.y - vBL.y) - (vTR.y - vBL.y) * (vTL.x - vBL.x)) * rd; - float sn = ((vBR.x - vTL.x) * (vTL.y - vBL.y) - (vBR.y - vTL.y) * (vTL.x - vBL.x)) * rd; - - olc::vf2d center; - if (!(rn < 0.f || rn > 1.f || sn < 0.f || sn > 1.f)) - center = vTL + rn * (vBR - vTL); - - std::array d = { { - (vTL - center).mag(), - (vTR - center).mag(), - (vBR - center).mag(), - (vBL - center).mag(), - } }; - - std::array q = { { - d[0] == 0.0f ? 1.0f : (d[0] + d[2]) / d[2], - d[1] == 0.0f ? 1.0f : (d[1] + d[3]) / d[3], - d[2] == 0.0f ? 1.0f : (d[2] + d[0]) / d[0], - d[3] == 0.0f ? 1.0f : (d[3] + d[1]) / d[1], - } }; - - olc::vf2d p0 = transformAffine.forward(vTL); - olc::vf2d p1 = transformAffine.forward(vTR); - olc::vf2d p2 = transformAffine.forward(vBR); - olc::vf2d p3 = transformAffine.forward(vBL); - - //batch.task.vertexBuffer.reserve(batch.task.vertexBuffer.size() + 6); - batch.task.vertexBuffer.push_back({ {p0.x, p0.y, q[0], 1.0f}, tint, {q[0] * image.coords[0].x, q[0] * image.coords[0].y}, {0, 0}, {0, 0}, {0, 0}}); - batch.task.vertexBuffer.push_back({ {p1.x, p1.y, q[1], 1.0f}, tint, {q[1] * image.coords[1].x, q[1] * image.coords[1].y}, {0, 0}, {0, 0}, {0, 0}}); - batch.task.vertexBuffer.push_back({ {p2.x, p2.y, q[2], 1.0f}, tint, {q[2] * image.coords[2].x, q[2] * image.coords[2].y}, {0, 0}, {0, 0}, {0, 0}}); - batch.task.vertexBuffer.push_back({ {p0.x, p0.y, q[0], 1.0f}, tint, {q[0] * image.coords[0].x, q[0] * image.coords[0].y}, {0, 0}, {0, 0}, {0, 0}}); - batch.task.vertexBuffer.push_back({ {p2.x, p2.y, q[2], 1.0f}, tint, {q[2] * image.coords[2].x, q[2] * image.coords[2].y}, {0, 0}, {0, 0}, {0, 0}}); - batch.task.vertexBuffer.push_back({ {p3.x, p3.y, q[3], 1.0f}, tint, {q[3] * image.coords[3].x, q[3] * image.coords[3].y}, {0, 0}, {0, 0}, {0, 0}}); - return batch; - } - - // Default is just return a textured quad - return Draw::Image(batch, image, vTL, vBR - vTL, tint); -} const GPUTask& olc::Draw::ImageQuad(olc::ImageRegion image, const std::vector& vecPoints, const olc::Pixel tint) { return ImageQuad(image, vecPoints[0], vecPoints[1], vecPoints[2], vecPoints[3], tint); } -const ImageBatch& olc::Draw::ImageQuad(olc::ImageBatch& batch, olc::ImageRegion image, const std::vector& vecPoints, const olc::Pixel tint) -{ - return ImageQuad(batch, image, vecPoints[0], vecPoints[1], vecPoints[2], vecPoints[3], tint); -} const GPUTask& olc::Draw::ImageRect(olc::ImageRegion image, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel tint) @@ -15584,12 +15197,6 @@ const GPUTask& olc::Draw::ImageRect(olc::ImageRegion image, const olc::vf2d& pos ))); } -const ImageBatch& olc::Draw::ImageRect(olc::ImageBatch& batch, olc::ImageRegion image, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel tint) -{ - olc_IgnoreUnused(image, pos, size, tint); - // TODO: Implement this function - return batch; -} void olc::Draw::SetCullMode(const olc::GPUTask::CullMode mode) { @@ -15659,6 +15266,435 @@ const olc::mf4d& olc::Draw::GetMVPMatrix() const return matMVP; } +using namespace olc; + +const GPUTask& olc::Draw::Batch(olc::ImageBatch& batch, const olc::Pixel tint) +{ + batch.task.tint = tint; + return vecGPUTasks.data.emplace_back(batch.task); +} + +FilledBatch olc::Draw::CreateFilledBatch() +{ + FilledBatch b; + b.task.structure = olc::Structure::List; + return b; +} + +const GPUTask& olc::Draw::Batch(olc::FilledBatch& batch, const olc::Pixel tint) +{ + batch.task.tint = tint; + return vecGPUTasks.data.emplace_back(batch.task); +} + +LineBatch olc::Draw::CreateLineBatch() +{ + LineBatch b; + b.task.structure = olc::Structure::LineList; + return b; +} + +const GPUTask& olc::Draw::Batch(olc::LineBatch& batch, const olc::Pixel tint) +{ + batch.task.tint = tint; + return vecGPUTasks.data.emplace_back(batch.task); +} + +const LineBatch& olc::Draw::Line(olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::Pixel col) +{ + return Line(batch, p1, col, p2, col); +} + +const LineBatch& olc::Draw::Line(olc::LineBatch& batch, const olc::vf2d& p1, const olc::Pixel c1, const olc::vf2d& p2, const olc::Pixel c2) +{ + batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + 2); + size_t idx = batch.task.vertexBuffer.size() - 2; + const olc::vf2d a1 = transformAffine.forwardRound(p1); + const olc::vf2d a2 = transformAffine.forwardRound(p2); + batch.task.vertexBuffer[idx + 0] = { {a1.x, a1.y, 1.0f, 1.0f}, c1, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + batch.task.vertexBuffer[idx + 1] = { {a2.x, a2.y, 1.0f, 1.0f}, c2, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + return batch; +} + +const LineBatch& olc::Draw::Rect(olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) +{ + return Rect(batch, pos, size, col, col, col, col); +} + +const LineBatch& olc::Draw::Rect(olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR) +{ + const olc::vf2d pTL = olc::vf2d(pos.x, pos.y); + const olc::vf2d pTR = olc::vf2d(pos.x + size.x, pos.y); + const olc::vf2d pBR = olc::vf2d(pos.x + size.x, pos.y + size.y); + const olc::vf2d pBL = olc::vf2d(pos.x, pos.y + size.y); + + Line(batch, pTL, colTL, pTR, colTR); + Line(batch, pTR, colTR, pBR, colBR); + Line(batch, pBR, colBR, pBL, colBL); + Line(batch, pBL, colBL, pTL, colTL); + + return batch; +} + +const FilledBatch& olc::Draw::FilledRect(olc::FilledBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) +{ + FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y), olc::vf2d(pos.x + size.x, pos.y + size.y), col, col, col); + FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y + size.y), olc::vf2d(pos.x, pos.y + size.y), col, col, col); + return batch; +} + +const FilledBatch& olc::Draw::FilledRect(olc::FilledBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR) +{ + FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y), olc::vf2d(pos.x + size.x, pos.y + size.y), colTL, colTR, colBR); + FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y + size.y), olc::vf2d(pos.x, pos.y + size.y), colTL, colBR, colBL); + return batch; +} + +const LineBatch& olc::Draw::Circle(olc::LineBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel col, int32_t nFacets) +{ + return Ellipse(batch, pos, radius, radius, col, nFacets); +} + +const FilledBatch& olc::Draw::FilledCircle(olc::FilledBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel col, int32_t nFacets) +{ + return FilledEllipse(batch, pos, radius, radius, col, nFacets); +} + +const FilledBatch& olc::Draw::FilledCircle(olc::FilledBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel colInner, const olc::Pixel colOuter, int32_t nFacets) +{ + return FilledEllipse(batch, pos, radius, radius, colInner, colOuter, nFacets); +} + +const LineBatch& olc::Draw::Ellipse(olc::LineBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, int32_t nFacets) +{ + // Fundamental for batched outline circle/ellipse with colour solid/gradient + + if (nFacets != int32_t(buffUnitCirclePoints.data.size() - 1)) + RedefineUnitCircleBuffer(nFacets); + + for (int32_t i = 0; i <= nFacets; i++) + { + const olc::vf2d a1 = { buffUnitCirclePoints.data[i].x * rx + pos.x, buffUnitCirclePoints.data[i].y * ry + pos.y }; + const olc::vf2d a2 = { buffUnitCirclePoints.data[(i + 1) % buffUnitCirclePoints.data.size()].x * rx + pos.x, + buffUnitCirclePoints.data[(i + 1) % buffUnitCirclePoints.data.size()].y * ry + pos.y }; + + Line(batch, a1, col, a2, col); + } + + return batch; +} + +const FilledBatch& olc::Draw::FilledEllipse(olc::FilledBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, int32_t nFacets) +{ + return FilledEllipse(batch, pos, rx, ry, col, col, nFacets); +} + +const FilledBatch& olc::Draw::FilledEllipse(olc::FilledBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel colInner, const olc::Pixel colOuter, int32_t nFacets) +{ + // Fundamental for batch filled circle/ellipse with colour solid/gradient + + if (nFacets != int32_t(buffUnitCirclePoints.data.size() - 1)) + RedefineUnitCircleBuffer(nFacets); + + for (int32_t i = 0; i <= nFacets; i++) + { + olc::vf2d p1 = { pos.x + rx * buffUnitCirclePoints.data[i].x, + pos.y + ry * buffUnitCirclePoints.data[i].y }; + + olc::vf2d p2 = { pos.x + rx * buffUnitCirclePoints.data[(i + 1) % buffUnitCirclePoints.data.size()].x, + pos.y + ry * buffUnitCirclePoints.data[(i + 1) % buffUnitCirclePoints.data.size()].y }; + + FilledTriangle(batch, pos, p1, p2, colInner, colOuter, colOuter); + } + + return batch; +} + + +const LineBatch& olc::Draw::RoundedRect(olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const float& radius, const olc::Pixel col, int32_t nFacets) +{ + buffPoints.reserve((nFacets + 1) * 4 + 1); + buffPoints.data.clear(); + + olc::vf2d adjustedPos = pos + olc::vf2d(radius, radius); + olc::vf2d adjustedSize = size - olc::vf2d(2.0f * radius, 2.0f * radius); + + // Top Left + for (int32_t i = 0; i <= nFacets; i++) + { + float theta = (float(i) / float(nFacets)) * (0.5f * 3.14159265358979323846f) - (1.0f * 3.14159265358979323846f); + buffPoints.data.push_back({ adjustedPos.x + radius * cosf(theta), adjustedPos.y + radius * sinf(theta) }); + } + + // Top Right + for (int32_t i = 0; i <= nFacets; i++) + { + float theta = (float(i) / float(nFacets)) * (0.5f * 3.14159265358979323846f) - (0.5f * 3.14159265358979323846f); + buffPoints.data.push_back({ adjustedPos.x + adjustedSize.x + radius * cosf(theta), adjustedPos.y + radius * sinf(theta) }); + } + + // Bottom Right + for (int32_t i = 0; i <= nFacets; i++) + { + float theta = (float(i) / float(nFacets)) * (0.5f * 3.14159265358979323846f) + (0.0f * 3.14159265358979323846f); + buffPoints.data.push_back({ adjustedPos.x + adjustedSize.x + radius * cosf(theta), adjustedPos.y + adjustedSize.y + radius * sinf(theta) }); + } + + // Bottom Left + for (int32_t i = 0; i <= nFacets; i++) + { + float theta = (float(i) / float(nFacets)) * (0.5f * 3.14159265358979323846f) + (0.5f * 3.14159265358979323846f); + buffPoints.data.push_back({ adjustedPos.x + radius * cosf(theta), adjustedPos.y + adjustedSize.y + radius * sinf(theta) }); + } + + buffPoints.data.push_back({ adjustedPos.x - radius, adjustedPos.y }); + + for (size_t i = 0; i < buffPoints.data.size() - 1; i++) + { + Line(batch, buffPoints.data[i], col, buffPoints.data[i + 1], col); + } + + return batch; +} + + +const LineBatch& olc::Draw::Triangle(olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col) +{ + return Triangle(batch, p1, p2, p3, col, col, col); +} + +const LineBatch& olc::Draw::Triangle(olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3) +{ + Line(batch, p1, c1, p2, c2); + Line(batch, p2, c2, p3, c3); + Line(batch, p3, c3, p1, c1); + return batch; +} + + + +const FilledBatch& olc::Draw::FilledTriangle(olc::FilledBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col) +{ + return FilledTriangle(batch, p1, p2, p3, col, col, col); +} + +const FilledBatch& olc::Draw::FilledTriangle(olc::FilledBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3) +{ + batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + 3); + size_t idx = batch.task.vertexBuffer.size() - 3; + const olc::vf2d a1 = transformAffine.forwardRound(p1); + const olc::vf2d a2 = transformAffine.forwardRound(p2); + const olc::vf2d a3 = transformAffine.forwardRound(p3); + batch.task.vertexBuffer[idx + 0] = { {a1.x, a1.y, 1.0f, 1.0f}, c1, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + batch.task.vertexBuffer[idx + 1] = { {a2.x, a2.y, 1.0f, 1.0f}, c2, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + batch.task.vertexBuffer[idx + 2] = { {a3.x, a3.y, 1.0f, 1.0f}, c3, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + return batch; +} + + +const LineBatch& olc::Draw::Polygon(olc::LineBatch& batch, const std::vector& vecPoints, const olc::Pixel col) +{ + for (size_t i = 0; i < vecPoints.size(); i++) + { + Line(batch, vecPoints[i], col, vecPoints[(i + 1) % vecPoints.size()], col); + } + + return batch; +} + +const LineBatch& olc::Draw::Polygon(olc::LineBatch& batch, const std::vector& vecPoints, const std::vector& vecColours) +{ + for (size_t i = 0; i < vecPoints.size(); i++) + { + Line(batch, vecPoints[i], vecColours[i], vecPoints[(i + 1) % vecPoints.size()], vecColours[(i + 1) % vecColours.size()]); + } + + return batch; +} + +const FilledBatch& olc::Draw::FilledPolygon(FilledBatch& batch, const olc::Structure structure, const std::vector& vecPoints, const std::vector& vecColours, const olc::Pixel tint) +{ + // TODO: Curiously, this approach is considerably slower than the naive approach of just + // calling FilledTriangle for each triangle in the polygon. I suspect this is due to the + // overhead of copying verts into the temporary buffer and then into the batch buffer, + // but it is worth investigating further. + // + // The challenge here is olc::Structure changes. Perhaps its worth flushing the batch + // when the structure changes, but this would prevent retaining composites for future + // reuse. + + auto pushTriangle = [&](const size_t idx, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel& c1, const olc::Pixel& c2, const olc::Pixel& c3) + { + batch.task.vertexBuffer[idx + 0] = { {p1.x, p1.y, 1.0f, 1.0f}, c1, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + batch.task.vertexBuffer[idx + 1] = { {p2.x, p2.y, 1.0f, 1.0f}, c2, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + batch.task.vertexBuffer[idx + 2] = { {p3.x, p3.y, 1.0f, 1.0f}, c3, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + }; + + // Transform unique verts into temporary buffer + buffPoints.data.clear(); + buffColours.data.clear(); + buffPoints.reserve(vecPoints.size()); + buffColours.reserve(vecColours.size()); + for (size_t i = 0; i < vecPoints.size(); i++) + { + buffPoints.data[i] = transformAffine.forwardRound(vecPoints[i]); + buffColours.data[i] = vecColours[i].blend(tint); + } + + switch (structure) + { + case olc::Structure::Fan: + { + size_t idx = batch.task.vertexBuffer.size(); + batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + (vecPoints.size() - 2) * 3); + + for (size_t i = 1; i < vecPoints.size() - 1; i++) + pushTriangle(idx + (i - 1) * 3, + buffPoints.data[0], buffPoints.data[i], buffPoints.data[i + 1], + buffColours.data[0], buffColours.data[i], buffColours.data[i + 1]); + } + break; + + case olc::Structure::Strip: + { + size_t idx = batch.task.vertexBuffer.size(); + batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + (vecPoints.size() - 2) * 3); + + for (size_t i = 0; i < vecPoints.size() - 2; i++) + pushTriangle(idx + (i * 3), + buffPoints.data[i], buffPoints.data[i + 1], buffPoints.data[i + 2], + buffColours.data[i], buffColours.data[i + 1], buffColours.data[i + 2]); + } + break; + + case olc::Structure::List: + { + size_t idx = batch.task.vertexBuffer.size(); + batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + (vecPoints.size() / 3)); + + for (size_t i = 0; i < vecPoints.size(); i += 3) + pushTriangle(idx + i, + buffPoints.data[i], buffPoints.data[i + 1], buffPoints.data[i + 2], + buffColours.data[i], buffColours.data[i + 1], buffColours.data[i + 2]); + } + } + + return batch; +} + + + +const ImageBatch& olc::Draw::Image(ImageBatch& batch, olc::ImageRegion image, const olc::vf2d& pos, const olc::vf2d& scale, const olc::Pixel tint) +{ + // Add quad to existing task + olc::vf2d size = image.regionsize * scale; + + olc::vf2d p0 = transformAffine.forward(olc::vf2d{ pos.x, pos.y }); + olc::vf2d p1 = transformAffine.forward(olc::vf2d{ pos.x + size.x, pos.y }); + olc::vf2d p2 = transformAffine.forward(olc::vf2d{ pos.x + size.x, pos.y + size.y }); + olc::vf2d p3 = transformAffine.forward(olc::vf2d{ pos.x, pos.y + size.y }); + + //batch.task.vertexBuffer.reserve(batch.task.vertexBuffer.size() + 6); // NOTE!! This tanked performance on large batches + batch.task.vertexBuffer.push_back({ {p0.x, p0.y, 1.0f, 1.0f}, tint, {image.coords[0].x, image.coords[0].y}, {0, 0}, {0, 0}, {0, 0} }); + batch.task.vertexBuffer.push_back({ {p1.x, p1.y, 1.0f, 1.0f}, tint, {image.coords[1].x, image.coords[1].y}, {0, 0}, {0, 0}, {0, 0} }); + batch.task.vertexBuffer.push_back({ {p2.x, p2.y, 1.0f, 1.0f}, tint, {image.coords[2].x, image.coords[2].y}, {0, 0}, {0, 0}, {0, 0} }); + batch.task.vertexBuffer.push_back({ {p0.x, p0.y, 1.0f, 1.0f}, tint, {image.coords[0].x, image.coords[0].y}, {0, 0}, {0, 0}, {0, 0} }); + batch.task.vertexBuffer.push_back({ {p2.x, p2.y, 1.0f, 1.0f}, tint, {image.coords[2].x, image.coords[2].y}, {0, 0}, {0, 0}, {0, 0} }); + batch.task.vertexBuffer.push_back({ {p3.x, p3.y, 1.0f, 1.0f}, tint, {image.coords[3].x, image.coords[3].y}, {0, 0}, {0, 0}, {0, 0} }); + return batch; +} + +const ImageBatch& olc::Draw::ImageRotated(olc::ImageBatch& batch, olc::ImageRegion image, const olc::vf2d& pos, const float theta, const olc::vf2d& center, const olc::vf2d& scale, const olc::Pixel tint) +{ + // Add quad to existing task + olc::vf2d size = image.regionsize * scale; + + std::array vPoints; + vPoints[0] = (olc::vf2d(0.0f, 0.0f) - center) * scale; + vPoints[1] = (olc::vf2d(size.x, 0.0f) - center) * scale; + vPoints[2] = (size - center) * scale; + vPoints[3] = (olc::vf2d(0.0f, size.y) - center) * scale; + + float c = cos(theta), s = sin(theta); + for (size_t i = 0; i < 4; i++) + vPoints[i] = pos + olc::vf2d(vPoints[i].x * c - vPoints[i].y * s, vPoints[i].x * s + vPoints[i].y * c); + + olc::vf2d p0 = transformAffine.forward(vPoints[0]); + olc::vf2d p1 = transformAffine.forward(vPoints[1]); + olc::vf2d p2 = transformAffine.forward(vPoints[2]); + olc::vf2d p3 = transformAffine.forward(vPoints[3]); + + //batch.task.vertexBuffer.reserve(batch.task.vertexBuffer.size() + 6); + batch.task.vertexBuffer.push_back({ {p0.x, p0.y, 1.0f, 1.0f}, tint, {image.coords[0].x, image.coords[0].y}, {0, 0}, {0, 0}, {0, 0} }); + batch.task.vertexBuffer.push_back({ {p1.x, p1.y, 1.0f, 1.0f}, tint, {image.coords[1].x, image.coords[1].y}, {0, 0}, {0, 0}, {0, 0} }); + batch.task.vertexBuffer.push_back({ {p2.x, p2.y, 1.0f, 1.0f}, tint, {image.coords[2].x, image.coords[2].y}, {0, 0}, {0, 0}, {0, 0} }); + batch.task.vertexBuffer.push_back({ {p0.x, p0.y, 1.0f, 1.0f}, tint, {image.coords[0].x, image.coords[0].y}, {0, 0}, {0, 0}, {0, 0} }); + batch.task.vertexBuffer.push_back({ {p2.x, p2.y, 1.0f, 1.0f}, tint, {image.coords[2].x, image.coords[2].y}, {0, 0}, {0, 0}, {0, 0} }); + batch.task.vertexBuffer.push_back({ {p3.x, p3.y, 1.0f, 1.0f}, tint, {image.coords[3].x, image.coords[3].y}, {0, 0}, {0, 0}, {0, 0} }); + return batch; +} + +const ImageBatch& olc::Draw::ImageQuad(olc::ImageBatch& batch, olc::ImageRegion image, const olc::vf2d& vTL, const olc::vf2d& vTR, const olc::vf2d& vBR, const olc::vf2d& vBL, const olc::Pixel tint) +{ + float rd = ((vBR.x - vTL.x) * (vTR.y - vBL.y) - (vTR.x - vBL.x) * (vBR.y - vTL.y)); + if (rd != 0) + { + rd = 1.0f / rd; + float rn = ((vTR.x - vBL.x) * (vTL.y - vBL.y) - (vTR.y - vBL.y) * (vTL.x - vBL.x)) * rd; + float sn = ((vBR.x - vTL.x) * (vTL.y - vBL.y) - (vBR.y - vTL.y) * (vTL.x - vBL.x)) * rd; + + olc::vf2d center; + if (!(rn < 0.f || rn > 1.f || sn < 0.f || sn > 1.f)) + center = vTL + rn * (vBR - vTL); + + std::array d = { { + (vTL - center).mag(), + (vTR - center).mag(), + (vBR - center).mag(), + (vBL - center).mag(), + } }; + + std::array q = { { + d[0] == 0.0f ? 1.0f : (d[0] + d[2]) / d[2], + d[1] == 0.0f ? 1.0f : (d[1] + d[3]) / d[3], + d[2] == 0.0f ? 1.0f : (d[2] + d[0]) / d[0], + d[3] == 0.0f ? 1.0f : (d[3] + d[1]) / d[1], + } }; + + olc::vf2d p0 = transformAffine.forward(vTL); + olc::vf2d p1 = transformAffine.forward(vTR); + olc::vf2d p2 = transformAffine.forward(vBR); + olc::vf2d p3 = transformAffine.forward(vBL); + + //batch.task.vertexBuffer.reserve(batch.task.vertexBuffer.size() + 6); + batch.task.vertexBuffer.push_back({ {p0.x, p0.y, q[0], 1.0f}, tint, {q[0] * image.coords[0].x, q[0] * image.coords[0].y}, {0, 0}, {0, 0}, {0, 0} }); + batch.task.vertexBuffer.push_back({ {p1.x, p1.y, q[1], 1.0f}, tint, {q[1] * image.coords[1].x, q[1] * image.coords[1].y}, {0, 0}, {0, 0}, {0, 0} }); + batch.task.vertexBuffer.push_back({ {p2.x, p2.y, q[2], 1.0f}, tint, {q[2] * image.coords[2].x, q[2] * image.coords[2].y}, {0, 0}, {0, 0}, {0, 0} }); + batch.task.vertexBuffer.push_back({ {p0.x, p0.y, q[0], 1.0f}, tint, {q[0] * image.coords[0].x, q[0] * image.coords[0].y}, {0, 0}, {0, 0}, {0, 0} }); + batch.task.vertexBuffer.push_back({ {p2.x, p2.y, q[2], 1.0f}, tint, {q[2] * image.coords[2].x, q[2] * image.coords[2].y}, {0, 0}, {0, 0}, {0, 0} }); + batch.task.vertexBuffer.push_back({ {p3.x, p3.y, q[3], 1.0f}, tint, {q[3] * image.coords[3].x, q[3] * image.coords[3].y}, {0, 0}, {0, 0}, {0, 0} }); + return batch; + } + + // Default is just return a textured quad + return Draw::Image(batch, image, vTL, vBR - vTL, tint); +} + +const ImageBatch& olc::Draw::ImageQuad(olc::ImageBatch& batch, olc::ImageRegion image, const std::vector& vecPoints, const olc::Pixel tint) +{ + return ImageQuad(batch, image, vecPoints[0], vecPoints[1], vecPoints[2], vecPoints[3], tint); +} + +const ImageBatch& olc::Draw::ImageRect(olc::ImageBatch& batch, olc::ImageRegion image, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel tint) +{ + olc_IgnoreUnused(image, pos, size, tint); + // TODO: Implement this function + return batch; +} + + + + #define PGE_DRAW_IMPLEMENTED 1 #endif From c8bbf80ded0ecb3509bc357dc56f9dc104ed54bc Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Sat, 14 Feb 2026 22:21:19 +0000 Subject: [PATCH 49/58] Added tints to batch draw calls --- .../olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj | 11 +- dev/src/draw.h | 43 ++++-- dev/src/draw_batch.cpp | 112 ++++++++------ examples/olcPGE3_BatchesOfFills.cpp | 4 +- olcPixelGameEngine3.h | 140 ++++++++++-------- 5 files changed, 185 insertions(+), 125 deletions(-) diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj index a66647f3..ea978033 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj @@ -32,24 +32,29 @@ true + false + false + false + false + + true true true true - + true true true true - + true true true true - true true diff --git a/dev/src/draw.h b/dev/src/draw.h index a79809b1..1a763246 100644 --- a/dev/src/draw.h +++ b/dev/src/draw.h @@ -253,7 +253,8 @@ namespace olc olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, - const olc::Pixel col = olc::Colour::WHITE); + const olc::Pixel col = olc::Colour::WHITE, + const olc::Pixel tint = olc::Colour::WHITE); // Draws a single pixel wide line with a gradient const GPUTask& Line( @@ -269,7 +270,8 @@ namespace olc const olc::vf2d& p1, const olc::Pixel c1, const olc::vf2d& p2, - const olc::Pixel c2); + const olc::Pixel c2, + const olc::Pixel tint = olc::Colour::WHITE); // === Rectangles === @@ -285,7 +287,8 @@ namespace olc olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, - const olc::Pixel col = olc::Colour::WHITE); + const olc::Pixel col = olc::Colour::WHITE, + const olc::Pixel tint = olc::Colour::WHITE); // Draws a multiple colour rectangle, with linear colour interpolation const GPUTask& Rect( @@ -305,7 +308,8 @@ namespace olc const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, - const olc::Pixel colBR); + const olc::Pixel colBR, + const olc::Pixel tint = olc::Colour::WHITE); // Draws a filled, single colour rectangle const GPUTask& FilledRect( @@ -319,7 +323,8 @@ namespace olc olc::FilledBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, - const olc::Pixel col = olc::Colour::WHITE); + const olc::Pixel col = olc::Colour::WHITE, + const olc::Pixel tint = olc::Colour::WHITE); // Draws a filled, multiple colour rectangle, with linear colour interpolation const GPUTask& FilledRect( @@ -339,7 +344,8 @@ namespace olc const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, - const olc::Pixel colBR); + const olc::Pixel colBR, + const olc::Pixel tint = olc::Colour::WHITE); // === Circles === @@ -357,6 +363,7 @@ namespace olc const olc::vf2d& pos, const float& radius, const olc::Pixel col = olc::Colour::WHITE, + const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); // Draws a filled circle with a single colour @@ -373,6 +380,7 @@ namespace olc const olc::vf2d& pos, const float& radius, const olc::Pixel col = olc::Colour::WHITE, + const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); // Draws a shaded circle with a radial gradient @@ -391,6 +399,7 @@ namespace olc const float& radius, const olc::Pixel colInner, const olc::Pixel colOuter, + const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); // === Ellipses === @@ -411,6 +420,7 @@ namespace olc const float& rx, const float& ry, const olc::Pixel col = olc::Colour::WHITE, + const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); // Draws a filled ellipse with a single colour @@ -429,6 +439,7 @@ namespace olc const float& rx, const float& ry, const olc::Pixel col = olc::Colour::WHITE, + const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); // Draws a shaded ellipse with a radial gradient @@ -449,6 +460,7 @@ namespace olc const float& ry, const olc::Pixel colInner, const olc::Pixel colOuter, + const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); // === Rounded Rectangles === @@ -469,6 +481,7 @@ namespace olc const olc::vf2d& size, // Size of bounding rectangle const float& radius, const olc::Pixel col = olc::Colour::WHITE, + const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS / 4); // Draws a filled rounded rectangle with a single colour @@ -497,7 +510,8 @@ namespace olc const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, - const olc::Pixel col = olc::Colour::WHITE); + const olc::Pixel col = olc::Colour::WHITE, + const olc::Pixel tint = olc::Colour::WHITE); // Draws a multiple colour triangle outline const GPUTask& Triangle( @@ -517,7 +531,8 @@ namespace olc const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, - const olc::Pixel c3); + const olc::Pixel c3, + const olc::Pixel tint = olc::Colour::WHITE); // Draws a filled, single colour triangle const GPUTask& FilledTriangle( @@ -533,7 +548,8 @@ namespace olc const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, - const olc::Pixel col = olc::Colour::WHITE); + const olc::Pixel col = olc::Colour::WHITE, + const olc::Pixel tint = olc::Colour::WHITE); // Draws a filled, multiple colour triangle const GPUTask& FilledTriangle( @@ -553,7 +569,8 @@ namespace olc const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, - const olc::Pixel c3); + const olc::Pixel c3, + const olc::Pixel tint = olc::Colour::WHITE); // Draws a textured triangle, with per vertex colouring const GPUTask& TexturedTriangle( @@ -581,7 +598,8 @@ namespace olc const LineBatch& Polygon( olc::LineBatch& batch, const std::vector& vecPoints, - const olc::Pixel col = olc::Colour::WHITE); + const olc::Pixel col = olc::Colour::WHITE, + const olc::Pixel tint = olc::Colour::WHITE); // Draws a polygon outline with multiple colours const GPUTask& Polygon( @@ -593,7 +611,8 @@ namespace olc const LineBatch& Polygon( olc::LineBatch& batch, const std::vector& vecPoints, - const std::vector& vecColours); + const std::vector& vecColours, + const olc::Pixel tint = olc::Colour::WHITE); // === Structured Polygons (Outlines & Fills) === diff --git a/dev/src/draw_batch.cpp b/dev/src/draw_batch.cpp index 3c9edb3d..c632ca61 100644 --- a/dev/src/draw_batch.cpp +++ b/dev/src/draw_batch.cpp @@ -1,5 +1,20 @@ #include "draw.h" +// I've kept the implementation of batch drawing functions in a +// separate file to avoid cluttering the main draw.cpp + +// Batching functions by neccessity treat the incoming vertices +// quite differently, decomposing into line segments or filled discrete +// triangles so the GPU only has to deal with one type of primitive + +// NOTE: There is a tremendous amount of scope for optimising these +// functions, as they are currently very much 'brute force' implementations. + +// The batch functions return a reference to the GPUTask that was created, +// so that the call can construct the task and reuse it later. This might +// be useful for complex geometries that need to be redrawn every frame, +// but don't want to pay the CPU cost of reconstructing the batch every frame. + //! START IMPLEMENTATION using namespace olc; @@ -35,72 +50,72 @@ const GPUTask& olc::Draw::Batch(olc::LineBatch& batch, const olc::Pixel tint) return vecGPUTasks.data.emplace_back(batch.task); } -const LineBatch& olc::Draw::Line(olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::Pixel col) +const LineBatch& olc::Draw::Line(olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::Pixel col, const olc::Pixel tint) { - return Line(batch, p1, col, p2, col); + return Line(batch, p1, col, p2, col, tint); } -const LineBatch& olc::Draw::Line(olc::LineBatch& batch, const olc::vf2d& p1, const olc::Pixel c1, const olc::vf2d& p2, const olc::Pixel c2) +const LineBatch& olc::Draw::Line(olc::LineBatch& batch, const olc::vf2d& p1, const olc::Pixel c1, const olc::vf2d& p2, const olc::Pixel c2, const olc::Pixel tint) { batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + 2); size_t idx = batch.task.vertexBuffer.size() - 2; const olc::vf2d a1 = transformAffine.forwardRound(p1); const olc::vf2d a2 = transformAffine.forwardRound(p2); - batch.task.vertexBuffer[idx + 0] = { {a1.x, a1.y, 1.0f, 1.0f}, c1, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; - batch.task.vertexBuffer[idx + 1] = { {a2.x, a2.y, 1.0f, 1.0f}, c2, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + batch.task.vertexBuffer[idx + 0] = { {a1.x, a1.y, 1.0f, 1.0f}, c1.blend(tint), {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + batch.task.vertexBuffer[idx + 1] = { {a2.x, a2.y, 1.0f, 1.0f}, c2.blend(tint), {0, 0}, {0, 0}, {0, 0}, {0, 0} }; return batch; } -const LineBatch& olc::Draw::Rect(olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) +const LineBatch& olc::Draw::Rect(olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col, const olc::Pixel tint) { - return Rect(batch, pos, size, col, col, col, col); + return Rect(batch, pos, size, col, col, col, col, tint); } -const LineBatch& olc::Draw::Rect(olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR) +const LineBatch& olc::Draw::Rect(olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR, const olc::Pixel tint) { const olc::vf2d pTL = olc::vf2d(pos.x, pos.y); const olc::vf2d pTR = olc::vf2d(pos.x + size.x, pos.y); const olc::vf2d pBR = olc::vf2d(pos.x + size.x, pos.y + size.y); const olc::vf2d pBL = olc::vf2d(pos.x, pos.y + size.y); - Line(batch, pTL, colTL, pTR, colTR); - Line(batch, pTR, colTR, pBR, colBR); - Line(batch, pBR, colBR, pBL, colBL); - Line(batch, pBL, colBL, pTL, colTL); + Line(batch, pTL, colTL, pTR, colTR, tint); + Line(batch, pTR, colTR, pBR, colBR, tint); + Line(batch, pBR, colBR, pBL, colBL, tint); + Line(batch, pBL, colBL, pTL, colTL, tint); return batch; } -const FilledBatch& olc::Draw::FilledRect(olc::FilledBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) +const FilledBatch& olc::Draw::FilledRect(olc::FilledBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col, const olc::Pixel tint) { - FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y), olc::vf2d(pos.x + size.x, pos.y + size.y), col, col, col); - FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y + size.y), olc::vf2d(pos.x, pos.y + size.y), col, col, col); + FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y), olc::vf2d(pos.x + size.x, pos.y + size.y), col, col, col, tint); + FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y + size.y), olc::vf2d(pos.x, pos.y + size.y), col, col, col, tint); return batch; } -const FilledBatch& olc::Draw::FilledRect(olc::FilledBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR) +const FilledBatch& olc::Draw::FilledRect(olc::FilledBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR, const olc::Pixel tint) { - FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y), olc::vf2d(pos.x + size.x, pos.y + size.y), colTL, colTR, colBR); - FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y + size.y), olc::vf2d(pos.x, pos.y + size.y), colTL, colBR, colBL); + FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y), olc::vf2d(pos.x + size.x, pos.y + size.y), colTL, colTR, colBR, tint); + FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y + size.y), olc::vf2d(pos.x, pos.y + size.y), colTL, colBR, colBL, tint); return batch; } -const LineBatch& olc::Draw::Circle(olc::LineBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel col, int32_t nFacets) +const LineBatch& olc::Draw::Circle(olc::LineBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { - return Ellipse(batch, pos, radius, radius, col, nFacets); + return Ellipse(batch, pos, radius, radius, col, tint, nFacets); } -const FilledBatch& olc::Draw::FilledCircle(olc::FilledBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel col, int32_t nFacets) +const FilledBatch& olc::Draw::FilledCircle(olc::FilledBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { - return FilledEllipse(batch, pos, radius, radius, col, nFacets); + return FilledEllipse(batch, pos, radius, radius, col, tint, nFacets); } -const FilledBatch& olc::Draw::FilledCircle(olc::FilledBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel colInner, const olc::Pixel colOuter, int32_t nFacets) +const FilledBatch& olc::Draw::FilledCircle(olc::FilledBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel colInner, const olc::Pixel colOuter, const olc::Pixel tint, int32_t nFacets) { - return FilledEllipse(batch, pos, radius, radius, colInner, colOuter, nFacets); + return FilledEllipse(batch, pos, radius, radius, colInner, colOuter, tint, nFacets); } -const LineBatch& olc::Draw::Ellipse(olc::LineBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, int32_t nFacets) +const LineBatch& olc::Draw::Ellipse(olc::LineBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { // Fundamental for batched outline circle/ellipse with colour solid/gradient @@ -113,18 +128,18 @@ const LineBatch& olc::Draw::Ellipse(olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d a2 = { buffUnitCirclePoints.data[(i + 1) % buffUnitCirclePoints.data.size()].x * rx + pos.x, buffUnitCirclePoints.data[(i + 1) % buffUnitCirclePoints.data.size()].y * ry + pos.y }; - Line(batch, a1, col, a2, col); + Line(batch, a1, col, a2, col, tint); } return batch; } -const FilledBatch& olc::Draw::FilledEllipse(olc::FilledBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, int32_t nFacets) +const FilledBatch& olc::Draw::FilledEllipse(olc::FilledBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { - return FilledEllipse(batch, pos, rx, ry, col, col, nFacets); + return FilledEllipse(batch, pos, rx, ry, col, col, tint, nFacets); } -const FilledBatch& olc::Draw::FilledEllipse(olc::FilledBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel colInner, const olc::Pixel colOuter, int32_t nFacets) +const FilledBatch& olc::Draw::FilledEllipse(olc::FilledBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel colInner, const olc::Pixel colOuter, const olc::Pixel tint, int32_t nFacets) { // Fundamental for batch filled circle/ellipse with colour solid/gradient @@ -139,14 +154,14 @@ const FilledBatch& olc::Draw::FilledEllipse(olc::FilledBatch& batch, const olc:: olc::vf2d p2 = { pos.x + rx * buffUnitCirclePoints.data[(i + 1) % buffUnitCirclePoints.data.size()].x, pos.y + ry * buffUnitCirclePoints.data[(i + 1) % buffUnitCirclePoints.data.size()].y }; - FilledTriangle(batch, pos, p1, p2, colInner, colOuter, colOuter); + FilledTriangle(batch, pos, p1, p2, colInner, colOuter, colOuter, tint); } return batch; } -const LineBatch& olc::Draw::RoundedRect(olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const float& radius, const olc::Pixel col, int32_t nFacets) +const LineBatch& olc::Draw::RoundedRect(olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const float& radius, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { buffPoints.reserve((nFacets + 1) * 4 + 1); buffPoints.data.clear(); @@ -186,62 +201,62 @@ const LineBatch& olc::Draw::RoundedRect(olc::LineBatch& batch, const olc::vf2d& for (size_t i = 0; i < buffPoints.data.size() - 1; i++) { - Line(batch, buffPoints.data[i], col, buffPoints.data[i + 1], col); + Line(batch, buffPoints.data[i], col, buffPoints.data[i + 1], col, tint); } return batch; } -const LineBatch& olc::Draw::Triangle(olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col) +const LineBatch& olc::Draw::Triangle(olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col, const olc::Pixel tint) { - return Triangle(batch, p1, p2, p3, col, col, col); + return Triangle(batch, p1, p2, p3, col, col, col, tint); } -const LineBatch& olc::Draw::Triangle(olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3) +const LineBatch& olc::Draw::Triangle(olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3, const olc::Pixel tint) { - Line(batch, p1, c1, p2, c2); - Line(batch, p2, c2, p3, c3); - Line(batch, p3, c3, p1, c1); + Line(batch, p1, c1, p2, c2, tint); + Line(batch, p2, c2, p3, c3, tint); + Line(batch, p3, c3, p1, c1, tint); return batch; } -const FilledBatch& olc::Draw::FilledTriangle(olc::FilledBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col) +const FilledBatch& olc::Draw::FilledTriangle(olc::FilledBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col, const olc::Pixel tint) { - return FilledTriangle(batch, p1, p2, p3, col, col, col); + return FilledTriangle(batch, p1, p2, p3, col, col, col, tint); } -const FilledBatch& olc::Draw::FilledTriangle(olc::FilledBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3) +const FilledBatch& olc::Draw::FilledTriangle(olc::FilledBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3, const olc::Pixel tint) { batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + 3); size_t idx = batch.task.vertexBuffer.size() - 3; const olc::vf2d a1 = transformAffine.forwardRound(p1); const olc::vf2d a2 = transformAffine.forwardRound(p2); const olc::vf2d a3 = transformAffine.forwardRound(p3); - batch.task.vertexBuffer[idx + 0] = { {a1.x, a1.y, 1.0f, 1.0f}, c1, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; - batch.task.vertexBuffer[idx + 1] = { {a2.x, a2.y, 1.0f, 1.0f}, c2, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; - batch.task.vertexBuffer[idx + 2] = { {a3.x, a3.y, 1.0f, 1.0f}, c3, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + batch.task.vertexBuffer[idx + 0] = { {a1.x, a1.y, 1.0f, 1.0f}, c1.blend(tint), {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + batch.task.vertexBuffer[idx + 1] = { {a2.x, a2.y, 1.0f, 1.0f}, c2.blend(tint), {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + batch.task.vertexBuffer[idx + 2] = { {a3.x, a3.y, 1.0f, 1.0f}, c3.blend(tint), {0, 0}, {0, 0}, {0, 0}, {0, 0} }; return batch; } -const LineBatch& olc::Draw::Polygon(olc::LineBatch& batch, const std::vector& vecPoints, const olc::Pixel col) +const LineBatch& olc::Draw::Polygon(olc::LineBatch& batch, const std::vector& vecPoints, const olc::Pixel col, const olc::Pixel tint) { for (size_t i = 0; i < vecPoints.size(); i++) { - Line(batch, vecPoints[i], col, vecPoints[(i + 1) % vecPoints.size()], col); + Line(batch, vecPoints[i], col, vecPoints[(i + 1) % vecPoints.size()], col, tint); } return batch; } -const LineBatch& olc::Draw::Polygon(olc::LineBatch& batch, const std::vector& vecPoints, const std::vector& vecColours) +const LineBatch& olc::Draw::Polygon(olc::LineBatch& batch, const std::vector& vecPoints, const std::vector& vecColours, const olc::Pixel tint) { for (size_t i = 0; i < vecPoints.size(); i++) { - Line(batch, vecPoints[i], vecColours[i], vecPoints[(i + 1) % vecPoints.size()], vecColours[(i + 1) % vecColours.size()]); + Line(batch, vecPoints[i], vecColours[i], vecPoints[(i + 1) % vecPoints.size()], vecColours[(i + 1) % vecColours.size()], tint); } return batch; @@ -330,6 +345,7 @@ const ImageBatch& olc::Draw::Image(ImageBatch& batch, olc::ImageRegion image, co olc::vf2d p3 = transformAffine.forward(olc::vf2d{ pos.x, pos.y + size.y }); //batch.task.vertexBuffer.reserve(batch.task.vertexBuffer.size() + 6); // NOTE!! This tanked performance on large batches + // NOTE: We fake tint by simply setting vertex colour batch.task.vertexBuffer.push_back({ {p0.x, p0.y, 1.0f, 1.0f}, tint, {image.coords[0].x, image.coords[0].y}, {0, 0}, {0, 0}, {0, 0} }); batch.task.vertexBuffer.push_back({ {p1.x, p1.y, 1.0f, 1.0f}, tint, {image.coords[1].x, image.coords[1].y}, {0, 0}, {0, 0}, {0, 0} }); batch.task.vertexBuffer.push_back({ {p2.x, p2.y, 1.0f, 1.0f}, tint, {image.coords[2].x, image.coords[2].y}, {0, 0}, {0, 0}, {0, 0} }); diff --git a/examples/olcPGE3_BatchesOfFills.cpp b/examples/olcPGE3_BatchesOfFills.cpp index 9c7eed2b..ae34e687 100644 --- a/examples/olcPGE3_BatchesOfFills.cpp +++ b/examples/olcPGE3_BatchesOfFills.cpp @@ -79,7 +79,7 @@ class Example_BatchesOfFills : public olc::PixelGameEngine // Draw a gradient circle draw.FilledCircle(batch, { 224.0f, 32.0f }, 20.0f, - olc::Colour::RED, olc::Colour::YELLOW); + olc::Colour::RED, olc::Colour::YELLOW, olc::Colour::WHITE); @@ -95,7 +95,7 @@ class Example_BatchesOfFills : public olc::PixelGameEngine // Draw a gradient ellipse draw.FilledEllipse(batch, { 224.0f, 96.0f }, 10, 20, - olc::Colour::BLUE, olc::Colour::CYAN); + olc::Colour::BLUE, olc::Colour::CYAN, olc::Colour::WHITE); // Draw a circle outline with fewer facets diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 8609e443..13516efa 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -2677,7 +2677,8 @@ namespace olc olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, - const olc::Pixel col = olc::Colour::WHITE); + const olc::Pixel col = olc::Colour::WHITE, + const olc::Pixel tint = olc::Colour::WHITE); // Draws a single pixel wide line with a gradient const GPUTask& Line( @@ -2693,7 +2694,8 @@ namespace olc const olc::vf2d& p1, const olc::Pixel c1, const olc::vf2d& p2, - const olc::Pixel c2); + const olc::Pixel c2, + const olc::Pixel tint = olc::Colour::WHITE); // === Rectangles === @@ -2709,7 +2711,8 @@ namespace olc olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, - const olc::Pixel col = olc::Colour::WHITE); + const olc::Pixel col = olc::Colour::WHITE, + const olc::Pixel tint = olc::Colour::WHITE); // Draws a multiple colour rectangle, with linear colour interpolation const GPUTask& Rect( @@ -2729,7 +2732,8 @@ namespace olc const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, - const olc::Pixel colBR); + const olc::Pixel colBR, + const olc::Pixel tint = olc::Colour::WHITE); // Draws a filled, single colour rectangle const GPUTask& FilledRect( @@ -2743,7 +2747,8 @@ namespace olc olc::FilledBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, - const olc::Pixel col = olc::Colour::WHITE); + const olc::Pixel col = olc::Colour::WHITE, + const olc::Pixel tint = olc::Colour::WHITE); // Draws a filled, multiple colour rectangle, with linear colour interpolation const GPUTask& FilledRect( @@ -2763,7 +2768,8 @@ namespace olc const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, - const olc::Pixel colBR); + const olc::Pixel colBR, + const olc::Pixel tint = olc::Colour::WHITE); // === Circles === @@ -2781,6 +2787,7 @@ namespace olc const olc::vf2d& pos, const float& radius, const olc::Pixel col = olc::Colour::WHITE, + const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); // Draws a filled circle with a single colour @@ -2797,6 +2804,7 @@ namespace olc const olc::vf2d& pos, const float& radius, const olc::Pixel col = olc::Colour::WHITE, + const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); // Draws a shaded circle with a radial gradient @@ -2815,6 +2823,7 @@ namespace olc const float& radius, const olc::Pixel colInner, const olc::Pixel colOuter, + const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); // === Ellipses === @@ -2835,6 +2844,7 @@ namespace olc const float& rx, const float& ry, const olc::Pixel col = olc::Colour::WHITE, + const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); // Draws a filled ellipse with a single colour @@ -2853,6 +2863,7 @@ namespace olc const float& rx, const float& ry, const olc::Pixel col = olc::Colour::WHITE, + const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); // Draws a shaded ellipse with a radial gradient @@ -2873,6 +2884,7 @@ namespace olc const float& ry, const olc::Pixel colInner, const olc::Pixel colOuter, + const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); // === Rounded Rectangles === @@ -2893,6 +2905,7 @@ namespace olc const olc::vf2d& size, // Size of bounding rectangle const float& radius, const olc::Pixel col = olc::Colour::WHITE, + const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS / 4); // Draws a filled rounded rectangle with a single colour @@ -2921,7 +2934,8 @@ namespace olc const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, - const olc::Pixel col = olc::Colour::WHITE); + const olc::Pixel col = olc::Colour::WHITE, + const olc::Pixel tint = olc::Colour::WHITE); // Draws a multiple colour triangle outline const GPUTask& Triangle( @@ -2941,7 +2955,8 @@ namespace olc const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, - const olc::Pixel c3); + const olc::Pixel c3, + const olc::Pixel tint = olc::Colour::WHITE); // Draws a filled, single colour triangle const GPUTask& FilledTriangle( @@ -2957,7 +2972,8 @@ namespace olc const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, - const olc::Pixel col = olc::Colour::WHITE); + const olc::Pixel col = olc::Colour::WHITE, + const olc::Pixel tint = olc::Colour::WHITE); // Draws a filled, multiple colour triangle const GPUTask& FilledTriangle( @@ -2977,7 +2993,8 @@ namespace olc const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, - const olc::Pixel c3); + const olc::Pixel c3, + const olc::Pixel tint = olc::Colour::WHITE); // Draws a textured triangle, with per vertex colouring const GPUTask& TexturedTriangle( @@ -3005,7 +3022,8 @@ namespace olc const LineBatch& Polygon( olc::LineBatch& batch, const std::vector& vecPoints, - const olc::Pixel col = olc::Colour::WHITE); + const olc::Pixel col = olc::Colour::WHITE, + const olc::Pixel tint = olc::Colour::WHITE); // Draws a polygon outline with multiple colours const GPUTask& Polygon( @@ -3017,7 +3035,8 @@ namespace olc const LineBatch& Polygon( olc::LineBatch& batch, const std::vector& vecPoints, - const std::vector& vecColours); + const std::vector& vecColours, + const olc::Pixel tint = olc::Colour::WHITE); // === Structured Polygons (Outlines & Fills) === @@ -15300,72 +15319,72 @@ const GPUTask& olc::Draw::Batch(olc::LineBatch& batch, const olc::Pixel tint) return vecGPUTasks.data.emplace_back(batch.task); } -const LineBatch& olc::Draw::Line(olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::Pixel col) +const LineBatch& olc::Draw::Line(olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::Pixel col, const olc::Pixel tint) { - return Line(batch, p1, col, p2, col); + return Line(batch, p1, col, p2, col, tint); } -const LineBatch& olc::Draw::Line(olc::LineBatch& batch, const olc::vf2d& p1, const olc::Pixel c1, const olc::vf2d& p2, const olc::Pixel c2) +const LineBatch& olc::Draw::Line(olc::LineBatch& batch, const olc::vf2d& p1, const olc::Pixel c1, const olc::vf2d& p2, const olc::Pixel c2, const olc::Pixel tint) { batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + 2); size_t idx = batch.task.vertexBuffer.size() - 2; const olc::vf2d a1 = transformAffine.forwardRound(p1); const olc::vf2d a2 = transformAffine.forwardRound(p2); - batch.task.vertexBuffer[idx + 0] = { {a1.x, a1.y, 1.0f, 1.0f}, c1, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; - batch.task.vertexBuffer[idx + 1] = { {a2.x, a2.y, 1.0f, 1.0f}, c2, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + batch.task.vertexBuffer[idx + 0] = { {a1.x, a1.y, 1.0f, 1.0f}, c1.blend(tint), {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + batch.task.vertexBuffer[idx + 1] = { {a2.x, a2.y, 1.0f, 1.0f}, c2.blend(tint), {0, 0}, {0, 0}, {0, 0}, {0, 0} }; return batch; } -const LineBatch& olc::Draw::Rect(olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) +const LineBatch& olc::Draw::Rect(olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col, const olc::Pixel tint) { - return Rect(batch, pos, size, col, col, col, col); + return Rect(batch, pos, size, col, col, col, col, tint); } -const LineBatch& olc::Draw::Rect(olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR) +const LineBatch& olc::Draw::Rect(olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR, const olc::Pixel tint) { const olc::vf2d pTL = olc::vf2d(pos.x, pos.y); const olc::vf2d pTR = olc::vf2d(pos.x + size.x, pos.y); const olc::vf2d pBR = olc::vf2d(pos.x + size.x, pos.y + size.y); const olc::vf2d pBL = olc::vf2d(pos.x, pos.y + size.y); - Line(batch, pTL, colTL, pTR, colTR); - Line(batch, pTR, colTR, pBR, colBR); - Line(batch, pBR, colBR, pBL, colBL); - Line(batch, pBL, colBL, pTL, colTL); + Line(batch, pTL, colTL, pTR, colTR, tint); + Line(batch, pTR, colTR, pBR, colBR, tint); + Line(batch, pBR, colBR, pBL, colBL, tint); + Line(batch, pBL, colBL, pTL, colTL, tint); return batch; } -const FilledBatch& olc::Draw::FilledRect(olc::FilledBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) +const FilledBatch& olc::Draw::FilledRect(olc::FilledBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col, const olc::Pixel tint) { - FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y), olc::vf2d(pos.x + size.x, pos.y + size.y), col, col, col); - FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y + size.y), olc::vf2d(pos.x, pos.y + size.y), col, col, col); + FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y), olc::vf2d(pos.x + size.x, pos.y + size.y), col, col, col, tint); + FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y + size.y), olc::vf2d(pos.x, pos.y + size.y), col, col, col, tint); return batch; } -const FilledBatch& olc::Draw::FilledRect(olc::FilledBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR) +const FilledBatch& olc::Draw::FilledRect(olc::FilledBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, const olc::Pixel colTR, const olc::Pixel colBL, const olc::Pixel colBR, const olc::Pixel tint) { - FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y), olc::vf2d(pos.x + size.x, pos.y + size.y), colTL, colTR, colBR); - FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y + size.y), olc::vf2d(pos.x, pos.y + size.y), colTL, colBR, colBL); + FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y), olc::vf2d(pos.x + size.x, pos.y + size.y), colTL, colTR, colBR, tint); + FilledTriangle(batch, pos, olc::vf2d(pos.x + size.x, pos.y + size.y), olc::vf2d(pos.x, pos.y + size.y), colTL, colBR, colBL, tint); return batch; } -const LineBatch& olc::Draw::Circle(olc::LineBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel col, int32_t nFacets) +const LineBatch& olc::Draw::Circle(olc::LineBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { - return Ellipse(batch, pos, radius, radius, col, nFacets); + return Ellipse(batch, pos, radius, radius, col, tint, nFacets); } -const FilledBatch& olc::Draw::FilledCircle(olc::FilledBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel col, int32_t nFacets) +const FilledBatch& olc::Draw::FilledCircle(olc::FilledBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { - return FilledEllipse(batch, pos, radius, radius, col, nFacets); + return FilledEllipse(batch, pos, radius, radius, col, tint, nFacets); } -const FilledBatch& olc::Draw::FilledCircle(olc::FilledBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel colInner, const olc::Pixel colOuter, int32_t nFacets) +const FilledBatch& olc::Draw::FilledCircle(olc::FilledBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel colInner, const olc::Pixel colOuter, const olc::Pixel tint, int32_t nFacets) { - return FilledEllipse(batch, pos, radius, radius, colInner, colOuter, nFacets); + return FilledEllipse(batch, pos, radius, radius, colInner, colOuter, tint, nFacets); } -const LineBatch& olc::Draw::Ellipse(olc::LineBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, int32_t nFacets) +const LineBatch& olc::Draw::Ellipse(olc::LineBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { // Fundamental for batched outline circle/ellipse with colour solid/gradient @@ -15378,18 +15397,18 @@ const LineBatch& olc::Draw::Ellipse(olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d a2 = { buffUnitCirclePoints.data[(i + 1) % buffUnitCirclePoints.data.size()].x * rx + pos.x, buffUnitCirclePoints.data[(i + 1) % buffUnitCirclePoints.data.size()].y * ry + pos.y }; - Line(batch, a1, col, a2, col); + Line(batch, a1, col, a2, col, tint); } return batch; } -const FilledBatch& olc::Draw::FilledEllipse(olc::FilledBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, int32_t nFacets) +const FilledBatch& olc::Draw::FilledEllipse(olc::FilledBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { - return FilledEllipse(batch, pos, rx, ry, col, col, nFacets); + return FilledEllipse(batch, pos, rx, ry, col, col, tint, nFacets); } -const FilledBatch& olc::Draw::FilledEllipse(olc::FilledBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel colInner, const olc::Pixel colOuter, int32_t nFacets) +const FilledBatch& olc::Draw::FilledEllipse(olc::FilledBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, const olc::Pixel colInner, const olc::Pixel colOuter, const olc::Pixel tint, int32_t nFacets) { // Fundamental for batch filled circle/ellipse with colour solid/gradient @@ -15404,14 +15423,14 @@ const FilledBatch& olc::Draw::FilledEllipse(olc::FilledBatch& batch, const olc:: olc::vf2d p2 = { pos.x + rx * buffUnitCirclePoints.data[(i + 1) % buffUnitCirclePoints.data.size()].x, pos.y + ry * buffUnitCirclePoints.data[(i + 1) % buffUnitCirclePoints.data.size()].y }; - FilledTriangle(batch, pos, p1, p2, colInner, colOuter, colOuter); + FilledTriangle(batch, pos, p1, p2, colInner, colOuter, colOuter, tint); } return batch; } -const LineBatch& olc::Draw::RoundedRect(olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const float& radius, const olc::Pixel col, int32_t nFacets) +const LineBatch& olc::Draw::RoundedRect(olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const float& radius, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) { buffPoints.reserve((nFacets + 1) * 4 + 1); buffPoints.data.clear(); @@ -15451,62 +15470,62 @@ const LineBatch& olc::Draw::RoundedRect(olc::LineBatch& batch, const olc::vf2d& for (size_t i = 0; i < buffPoints.data.size() - 1; i++) { - Line(batch, buffPoints.data[i], col, buffPoints.data[i + 1], col); + Line(batch, buffPoints.data[i], col, buffPoints.data[i + 1], col, tint); } return batch; } -const LineBatch& olc::Draw::Triangle(olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col) +const LineBatch& olc::Draw::Triangle(olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col, const olc::Pixel tint) { - return Triangle(batch, p1, p2, p3, col, col, col); + return Triangle(batch, p1, p2, p3, col, col, col, tint); } -const LineBatch& olc::Draw::Triangle(olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3) +const LineBatch& olc::Draw::Triangle(olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3, const olc::Pixel tint) { - Line(batch, p1, c1, p2, c2); - Line(batch, p2, c2, p3, c3); - Line(batch, p3, c3, p1, c1); + Line(batch, p1, c1, p2, c2, tint); + Line(batch, p2, c2, p3, c3, tint); + Line(batch, p3, c3, p1, c1, tint); return batch; } -const FilledBatch& olc::Draw::FilledTriangle(olc::FilledBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col) +const FilledBatch& olc::Draw::FilledTriangle(olc::FilledBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col, const olc::Pixel tint) { - return FilledTriangle(batch, p1, p2, p3, col, col, col); + return FilledTriangle(batch, p1, p2, p3, col, col, col, tint); } -const FilledBatch& olc::Draw::FilledTriangle(olc::FilledBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3) +const FilledBatch& olc::Draw::FilledTriangle(olc::FilledBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel c1, const olc::Pixel c2, const olc::Pixel c3, const olc::Pixel tint) { batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + 3); size_t idx = batch.task.vertexBuffer.size() - 3; const olc::vf2d a1 = transformAffine.forwardRound(p1); const olc::vf2d a2 = transformAffine.forwardRound(p2); const olc::vf2d a3 = transformAffine.forwardRound(p3); - batch.task.vertexBuffer[idx + 0] = { {a1.x, a1.y, 1.0f, 1.0f}, c1, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; - batch.task.vertexBuffer[idx + 1] = { {a2.x, a2.y, 1.0f, 1.0f}, c2, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; - batch.task.vertexBuffer[idx + 2] = { {a3.x, a3.y, 1.0f, 1.0f}, c3, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + batch.task.vertexBuffer[idx + 0] = { {a1.x, a1.y, 1.0f, 1.0f}, c1.blend(tint), {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + batch.task.vertexBuffer[idx + 1] = { {a2.x, a2.y, 1.0f, 1.0f}, c2.blend(tint), {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + batch.task.vertexBuffer[idx + 2] = { {a3.x, a3.y, 1.0f, 1.0f}, c3.blend(tint), {0, 0}, {0, 0}, {0, 0}, {0, 0} }; return batch; } -const LineBatch& olc::Draw::Polygon(olc::LineBatch& batch, const std::vector& vecPoints, const olc::Pixel col) +const LineBatch& olc::Draw::Polygon(olc::LineBatch& batch, const std::vector& vecPoints, const olc::Pixel col, const olc::Pixel tint) { for (size_t i = 0; i < vecPoints.size(); i++) { - Line(batch, vecPoints[i], col, vecPoints[(i + 1) % vecPoints.size()], col); + Line(batch, vecPoints[i], col, vecPoints[(i + 1) % vecPoints.size()], col, tint); } return batch; } -const LineBatch& olc::Draw::Polygon(olc::LineBatch& batch, const std::vector& vecPoints, const std::vector& vecColours) +const LineBatch& olc::Draw::Polygon(olc::LineBatch& batch, const std::vector& vecPoints, const std::vector& vecColours, const olc::Pixel tint) { for (size_t i = 0; i < vecPoints.size(); i++) { - Line(batch, vecPoints[i], vecColours[i], vecPoints[(i + 1) % vecPoints.size()], vecColours[(i + 1) % vecColours.size()]); + Line(batch, vecPoints[i], vecColours[i], vecPoints[(i + 1) % vecPoints.size()], vecColours[(i + 1) % vecColours.size()], tint); } return batch; @@ -15595,6 +15614,7 @@ const ImageBatch& olc::Draw::Image(ImageBatch& batch, olc::ImageRegion image, co olc::vf2d p3 = transformAffine.forward(olc::vf2d{ pos.x, pos.y + size.y }); //batch.task.vertexBuffer.reserve(batch.task.vertexBuffer.size() + 6); // NOTE!! This tanked performance on large batches + // NOTE: We fake tint by simply setting vertex colour batch.task.vertexBuffer.push_back({ {p0.x, p0.y, 1.0f, 1.0f}, tint, {image.coords[0].x, image.coords[0].y}, {0, 0}, {0, 0}, {0, 0} }); batch.task.vertexBuffer.push_back({ {p1.x, p1.y, 1.0f, 1.0f}, tint, {image.coords[1].x, image.coords[1].y}, {0, 0}, {0, 0}, {0, 0} }); batch.task.vertexBuffer.push_back({ {p2.x, p2.y, 1.0f, 1.0f}, tint, {image.coords[2].x, image.coords[2].y}, {0, 0}, {0, 0}, {0, 0} }); From 4fb41509b9d2ec56008a1b6dfee87b463bb65b44 Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Sat, 14 Feb 2026 22:48:56 +0000 Subject: [PATCH 50/58] rearranged batch declarations to stop moros bitchin with his feeble ide --- dev/src/draw.h | 304 ++++++++++++++++++++++-------------------- olcPixelGameEngine3.h | 245 ++++++++++++++++++---------------- 2 files changed, 293 insertions(+), 256 deletions(-) diff --git a/dev/src/draw.h b/dev/src/draw.h index 1a763246..b1ead2c0 100644 --- a/dev/src/draw.h +++ b/dev/src/draw.h @@ -58,76 +58,79 @@ Note A: TexturedXXX batch functions use an image batch, naturally restricting all shapes in the batch to use the same texture source. + Note B: The functions are declared as "batch first". After experimentation it turns out that this + makes intellisense more sensible, showing the primitive overloads first + [#] Clear(col) [#] Pixel(pos, col) [#] Line(p1, p2, col, [tint]) [#] Line(p1, c1, p2, c2, [tint]) - [#] Line(batch, p1, p2, col) - [#] Line(batch, p1, c1, p2, c2) + [#] Line(batch, p1, p2, col, [tint]) + [#] Line(batch, p1, c1, p2, c2, [tint]) [#] Rect(pos, size, col, [tint]) [#] Rect(pos, size, colTL, colTR, colBL, colBR, [tint]) - [#] Rect(batch, pos, size, col) - [#] Rect(batch, pos, size, colTL, colTR, colBL, colBR) + [#] Rect(batch, pos, size, col, [tint]) + [#] Rect(batch, pos, size, colTL, colTR, colBL, colBR, [tint]) [#] FilledRect(pos, size, col, [tint]) [#] FilledRect(pos, size, colTL, colTR, colBL, colBR, [tint]) - [#] FilledRect(batch, pos, size, col) - [#] FilledRect(batch, pos, size, colTL, colTR, colBL, colBR) + [#] FilledRect(batch, pos, size, col, [tint]) + [#] FilledRect(batch, pos, size, colTL, colTR, colBL, colBR, [tint]) [#] Circle(pos, radius, col, [tint], [facets]) [#] Circle(pos, radius, colInner, colOuter, [tint], [facets]) - [#] Circle(batch, pos, radius, col, [facets]) - [#] Circle(batch, pos, radius, colInner, colOuter, [facets]) + [#] Circle(batch, pos, radius, col, [tint], [facets]) + [#] Circle(batch, pos, radius, colInner, colOuter, [tint], [facets]) [#] FilledCircle(pos, radius, col, [tint], [facets]) [#] FilledCircle(pos, radius, colInner, colOuter, [tint], [facets]) - [#] FilledCircle(batch, pos, radius, col, [facets]) - [#] FilledCircle(batch, pos, radius, colInner, colOuter, [facets]) + [#] FilledCircle(batch, pos, radius, col, [tint], [facets]) + [#] FilledCircle(batch, pos, radius, colInner, colOuter, [tint], [facets]) [#] Ellipse(pos, radiusX, radiusY, col, [tint], [facets]) [#] Ellipse(pos, radiusX, radiusY, colInner, colOuter, [tint], [facets]) - [#] Ellipse(batch, pos, radiusX, radiusY, col, [facets]) - [#] Ellipse(batch, pos, radiusX, radiusY, colInner, colOuter, [facets]) + [#] Ellipse(batch, pos, radiusX, radiusY, col, [tint], [facets]) + [#] Ellipse(batch, pos, radiusX, radiusY, colInner, colOuter, [tint], [facets]) [#] FilledEllipse(pos, radiusX, radiusY, col, [tint], [facets]) [#] FilledEllipse(pos, radiusX, radiusY, colInner, colOuter, [tint], [facets]) - [#] FilledEllipse(batch, pos, radiusX, radiusY, col, [facets]) - [#] FilledEllipse(batch, pos, radiusX, radiusY, colInner, colOuter, [facets]) + [#] FilledEllipse(batch, pos, radiusX, radiusY, col, [tint], [facets]) + [#] FilledEllipse(batch, pos, radiusX, radiusY, colInner, colOuter, [tint], [facets]) [#] RoundedRect(pos, size, radius, col, [tint], [facets/4]) [#] FilledRoundedRect(pos, size, radius, col, [tint], [facets/4]) - [ ] RoundedRect(batch, pos, size, radius, col, [facets/4]) - [ ] FilledRoundedRect(batch, pos, size, radius, col, [facets/4]) + [ ] RoundedRect(batch, pos, size, radius, col, [tint], [facets/4]) + [ ] FilledRoundedRect(batch, pos, size, radius, col, [tint], [facets/4]) [#] Triangle(p1, p2, p3, col, [tint]) [#] Triangle(p1, c1, p2, c2, p3, c3, [tint]) - [#] Triangle(batch, p1, p2, p3, col) - [#] Triangle(batch, p1, c1, p2, c2, p3, c3) + [#] Triangle(batch, p1, p2, p3, col, [tint]) + [#] Triangle(batch, p1, c1, p2, c2, p3, c3, [tint]) [#] FilledTriangle(p1, p2, p3, col, [tint]) [#] FilledTriangle(p1, c1, p2, c2, p3, c3, [tint]) - [#] FilledTriangle(batch, p1, p2, p3, col) - [#] FilledTriangle(batch, p1, c1, p2, c2, p3, c3) + [#] FilledTriangle(batch, p1, p2, p3, col, [tint]) + [#] FilledTriangle(batch, p1, c1, p2, c2, p3, c3, [tint]) [#] Polygon(structure, points[], col, [tint]) [#] Polygon(structure, points[], colours[], [tint]) - [ ] Polygon(batch, structure, points[], col) - [ ] Polygon(batch, structure, points[], colours[]) + [#] Polygon(batch, structure, points[], col, [tint]) + [#] Polygon(batch, structure, points[], colours[], [tint]) [#] FilledPolygon(structure, points[], col, [tint]) [#] FilledPolygon(structure, points[], colours[], [tint]) [ ] FilledPolygon(batch, structure, points[], col) - [ ] FilledPolygon(batch, structure, points[], colours[]) + [#] FilledPolygon(batch, structure, points[], colours[]) [#] TexturedTriangle(p1, p2, p3, c1, c2, c3, uv1, uv2, uv3, image, [tint]) - [ ] TexturedTriangle(batch, p1, p2, p3, c1, c2, c3, uv1, uv2, uv3, image) + [ ] TexturedTriangle(batch, p1, p2, p3, c1, c2, c3, uv1, uv2, uv3, image, [tint]) [#] TexturedPolygon(structure, points[], colours[], uvs[], image, [tint]) - [ ] TexturedPolygon(batch, structure, points[], colours[], uvs[], image) + [ ] TexturedPolygon(batch, structure, points[], colours[], uvs[], image, [tint]) [#] String(pos, text, col, [scale], [font]) [#] StringProp(pos, text, col, [scale], [font]) @@ -154,7 +157,11 @@ 3D Rendering Functions ~~~~~~~~~~~~~~~~~~~~~~ - + [#] Line(p1, p2, col, [tint]) + + [#] Mesh(structure, points[], col, [tint]) + [#] Mesh(structure, points[], colours[], [tint]) + [#] Mesh(structure, points[], colours[], uvs[], image, [tint]) */ @@ -241,13 +248,6 @@ namespace olc // === Lines === - // Draws a single pixel wide line - const GPUTask& Line( - const olc::vf2d& p1, - const olc::vf2d& p2, - const olc::Pixel col = olc::Colour::WHITE, - const olc::Pixel tint = olc::Colour::WHITE); - // Draws a single pixel wide line into a batch const LineBatch& Line( olc::LineBatch& batch, @@ -255,6 +255,15 @@ namespace olc const olc::vf2d& p2, const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE); + + // Draws a single pixel wide line with a gradient into a batch + const LineBatch& Line( + olc::LineBatch& batch, + const olc::vf2d& p1, + const olc::Pixel c1, + const olc::vf2d& p2, + const olc::Pixel c2, + const olc::Pixel tint = olc::Colour::WHITE); // Draws a single pixel wide line with a gradient const GPUTask& Line( @@ -264,22 +273,24 @@ namespace olc const olc::Pixel c2, const olc::Pixel tint = olc::Colour::WHITE); - // Draws a single pixel wide line with a gradient into a batch - const LineBatch& Line( - olc::LineBatch& batch, - const olc::vf2d& p1, - const olc::Pixel c1, - const olc::vf2d& p2, - const olc::Pixel c2, + // Draws a single pixel wide line + const GPUTask& Line( + const olc::vf2d& p1, + const olc::vf2d& p2, + const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE); // === Rectangles === - // Draws a rectangle outline - const GPUTask& Rect( - const olc::vf2d& pos, - const olc::vf2d& size, - const olc::Pixel col = olc::Colour::WHITE, + // Draws a multiple colour rectangle, with linear colour interpolation, into a batch + const LineBatch& Rect( + olc::LineBatch& batch, + const olc::vf2d& pos, + const olc::vf2d& size, + const olc::Pixel colTL, + const olc::Pixel colTR, + const olc::Pixel colBL, + const olc::Pixel colBR, const olc::Pixel tint = olc::Colour::WHITE); // Draws a rectangle outline into a batch @@ -300,9 +311,16 @@ namespace olc const olc::Pixel colBR, const olc::Pixel tint = olc::Colour::WHITE); - // Draws a multiple colour rectangle, with linear colour interpolation, into a batch - const LineBatch& Rect( - olc::LineBatch& batch, + // Draws a rectangle outline + const GPUTask& Rect( + const olc::vf2d& pos, + const olc::vf2d& size, + const olc::Pixel col = olc::Colour::WHITE, + const olc::Pixel tint = olc::Colour::WHITE); + + // Draws a filled, multiple colour rectangle, with linear colour interpolation, into a batch + const FilledBatch& FilledRect( + olc::FilledBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, @@ -311,13 +329,6 @@ namespace olc const olc::Pixel colBR, const olc::Pixel tint = olc::Colour::WHITE); - // Draws a filled, single colour rectangle - const GPUTask& FilledRect( - const olc::vf2d& pos, - const olc::vf2d& size, - const olc::Pixel col = olc::Colour::WHITE, - const olc::Pixel tint = olc::Colour::WHITE); - // Draws a filled, single colour rectangle into a batch const FilledBatch& FilledRect( olc::FilledBatch& batch, @@ -335,27 +346,18 @@ namespace olc const olc::Pixel colBL, const olc::Pixel colBR, const olc::Pixel tint = olc::Colour::WHITE); - - // Draws a filled, multiple colour rectangle, with linear colour interpolation, into a batch - const FilledBatch& FilledRect( - olc::FilledBatch& batch, - const olc::vf2d& pos, - const olc::vf2d& size, - const olc::Pixel colTL, - const olc::Pixel colTR, - const olc::Pixel colBL, - const olc::Pixel colBR, + + // Draws a filled, single colour rectangle + const GPUTask& FilledRect( + const olc::vf2d& pos, + const olc::vf2d& size, + const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE); + // === Circles === - // Draws a circle outline with a single colour - const GPUTask& Circle( - const olc::vf2d& pos, - const float& radius, - const olc::Pixel col = olc::Colour::WHITE, - const olc::Pixel tint = olc::Colour::WHITE, - int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); + // Draws a circle outline with a single colour into a batch const LineBatch& Circle( @@ -366,20 +368,22 @@ namespace olc const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); - // Draws a filled circle with a single colour - const GPUTask& FilledCircle( + // Draws a filled circle with a single colour into a batch + const FilledBatch& FilledCircle( + olc::FilledBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); - // Draws a filled circle with a single colour into a batch + // Draws a shaded circle with a radial gradient into a batch const FilledBatch& FilledCircle( olc::FilledBatch& batch, const olc::vf2d& pos, const float& radius, - const olc::Pixel col = olc::Colour::WHITE, + const olc::Pixel colInner, + const olc::Pixel colOuter, const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); @@ -392,27 +396,24 @@ namespace olc const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); - // Draws a shaded circle with a radial gradient into a batch - const FilledBatch& FilledCircle( - olc::FilledBatch& batch, + // Draws a filled circle with a single colour + const GPUTask& FilledCircle( const olc::vf2d& pos, const float& radius, - const olc::Pixel colInner, - const olc::Pixel colOuter, + const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); -// === Ellipses === - - // Draws an ellipse outline with a single colour - const GPUTask& Ellipse( + // Draws a circle outline with a single colour + const GPUTask& Circle( const olc::vf2d& pos, - const float& rx, - const float& ry, + const float& radius, const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); +// === Ellipses === + // Draws an ellipse outline with a single colour into a batch const LineBatch& Ellipse( olc::LineBatch& batch, @@ -423,8 +424,9 @@ namespace olc const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); - // Draws a filled ellipse with a single colour - const GPUTask& FilledEllipse( + // Draws a filled ellipse with a single colour into a batch + const FilledBatch& FilledEllipse( + olc::FilledBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, @@ -432,9 +434,19 @@ namespace olc const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); - // Draws a filled ellipse with a single colour into a batch + // Draws a shaded ellipse with a radial gradient into a batch const FilledBatch& FilledEllipse( olc::FilledBatch& batch, + const olc::vf2d& pos, + const float& rx, + const float& ry, + const olc::Pixel colInner, + const olc::Pixel colOuter, + const olc::Pixel tint = olc::Colour::WHITE, + int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); + + // Draws an ellipse outline with a single colour + const GPUTask& Ellipse( const olc::vf2d& pos, const float& rx, const float& ry, @@ -442,19 +454,17 @@ namespace olc const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); - // Draws a shaded ellipse with a radial gradient + // Draws a filled ellipse with a single colour const GPUTask& FilledEllipse( const olc::vf2d& pos, const float& rx, const float& ry, - const olc::Pixel colInner, - const olc::Pixel colOuter, + const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); - // Draws a shaded ellipse with a radial gradient into a batch - const FilledBatch& FilledEllipse( - olc::FilledBatch& batch, + // Draws a shaded ellipse with a radial gradient + const GPUTask& FilledEllipse( const olc::vf2d& pos, const float& rx, const float& ry, @@ -465,8 +475,11 @@ namespace olc // === Rounded Rectangles === - // Draws a rounded rectangle outline with a single colour - const GPUTask& RoundedRect( + + + // Draws a rounded rectangle outline with a single colour into a batch + const LineBatch& RoundedRect( + olc::LineBatch& batch, const olc::vf2d& pos, // Top left of bounding rectangle const olc::vf2d& size, // Size of bounding rectangle const float& radius, @@ -474,9 +487,8 @@ namespace olc const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS / 4); - // Draws a rounded rectangle outline with a single colour into a batch - const LineBatch& RoundedRect( - olc::LineBatch& batch, + // Draws a filled rounded rectangle with a single colour + const GPUTask& FilledRoundedRect( const olc::vf2d& pos, // Top left of bounding rectangle const olc::vf2d& size, // Size of bounding rectangle const float& radius, @@ -484,8 +496,8 @@ namespace olc const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS / 4); - // Draws a filled rounded rectangle with a single colour - const GPUTask& FilledRoundedRect( + // Draws a rounded rectangle outline with a single colour + const GPUTask& RoundedRect( const olc::vf2d& pos, // Top left of bounding rectangle const olc::vf2d& size, // Size of bounding rectangle const float& radius, @@ -496,13 +508,7 @@ namespace olc // === Triangles === - // Draws a triangle outline with a single colour - const GPUTask& Triangle( - const olc::vf2d& p1, - const olc::vf2d& p2, - const olc::vf2d& p3, - const olc::Pixel col = olc::Colour::WHITE, - const olc::Pixel tint = olc::Colour::WHITE); + // Draws a triangle outline with a single colour into a batch const LineBatch& Triangle( @@ -513,8 +519,9 @@ namespace olc const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE); - // Draws a multiple colour triangle outline - const GPUTask& Triangle( + // Draws a multiple colour triangle outline into a batch + const LineBatch& Triangle( + olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, @@ -523,9 +530,8 @@ namespace olc const olc::Pixel c3, const olc::Pixel tint = olc::Colour::WHITE); - // Draws a multiple colour triangle outline into a batch - const LineBatch& Triangle( - olc::LineBatch& batch, + // Draws a multiple colour triangle outline + const GPUTask& Triangle( const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, @@ -534,14 +540,17 @@ namespace olc const olc::Pixel c3, const olc::Pixel tint = olc::Colour::WHITE); - // Draws a filled, single colour triangle - const GPUTask& FilledTriangle( + + // Draws a triangle outline with a single colour + const GPUTask& Triangle( const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE); + + // Draws a filled, single colour triangle into a batch const FilledBatch& FilledTriangle( olc::FilledBatch& batch, @@ -550,9 +559,10 @@ namespace olc const olc::vf2d& p3, const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE); - - // Draws a filled, multiple colour triangle - const GPUTask& FilledTriangle( + + // Draws a filled, multiple colour triangle into a batch + const FilledBatch& FilledTriangle( + olc::FilledBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, @@ -561,9 +571,8 @@ namespace olc const olc::Pixel c3, const olc::Pixel tint = olc::Colour::WHITE); - // Draws a filled, multiple colour triangle into a batch - const FilledBatch& FilledTriangle( - olc::FilledBatch& batch, + // Draws a filled, multiple colour triangle + const GPUTask& FilledTriangle( const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, @@ -572,6 +581,14 @@ namespace olc const olc::Pixel c3, const olc::Pixel tint = olc::Colour::WHITE); + // Draws a filled, single colour triangle + const GPUTask& FilledTriangle( + const olc::vf2d& p1, + const olc::vf2d& p2, + const olc::vf2d& p3, + const olc::Pixel col = olc::Colour::WHITE, + const olc::Pixel tint = olc::Colour::WHITE); + // Draws a textured triangle, with per vertex colouring const GPUTask& TexturedTriangle( const olc::vf2d& p1, @@ -588,11 +605,7 @@ namespace olc // === Polygon Outlines === - // Draws a polygon outline with a single colour - const GPUTask& Polygon( - const std::vector& vecPoints, - const olc::Pixel col = olc::Colour::WHITE, - const olc::Pixel tint = olc::Colour::WHITE); + // Draws a polygon outline with a single colour into a batch const LineBatch& Polygon( @@ -600,27 +613,34 @@ namespace olc const std::vector& vecPoints, const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE); - + + // Draws a polygon outline with multiple colours into a batch + const LineBatch& Polygon( + olc::LineBatch& batch, + const std::vector& vecPoints, + const std::vector& vecColours, + const olc::Pixel tint = olc::Colour::WHITE); + // Draws a polygon outline with multiple colours const GPUTask& Polygon( const std::vector& vecPoints, const std::vector& vecColours, const olc::Pixel tint = olc::Colour::WHITE); - // Draws a polygon outline with multiple colours into a batch - const LineBatch& Polygon( - olc::LineBatch& batch, + // Draws a polygon outline with a single colour + const GPUTask& Polygon( const std::vector& vecPoints, - const std::vector& vecColours, + const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE); // === Structured Polygons (Outlines & Fills) === + // Draws a polygon outline with a single colour const GPUTask& Polygon( const olc::Structure structure, const std::vector& vecPoints, - const olc::Pixel col = olc::Colour::WHITE, + const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE); // Draws a polygon outline with multiple colours @@ -630,6 +650,14 @@ namespace olc const std::vector& vecColours, const olc::Pixel tint = olc::Colour::WHITE); + // Draws a filled polygon with multiple colours into a btach + const FilledBatch& FilledPolygon( + FilledBatch& batch, + const olc::Structure structure, + const std::vector& vecPoints, + const std::vector& vecColours, + const olc::Pixel tint = olc::Colour::WHITE); + // Draws a filled polygon with a single colour const GPUTask& FilledPolygon( const olc::Structure structure, @@ -644,13 +672,7 @@ namespace olc const std::vector& vecColours, const olc::Pixel tint = olc::Colour::WHITE); - // Draws a filled polygon with multiple colours into a btach - const FilledBatch& FilledPolygon( - FilledBatch& batch, - const olc::Structure structure, - const std::vector& vecPoints, - const std::vector& vecColours, - const olc::Pixel tint = olc::Colour::WHITE); + // Draws a textured polygon with per vertex colouring const GPUTask& TexturedPolygon( diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 13516efa..25cd1ecd 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -2665,13 +2665,6 @@ namespace olc // === Lines === - // Draws a single pixel wide line - const GPUTask& Line( - const olc::vf2d& p1, - const olc::vf2d& p2, - const olc::Pixel col = olc::Colour::WHITE, - const olc::Pixel tint = olc::Colour::WHITE); - // Draws a single pixel wide line into a batch const LineBatch& Line( olc::LineBatch& batch, @@ -2679,6 +2672,15 @@ namespace olc const olc::vf2d& p2, const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE); + + // Draws a single pixel wide line with a gradient into a batch + const LineBatch& Line( + olc::LineBatch& batch, + const olc::vf2d& p1, + const olc::Pixel c1, + const olc::vf2d& p2, + const olc::Pixel c2, + const olc::Pixel tint = olc::Colour::WHITE); // Draws a single pixel wide line with a gradient const GPUTask& Line( @@ -2688,22 +2690,24 @@ namespace olc const olc::Pixel c2, const olc::Pixel tint = olc::Colour::WHITE); - // Draws a single pixel wide line with a gradient into a batch - const LineBatch& Line( - olc::LineBatch& batch, - const olc::vf2d& p1, - const olc::Pixel c1, - const olc::vf2d& p2, - const olc::Pixel c2, + // Draws a single pixel wide line + const GPUTask& Line( + const olc::vf2d& p1, + const olc::vf2d& p2, + const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE); // === Rectangles === - // Draws a rectangle outline - const GPUTask& Rect( - const olc::vf2d& pos, - const olc::vf2d& size, - const olc::Pixel col = olc::Colour::WHITE, + // Draws a multiple colour rectangle, with linear colour interpolation, into a batch + const LineBatch& Rect( + olc::LineBatch& batch, + const olc::vf2d& pos, + const olc::vf2d& size, + const olc::Pixel colTL, + const olc::Pixel colTR, + const olc::Pixel colBL, + const olc::Pixel colBR, const olc::Pixel tint = olc::Colour::WHITE); // Draws a rectangle outline into a batch @@ -2724,9 +2728,16 @@ namespace olc const olc::Pixel colBR, const olc::Pixel tint = olc::Colour::WHITE); - // Draws a multiple colour rectangle, with linear colour interpolation, into a batch - const LineBatch& Rect( - olc::LineBatch& batch, + // Draws a rectangle outline + const GPUTask& Rect( + const olc::vf2d& pos, + const olc::vf2d& size, + const olc::Pixel col = olc::Colour::WHITE, + const olc::Pixel tint = olc::Colour::WHITE); + + // Draws a filled, multiple colour rectangle, with linear colour interpolation, into a batch + const FilledBatch& FilledRect( + olc::FilledBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel colTL, @@ -2735,13 +2746,6 @@ namespace olc const olc::Pixel colBR, const olc::Pixel tint = olc::Colour::WHITE); - // Draws a filled, single colour rectangle - const GPUTask& FilledRect( - const olc::vf2d& pos, - const olc::vf2d& size, - const olc::Pixel col = olc::Colour::WHITE, - const olc::Pixel tint = olc::Colour::WHITE); - // Draws a filled, single colour rectangle into a batch const FilledBatch& FilledRect( olc::FilledBatch& batch, @@ -2759,27 +2763,18 @@ namespace olc const olc::Pixel colBL, const olc::Pixel colBR, const olc::Pixel tint = olc::Colour::WHITE); - - // Draws a filled, multiple colour rectangle, with linear colour interpolation, into a batch - const FilledBatch& FilledRect( - olc::FilledBatch& batch, - const olc::vf2d& pos, - const olc::vf2d& size, - const olc::Pixel colTL, - const olc::Pixel colTR, - const olc::Pixel colBL, - const olc::Pixel colBR, + + // Draws a filled, single colour rectangle + const GPUTask& FilledRect( + const olc::vf2d& pos, + const olc::vf2d& size, + const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE); + // === Circles === - // Draws a circle outline with a single colour - const GPUTask& Circle( - const olc::vf2d& pos, - const float& radius, - const olc::Pixel col = olc::Colour::WHITE, - const olc::Pixel tint = olc::Colour::WHITE, - int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); + // Draws a circle outline with a single colour into a batch const LineBatch& Circle( @@ -2790,20 +2785,22 @@ namespace olc const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); - // Draws a filled circle with a single colour - const GPUTask& FilledCircle( + // Draws a filled circle with a single colour into a batch + const FilledBatch& FilledCircle( + olc::FilledBatch& batch, const olc::vf2d& pos, const float& radius, const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); - // Draws a filled circle with a single colour into a batch + // Draws a shaded circle with a radial gradient into a batch const FilledBatch& FilledCircle( olc::FilledBatch& batch, const olc::vf2d& pos, const float& radius, - const olc::Pixel col = olc::Colour::WHITE, + const olc::Pixel colInner, + const olc::Pixel colOuter, const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); @@ -2816,27 +2813,24 @@ namespace olc const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); - // Draws a shaded circle with a radial gradient into a batch - const FilledBatch& FilledCircle( - olc::FilledBatch& batch, + // Draws a filled circle with a single colour + const GPUTask& FilledCircle( const olc::vf2d& pos, const float& radius, - const olc::Pixel colInner, - const olc::Pixel colOuter, + const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); -// === Ellipses === - - // Draws an ellipse outline with a single colour - const GPUTask& Ellipse( + // Draws a circle outline with a single colour + const GPUTask& Circle( const olc::vf2d& pos, - const float& rx, - const float& ry, + const float& radius, const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); +// === Ellipses === + // Draws an ellipse outline with a single colour into a batch const LineBatch& Ellipse( olc::LineBatch& batch, @@ -2847,8 +2841,9 @@ namespace olc const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); - // Draws a filled ellipse with a single colour - const GPUTask& FilledEllipse( + // Draws a filled ellipse with a single colour into a batch + const FilledBatch& FilledEllipse( + olc::FilledBatch& batch, const olc::vf2d& pos, const float& rx, const float& ry, @@ -2856,9 +2851,19 @@ namespace olc const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); - // Draws a filled ellipse with a single colour into a batch + // Draws a shaded ellipse with a radial gradient into a batch const FilledBatch& FilledEllipse( olc::FilledBatch& batch, + const olc::vf2d& pos, + const float& rx, + const float& ry, + const olc::Pixel colInner, + const olc::Pixel colOuter, + const olc::Pixel tint = olc::Colour::WHITE, + int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); + + // Draws an ellipse outline with a single colour + const GPUTask& Ellipse( const olc::vf2d& pos, const float& rx, const float& ry, @@ -2866,19 +2871,17 @@ namespace olc const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); - // Draws a shaded ellipse with a radial gradient + // Draws a filled ellipse with a single colour const GPUTask& FilledEllipse( const olc::vf2d& pos, const float& rx, const float& ry, - const olc::Pixel colInner, - const olc::Pixel colOuter, + const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); - // Draws a shaded ellipse with a radial gradient into a batch - const FilledBatch& FilledEllipse( - olc::FilledBatch& batch, + // Draws a shaded ellipse with a radial gradient + const GPUTask& FilledEllipse( const olc::vf2d& pos, const float& rx, const float& ry, @@ -2889,8 +2892,11 @@ namespace olc // === Rounded Rectangles === - // Draws a rounded rectangle outline with a single colour - const GPUTask& RoundedRect( + + + // Draws a rounded rectangle outline with a single colour into a batch + const LineBatch& RoundedRect( + olc::LineBatch& batch, const olc::vf2d& pos, // Top left of bounding rectangle const olc::vf2d& size, // Size of bounding rectangle const float& radius, @@ -2898,9 +2904,8 @@ namespace olc const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS / 4); - // Draws a rounded rectangle outline with a single colour into a batch - const LineBatch& RoundedRect( - olc::LineBatch& batch, + // Draws a filled rounded rectangle with a single colour + const GPUTask& FilledRoundedRect( const olc::vf2d& pos, // Top left of bounding rectangle const olc::vf2d& size, // Size of bounding rectangle const float& radius, @@ -2908,8 +2913,8 @@ namespace olc const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS / 4); - // Draws a filled rounded rectangle with a single colour - const GPUTask& FilledRoundedRect( + // Draws a rounded rectangle outline with a single colour + const GPUTask& RoundedRect( const olc::vf2d& pos, // Top left of bounding rectangle const olc::vf2d& size, // Size of bounding rectangle const float& radius, @@ -2920,13 +2925,7 @@ namespace olc // === Triangles === - // Draws a triangle outline with a single colour - const GPUTask& Triangle( - const olc::vf2d& p1, - const olc::vf2d& p2, - const olc::vf2d& p3, - const olc::Pixel col = olc::Colour::WHITE, - const olc::Pixel tint = olc::Colour::WHITE); + // Draws a triangle outline with a single colour into a batch const LineBatch& Triangle( @@ -2937,8 +2936,9 @@ namespace olc const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE); - // Draws a multiple colour triangle outline - const GPUTask& Triangle( + // Draws a multiple colour triangle outline into a batch + const LineBatch& Triangle( + olc::LineBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, @@ -2947,9 +2947,8 @@ namespace olc const olc::Pixel c3, const olc::Pixel tint = olc::Colour::WHITE); - // Draws a multiple colour triangle outline into a batch - const LineBatch& Triangle( - olc::LineBatch& batch, + // Draws a multiple colour triangle outline + const GPUTask& Triangle( const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, @@ -2958,14 +2957,17 @@ namespace olc const olc::Pixel c3, const olc::Pixel tint = olc::Colour::WHITE); - // Draws a filled, single colour triangle - const GPUTask& FilledTriangle( + + // Draws a triangle outline with a single colour + const GPUTask& Triangle( const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE); + + // Draws a filled, single colour triangle into a batch const FilledBatch& FilledTriangle( olc::FilledBatch& batch, @@ -2974,9 +2976,10 @@ namespace olc const olc::vf2d& p3, const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE); - - // Draws a filled, multiple colour triangle - const GPUTask& FilledTriangle( + + // Draws a filled, multiple colour triangle into a batch + const FilledBatch& FilledTriangle( + olc::FilledBatch& batch, const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, @@ -2985,9 +2988,8 @@ namespace olc const olc::Pixel c3, const olc::Pixel tint = olc::Colour::WHITE); - // Draws a filled, multiple colour triangle into a batch - const FilledBatch& FilledTriangle( - olc::FilledBatch& batch, + // Draws a filled, multiple colour triangle + const GPUTask& FilledTriangle( const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, @@ -2996,6 +2998,14 @@ namespace olc const olc::Pixel c3, const olc::Pixel tint = olc::Colour::WHITE); + // Draws a filled, single colour triangle + const GPUTask& FilledTriangle( + const olc::vf2d& p1, + const olc::vf2d& p2, + const olc::vf2d& p3, + const olc::Pixel col = olc::Colour::WHITE, + const olc::Pixel tint = olc::Colour::WHITE); + // Draws a textured triangle, with per vertex colouring const GPUTask& TexturedTriangle( const olc::vf2d& p1, @@ -3012,11 +3022,7 @@ namespace olc // === Polygon Outlines === - // Draws a polygon outline with a single colour - const GPUTask& Polygon( - const std::vector& vecPoints, - const olc::Pixel col = olc::Colour::WHITE, - const olc::Pixel tint = olc::Colour::WHITE); + // Draws a polygon outline with a single colour into a batch const LineBatch& Polygon( @@ -3024,27 +3030,34 @@ namespace olc const std::vector& vecPoints, const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE); - + + // Draws a polygon outline with multiple colours into a batch + const LineBatch& Polygon( + olc::LineBatch& batch, + const std::vector& vecPoints, + const std::vector& vecColours, + const olc::Pixel tint = olc::Colour::WHITE); + // Draws a polygon outline with multiple colours const GPUTask& Polygon( const std::vector& vecPoints, const std::vector& vecColours, const olc::Pixel tint = olc::Colour::WHITE); - // Draws a polygon outline with multiple colours into a batch - const LineBatch& Polygon( - olc::LineBatch& batch, + // Draws a polygon outline with a single colour + const GPUTask& Polygon( const std::vector& vecPoints, - const std::vector& vecColours, + const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE); // === Structured Polygons (Outlines & Fills) === + // Draws a polygon outline with a single colour const GPUTask& Polygon( const olc::Structure structure, const std::vector& vecPoints, - const olc::Pixel col = olc::Colour::WHITE, + const olc::Pixel col = olc::Colour::WHITE, const olc::Pixel tint = olc::Colour::WHITE); // Draws a polygon outline with multiple colours @@ -3054,6 +3067,14 @@ namespace olc const std::vector& vecColours, const olc::Pixel tint = olc::Colour::WHITE); + // Draws a filled polygon with multiple colours into a btach + const FilledBatch& FilledPolygon( + FilledBatch& batch, + const olc::Structure structure, + const std::vector& vecPoints, + const std::vector& vecColours, + const olc::Pixel tint = olc::Colour::WHITE); + // Draws a filled polygon with a single colour const GPUTask& FilledPolygon( const olc::Structure structure, @@ -3068,13 +3089,7 @@ namespace olc const std::vector& vecColours, const olc::Pixel tint = olc::Colour::WHITE); - // Draws a filled polygon with multiple colours into a btach - const FilledBatch& FilledPolygon( - FilledBatch& batch, - const olc::Structure structure, - const std::vector& vecPoints, - const std::vector& vecColours, - const olc::Pixel tint = olc::Colour::WHITE); + // Draws a textured polygon with per vertex colouring const GPUTask& TexturedPolygon( From ad779163ace520ec2c68bde0e648a721aa772e91 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Sun, 15 Feb 2026 01:40:19 -0500 Subject: [PATCH 51/58] [windows] make mh cmake work on windows --- CMakeLists.txt | 4 + dev/src/host_iface.h | 2 +- dev/src/hw_keyboard.cpp | 2 +- dev/src/matrix3d.h | 1 + dev/src/matrix4d.h | 1 + dev/tests/CMakeLists.txt | 297 +++++++++++++++++++++++++++++++++++++-- 6 files changed, 293 insertions(+), 14 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fba11b01..d8f8a04a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,3 +52,7 @@ add_dependencies(olcPixelGameEngine3 CopyHeader) if(BUILD_EXAMPLES) add_subdirectory(examples) endif() + +if(BUILD_TEST) + add_subdirectory(dev/tests) +endif() \ No newline at end of file diff --git a/dev/src/host_iface.h b/dev/src/host_iface.h index 1461eaaf..1eda37d3 100644 --- a/dev/src/host_iface.h +++ b/dev/src/host_iface.h @@ -22,7 +22,7 @@ namespace olc inline static size_t uuid = 0; #if OLC_HOST == OLC_HOST_WINDOWS - inline constexpr size_t CreateUID() + inline size_t CreateUID() { return uuid++; } diff --git a/dev/src/hw_keyboard.cpp b/dev/src/hw_keyboard.cpp index 1c01291f..f5d6ff5d 100644 --- a/dev/src/hw_keyboard.cpp +++ b/dev/src/hw_keyboard.cpp @@ -2,7 +2,7 @@ #if OLC_HOST == OLC_HOST_WINDOWS #include "host_win_winapi.h" -#include +#include #endif diff --git a/dev/src/matrix3d.h b/dev/src/matrix3d.h index a950caba..e6a123a9 100644 --- a/dev/src/matrix3d.h +++ b/dev/src/matrix3d.h @@ -6,6 +6,7 @@ #include #include #include +#include //! END STDHEADER //! START CUSTOMHEADER diff --git a/dev/src/matrix4d.h b/dev/src/matrix4d.h index 2e65d998..0f859e4d 100644 --- a/dev/src/matrix4d.h +++ b/dev/src/matrix4d.h @@ -6,6 +6,7 @@ #include #include #include +#include //! END STDHEADER //! START CUSTOMHEADER diff --git a/dev/tests/CMakeLists.txt b/dev/tests/CMakeLists.txt index 4d2ba57d..81f2c7d9 100644 --- a/dev/tests/CMakeLists.txt +++ b/dev/tests/CMakeLists.txt @@ -1,22 +1,295 @@ -cmake_minimum_required(VERSION 3.10) -project(pge3_test) +cmake_minimum_required(VERSION 3.19) +project(PGE_MultiHeader) # Set C++ Standards set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) -###################################################################### -# Directories +option(BUILD_WAYLAND "Build example programs with Wayland" OFF) + +# Platform specific sources +set(HOST_ANDROID_SOURCE_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/../src/host_android.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../src/host_android.h +) +set(HOST_MACOS_SOURCE_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/../src/api_macos.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../src/api_macos.h + ${CMAKE_CURRENT_SOURCE_DIR}/../src/api_macos_wrapper.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/../src/host_apple_macos.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../src/host_apple_macos.h +) +set(HOST_LINUX_WAYLAND_SOURCE_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/../src/host_lin_wayland.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../src/host_lin_wayland.h +) +set(HOST_LINUX_X11_SOURCE_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/../src/host_lin_x11.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../src/host_lin_x11.h +) +set(HOST_WEB_EMSCRIPTEN_SOURCE_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/../src/host_web_emscripten.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../src/host_web_emscripten.h +) +set(HOST_WIN_WINAPI_SOURCE_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/../src/host_win_winapi.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../src/host_win_winapi.h +) +set(IMAGELOADER_ANDROID_SOURCE_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/../src/imload_android.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../src/imload_android.h +) +set(IMAGELOADER_LIBPNG_SOURCE_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/../src/imload_lib_png.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../src/imload_lib_png.h +) +set(IMAGELOADER_MACOS_SOURCE_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/../src/imload_macos.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../src/imload_macos.h +) +set(IMAGELOADER_STB_IMAGE_SOURCE_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/../src/imload_stb_image.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../src/imload_stb_image.h +) +set(IMAGELOADER_WINGDI_SOURCE_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/../src/imload_wingdi.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../src/imload_wingdi.h +) -# Source Files are Curated Here file( - GLOB SOURCE_CXX_FILES - "*.c" - "*.cpp" - "*.h" - "*.hpp" + GLOB SOURCES + ../src/*.c + ../src/*.cpp + ../src/*.h + ../src/*.hpp ) +list( + REMOVE_ITEM SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/../src/sh_template.h + ${CMAKE_CURRENT_SOURCE_DIR}/../src/olcpge3.h + ${HOST_ANDROID_SOURCE_FILES} + ${HOST_MACOS_SOURCE_FILES} + ${HOST_LINUX_WAYLAND_SOURCE_FILES} + ${HOST_LINUX_X11_SOURCE_FILES} + ${HOST_WEB_EMSCRIPTEN_SOURCE_FILES} + ${HOST_WIN_WINAPI_SOURCE_FILES} + ${IMAGELOADER_ANDROID_SOURCE_FILES} + ${IMAGELOADER_MACOS_SOURCE_FILES} + ${IMAGELOADER_LIBPNG_SOURCE_FILES} + ${IMAGELOADER_STB_IMAGE_SOURCE_FILES} + ${IMAGELOADER_WINGDI_SOURCE_FILES} +) + +if(ANDROID) + set(HOST_SOURCE_FILES ${HOST_ANDROID_SOURCE_FILES}) +endif() # Android + +if(APPLE) + if(CMAKE_SYSTEM_NAME STREQUAL "iOS") + # TODO: Building for iOS + else() + set(HOST_SOURCE_FILES ${HOST_MACOS_SOURCE_FILES}) + endif() # MacOS +endif() # Apple + +if(UNIX AND NOT APPLE AND NOT EMSCRIPTEN) + if(BUILD_WAYLAND) # wayland + set(HOST_SOURCE_FILES ${HOST_LINUX_WAYLAND_SOURCE_FILES}) + else() # x11 + set(HOST_SOURCE_FILES ${HOST_LINUX_X11_SOURCE_FILES}) + endif() +endif() # LINUX + +if(EMSCRIPTEN) + set(HOST_SOURCE_FILES ${HOST_WEB_EMSCRIPTEN_SOURCE_FILES}) +endif() # emscripten + +if(WIN32 AND NOT EMSCRIPTEN) + set(HOST_SOURCE_FILES ${HOST_WIN_WINAPI_SOURCE_FILES}) +endif() # windows + +# Image Loader source files +if(USE_STB) + set(IMAGELOADER_SOURCE_FILES ${IMAGELOADER_STB_IMAGE_SOURCE_FILES}) +else() + if(ANDROID) + set(IMAGELOADER_SOURCE_FILES ${IMAGELOADER_ANDROID_SOURCE_FILES}) + endif() + + if(APPLE) + if(CMAKE_SYSTEM_NAME STREQUAL "iOS") + # TODO: Building for iOS + else() + set(IMAGELOADER_SOURCE_FILES ${IMAGELOADER_MACOS_SOURCE_FILES}) + endif() + endif() + + if(UNIX AND NOT APPLE AND NOT EMSCRIPTEN) + set(IMAGELOADER_SOURCE_FILES ${IMAGELOADER_LIBPNG_SOURCE_FILES}) + endif() + + if(WIN32 AND NOT EMSCRIPTEN) + set(IMAGELOADER_SOURCE_FILES ${IMAGELOADER_WINGDI_SOURCE_FILES}) + endif() + + if(EMSCRIPTEN) + set(IMAGELOADER_SOURCE_FILES ${IMAGELOADER_LIBPNG_SOURCE_FILES}) + endif() +endif() + + +if(EMSCRIPTEN) + # build Cache: libpng, zlib + execute_process(COMMAND "${EMSCRIPTEN_ROOT_PATH}/embuilder${EMCC_SUFFIX}" build libpng zlib) + set(CMAKE_EXECUTABLE_SUFFIX .html) +endif() + +set(EXE_NAME test_mh) + +add_executable(${EXE_NAME} test_mh.cpp ${HOST_SOURCE_FILES} ${IMAGELOADER_SOURCE_FILES} ${SOURCES}) + +target_include_directories(${EXE_NAME} PRIVATE ../src) + +if(APPLE) + if(CMAKE_SYSTEM_NAME STREQUAL "iOS") + # TODO: Building for iOS + else() + target_link_libraries(${EXE_NAME} PRIVATE "-framework Metal") + target_link_libraries(${EXE_NAME} PRIVATE "-framework MetalKit") + target_link_libraries(${EXE_NAME} PRIVATE "-framework AppKit") + target_link_libraries(${EXE_NAME} PRIVATE "-framework QuartzCore") + target_link_libraries(${EXE_NAME} PRIVATE "-framework OpenGL") + target_link_libraries(${EXE_NAME} PRIVATE "-framework Foundation") + endif() +endif() # APPLE + +if(UNIX AND NOT APPLE AND NOT EMSCRIPTEN) + if(BUILD_WAYLAND) + find_package(PkgConfig REQUIRED) + pkg_check_modules(XKBCOMMON REQUIRED xkbcommon) + pkg_check_modules(WAYLAND_CLIENT REQUIRED wayland-client) + pkg_check_modules(WAYLAND_EGL REQUIRED wayland-egl) + pkg_check_modules(EGL REQUIRED egl) + + find_package(OpenGL REQUIRED) + find_package(PNG REQUIRED) + find_package(Threads REQUIRED) + + if(NOT BUILD_EXAMPLES) + # Find wayland-scanner + find_program(WAYLAND_SCANNER wayland-scanner REQUIRED) + + # Check for wayland protocols + set(XDG_SHELL_PROTOCOL "/usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml") + set(XDG_DECORATION_PROTOCOL "/usr/share/wayland-protocols/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml") + + if(NOT EXISTS ${XDG_SHELL_PROTOCOL}) + message(FATAL_ERROR "xdg-shell protocol not found. You may need to install wayland-protocols via your package manager") + endif() + + if(NOT EXISTS ${XDG_DECORATION_PROTOCOL}) + message(FATAL_ERROR "xdg-decoration protocol not found. You may need to install wayland-protocols via your package manager") + endif() + + # Generate wayland protocol files + set(XDG_SHELL_C ${CMAKE_CURRENT_BINARY_DIR}/xdg-shell.c) + set(XDG_SHELL_H ${CMAKE_CURRENT_BINARY_DIR}/xdg-shell.h) + set(XDG_DECORATION_C ${CMAKE_CURRENT_BINARY_DIR}/xdg-decoration.c) + set(XDG_DECORATION_H ${CMAKE_CURRENT_BINARY_DIR}/xdg-decoration.h) + + add_custom_command( + OUTPUT ${XDG_SHELL_C} + COMMAND ${WAYLAND_SCANNER} private-code ${XDG_SHELL_PROTOCOL} ${XDG_SHELL_C} + DEPENDS ${XDG_SHELL_PROTOCOL} + VERBATIM + ) + + add_custom_command( + OUTPUT ${XDG_SHELL_H} + COMMAND ${WAYLAND_SCANNER} client-header ${XDG_SHELL_PROTOCOL} ${XDG_SHELL_H} + DEPENDS ${XDG_SHELL_PROTOCOL} + VERBATIM + ) + + add_custom_command( + OUTPUT ${XDG_DECORATION_C} + COMMAND ${WAYLAND_SCANNER} private-code ${XDG_DECORATION_PROTOCOL} ${XDG_DECORATION_C} + DEPENDS ${XDG_DECORATION_PROTOCOL} + VERBATIM + ) + + add_custom_command( + OUTPUT ${XDG_DECORATION_H} + COMMAND ${WAYLAND_SCANNER} client-header ${XDG_DECORATION_PROTOCOL} ${XDG_DECORATION_H} + DEPENDS ${XDG_DECORATION_PROTOCOL} + VERBATIM + ) + + # Create a library for the wayland protocol files + add_library(wayland_protocols STATIC + ${XDG_SHELL_C} + ${XDG_SHELL_H} + ${XDG_DECORATION_C} + ${XDG_DECORATION_H} + ) + + target_include_directories(wayland_protocols PUBLIC + ${CMAKE_CURRENT_BINARY_DIR} + ) + endif() # if not build_examples + + target_compile_definitions(${EXE_NAME} PRIVATE OLC_HOST=3) + + target_link_libraries(${EXE_NAME} PRIVATE + wayland_protocols + ${XKBCOMMON_LIBRARIES} + ${WAYLAND_CLIENT_LIBRARIES} + ${WAYLAND_EGL_LIBRARIES} + ${EGL_LIBRARIES} + OpenGL::GL + PNG::PNG + Threads::Threads + ) + + else() # x11 + target_link_libraries(${EXE_NAME} PRIVATE png) + target_link_libraries(${EXE_NAME} PRIVATE GL) + target_link_libraries(${EXE_NAME} PRIVATE pthread) + target_link_libraries(${EXE_NAME} PRIVATE X11) + endif() +endif() + +if(WIN32 AND NOT MSVC AND NOT EMSCRIPTEN) + target_link_libraries(${EXE_NAME} PRIVATE user32) + target_link_libraries(${EXE_NAME} PRIVATE gdi32) + target_link_libraries(${EXE_NAME} PRIVATE dwmapi) + target_link_libraries(${EXE_NAME} PRIVATE gdiplus) + target_link_libraries(${EXE_NAME} PRIVATE shlwapi) + target_link_libraries(${EXE_NAME} PRIVATE opengl32) +endif() + +if(EMSCRIPTEN) + target_link_options(${EXE_NAME} PRIVATE -sASYNCIFY) + target_link_options(${EXE_NAME} PRIVATE -sALLOW_MEMORY_GROWTH=1) + target_link_options(${EXE_NAME} PRIVATE -sSTACK_SIZE=1048576) + target_link_options(${EXE_NAME} PRIVATE -sEXPORTED_RUNTIME_METHODS=HEAPF32) + target_link_options(${EXE_NAME} PRIVATE -sMAX_WEBGL_VERSION=2) + target_link_options(${EXE_NAME} PRIVATE -sMIN_WEBGL_VERSION=2) + target_link_options(${EXE_NAME} PRIVATE -sUSE_LIBPNG=1) + target_link_options(${EXE_NAME} PRIVATE -sLLD_REPORT_UNDEFINED) + target_link_options(${EXE_NAME} PRIVATE --preload-file ${CMAKE_SOURCE_DIR}/examples/assets@assets) +endif() + +if(NOT EMSCRIPTEN) + # Copy assets when not Web platform + add_custom_command( + TARGET ${EXE_NAME} + POST_BUILD + COMMAND ${CMAKE_COMMAND} + ARGS -E copy_directory ${CMAKE_SOURCE_DIR}/examples/assets ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/assets + ) +endif() + + -# Executable aka binary output -add_executable(${CMAKE_PROJECT_NAME} ${SOURCE_CXX_FILES}) From 0fddb92c03e59b412b5409372d66d4ed963ecda0 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Sun, 15 Feb 2026 02:02:20 -0500 Subject: [PATCH 52/58] [x11] make mh cmake work on linux x11 --- dev/src/api_opengl.h | 9 +++++---- dev/src/host_lin_x11.h | 1 + olcPixelGameEngine3.h | 14 +++++++++----- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/dev/src/api_opengl.h b/dev/src/api_opengl.h index 4ae88951..8b3f6e30 100644 --- a/dev/src/api_opengl.h +++ b/dev/src/api_opengl.h @@ -1,6 +1,5 @@ #pragma once - //! START CUSTOMHEADER #include "config.h" @@ -12,6 +11,10 @@ #include //! END STDHEADER +// deliberately outside of the single header +#if OLC_HOST == OLC_HOST_LINUX_X11 +#include "host_lin_x11.h" +#endif //! START OPENGL_CONFIG @@ -31,9 +34,7 @@ #if OLC_HOST == OLC_HOST_LINUX_X11 #include - #if OLC_HOST == OLC_HOST_LINUX_X11 - #define OGL_LOAD(t) reinterpret_cast(X11::glXGetProcAddress(reinterpret_cast(#t))) - #endif + #define OGL_LOAD(t) reinterpret_cast(X11::glXGetProcAddress(reinterpret_cast(#t))) #endif #if OLC_HOST == OLC_HOST_LINUX_WAYLAND diff --git a/dev/src/host_lin_x11.h b/dev/src/host_lin_x11.h index eb33e733..4af2ff65 100644 --- a/dev/src/host_lin_x11.h +++ b/dev/src/host_lin_x11.h @@ -1,4 +1,5 @@ #pragma once +#include "core.h" //! START STDHEADER GLOBAL #include diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 1e22ab05..fa7c25a8 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -3703,7 +3703,7 @@ namespace olc inline static size_t uuid = 0; #if OLC_HOST == OLC_HOST_WINDOWS - inline constexpr size_t CreateUID() + inline size_t CreateUID() { return uuid++; } @@ -5916,10 +5916,14 @@ namespace olc::host #if OLC_GPU == OLC_GPU_OPENGL33 #if OLC_HOST == OLC_HOST_WINDOWS - #include + #include #pragma comment(lib, "gdi32.lib") #pragma comment(lib, "opengl32.lib") +#if defined(__MINGW32__) || defined(__MINGW64__) + #include +#else #include +#endif #define CALLSTYLE __stdcall // ooof... was getting a bunch of spurious C4191 from MSVC 17.14.9, so round trip via void-town #define OGL_LOAD(t) reinterpret_cast(reinterpret_cast(wglGetProcAddress(#t))) @@ -5927,9 +5931,7 @@ namespace olc::host #if OLC_HOST == OLC_HOST_LINUX_X11 #include - #if OLC_HOST == OLC_HOST_LINUX_X11 - #define OGL_LOAD(t) reinterpret_cast(X11::glXGetProcAddress(reinterpret_cast(#t))) - #endif + #define OGL_LOAD(t) reinterpret_cast(X11::glXGetProcAddress(reinterpret_cast(#t))) #endif #if OLC_HOST == OLC_HOST_LINUX_WAYLAND @@ -6385,7 +6387,9 @@ namespace olc #pragma comment(lib, "Shlwapi.lib") #include #include +#if !defined(__MINGW32__) && !defined(__MINGW64__) #include +#endif #include #undef _WINSOCKAPI_ From 005fd1691b4db1e875a50e2e04b5417136772ef2 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Sun, 15 Feb 2026 02:26:37 -0500 Subject: [PATCH 53/58] [wayland] make mh cmake work on linux wayland --- dev/src/gpu_opengl33.cpp | 2 +- dev/src/gpu_opengl33.h | 4 ++++ dev/src/host_lin_wayland.h | 2 ++ olcPixelGameEngine3.h | 2 +- 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/dev/src/gpu_opengl33.cpp b/dev/src/gpu_opengl33.cpp index 008e23b6..52efb394 100644 --- a/dev/src/gpu_opengl33.cpp +++ b/dev/src/gpu_opengl33.cpp @@ -288,7 +288,7 @@ void main() EGLNativeDisplayType display = EGL_DEFAULT_DISPLAY; #else const auto wayland_window = reinterpret_cast(os_win_id[0]); - EGLNativeWindowType window_handle = wayland_window->window; + EGLNativeWindowType window_handle = reinterpret_cast(wayland_window->window); EGLNativeDisplayType display = reinterpret_cast(os_win_id[1]); #endif diff --git a/dev/src/gpu_opengl33.h b/dev/src/gpu_opengl33.h index 6d1782bb..00ea2bbd 100644 --- a/dev/src/gpu_opengl33.h +++ b/dev/src/gpu_opengl33.h @@ -3,6 +3,10 @@ #include "gpu_iface.h" #include "api_opengl.h" +#if OLC_HOST == OLC_HOST_LINUX_WAYLAND +#include "host_lin_wayland.h" +#endif + //! START DECLARATION #if !defined(PGE_RENDERER_OPENGL33_DECLARED) namespace olc diff --git a/dev/src/host_lin_wayland.h b/dev/src/host_lin_wayland.h index 0fc6970d..0b343870 100644 --- a/dev/src/host_lin_wayland.h +++ b/dev/src/host_lin_wayland.h @@ -1,5 +1,7 @@ #pragma once +#include "core.h" + //! START STDHEADER GLOBAL #include #include diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index fa7c25a8..4e3601c6 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -13268,7 +13268,7 @@ void main() EGLNativeDisplayType display = EGL_DEFAULT_DISPLAY; #else const auto wayland_window = reinterpret_cast(os_win_id[0]); - EGLNativeWindowType window_handle = wayland_window->window; + EGLNativeWindowType window_handle = reinterpret_cast(wayland_window->window); EGLNativeDisplayType display = reinterpret_cast(os_win_id[1]); #endif From 5bad7f233d7285560c942ed612959e14bcba3a78 Mon Sep 17 00:00:00 2001 From: John Galvin <96908304+Johnnyg63@users.noreply.github.com> Date: Sun, 15 Feb 2026 10:26:55 +0000 Subject: [PATCH 54/58] Update Xcode project file: Remove draw2d/3d, and unreferance test/cpps, added draw.h/cpp, draw_batch.cpp, extension --- .../olcPGE3.xcodeproj/project.pbxproj | 36 +++++++------------ 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/dev/xcode_macos/olcPGE3.xcodeproj/project.pbxproj b/dev/xcode_macos/olcPGE3.xcodeproj/project.pbxproj index 562ee0d4..904e7939 100644 --- a/dev/xcode_macos/olcPGE3.xcodeproj/project.pbxproj +++ b/dev/xcode_macos/olcPGE3.xcodeproj/project.pbxproj @@ -8,11 +8,11 @@ /* Begin PBXBuildFile section */ 460496082E362F190047E223 /* test_mh.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 460495FB2E300E0F0047E223 /* test_mh.cpp */; }; - 462680A32E69CDED00799C36 /* draw2d.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 462680A22E69CDED00799C36 /* draw2d.cpp */; }; 462680A42E69D03800799C36 /* image.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 460495EC2E300E000047E223 /* image.cpp */; }; 462680A52E69D15C00799C36 /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 46E85DF82E39135B00B74FA3 /* OpenGL.framework */; }; 462680D12E6C3C4500799C36 /* gpu_iface.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 462680D02E6C3C4500799C36 /* gpu_iface.cpp */; }; - 466C384B2F3A0B0E00F75AF4 /* draw3d.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 466C38482F3A0B0E00F75AF4 /* draw3d.cpp */; }; + 466C385E2F41D5D400F75AF4 /* draw.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 466C385C2F41D5D400F75AF4 /* draw.cpp */; }; + 466C38602F41D69000F75AF4 /* draw_batch.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 466C385F2F41D69000F75AF4 /* draw_batch.cpp */; }; 467318BA2EEC213700714D4C /* font.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 467318B92EEC213700714D4C /* font.cpp */; }; 46964CE82EE5C23300B7BCF1 /* api_macos.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 46964CE62EE5C23300B7BCF1 /* api_macos.cpp */; }; 469A92132E79B0F1007BB460 /* hw_mouse.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 469A92122E79B0F1007BB460 /* hw_mouse.cpp */; }; @@ -27,7 +27,6 @@ 46E85E092E3924FB00B74FA3 /* core.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 460495DD2E300E000047E223 /* core.cpp */; }; 46E85E0A2E39252200B74FA3 /* window.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 460495F22E300E000047E223 /* window.cpp */; }; 46E85E0B2E39252B00B74FA3 /* gpu_opengl33.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 460495E12E300E000047E223 /* gpu_opengl33.cpp */; }; - 46FB045A2F02F7DF00BFA86D /* draw2d_sw.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 46FB04592F02F7DF00BFA86D /* draw2d_sw.cpp */; }; 46FD95932F2AA035004EAEC5 /* hw_keyboard.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 46FD95922F2AA035004EAEC5 /* hw_keyboard.cpp */; }; /* End PBXBuildFile section */ @@ -48,7 +47,6 @@ 460495DB2E300E000047E223 /* api_opengl.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = api_opengl.cpp; sourceTree = ""; }; 460495DC2E300E000047E223 /* core.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = core.h; sourceTree = ""; }; 460495DD2E300E000047E223 /* core.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = core.cpp; sourceTree = ""; }; - 460495DE2E300E000047E223 /* draw2d.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = draw2d.h; sourceTree = ""; }; 460495DF2E300E000047E223 /* gpu_iface.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = gpu_iface.h; sourceTree = ""; }; 460495E02E300E000047E223 /* gpu_opengl33.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = gpu_opengl33.h; sourceTree = ""; }; 460495E12E300E000047E223 /* gpu_opengl33.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = gpu_opengl33.cpp; sourceTree = ""; }; @@ -67,14 +65,14 @@ 460495F02E300E000047E223 /* sh_template.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = sh_template.h; sourceTree = ""; }; 460495F12E300E000047E223 /* window.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = window.h; sourceTree = ""; }; 460495F22E300E000047E223 /* window.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = window.cpp; sourceTree = ""; }; - 460495FA2E300E0F0047E223 /* test_matrices.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = test_matrices.cpp; sourceTree = ""; }; 460495FB2E300E0F0047E223 /* test_mh.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = test_mh.cpp; sourceTree = ""; }; - 462680A22E69CDED00799C36 /* draw2d.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = draw2d.cpp; sourceTree = ""; }; 462680D02E6C3C4500799C36 /* gpu_iface.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = gpu_iface.cpp; sourceTree = ""; }; - 466C38472F3A0B0E00F75AF4 /* draw3d.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = draw3d.h; sourceTree = ""; }; - 466C38482F3A0B0E00F75AF4 /* draw3d.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = draw3d.cpp; sourceTree = ""; }; 466C38492F3A0B0E00F75AF4 /* matrix4d.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = matrix4d.h; sourceTree = ""; }; 466C384A2F3A0B0E00F75AF4 /* vector4d.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = vector4d.h; sourceTree = ""; }; + 466C385B2F41D5D400F75AF4 /* draw.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = draw.h; sourceTree = ""; }; + 466C385C2F41D5D400F75AF4 /* draw.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = draw.cpp; sourceTree = ""; }; + 466C385D2F41D5D400F75AF4 /* extension.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = extension.h; sourceTree = ""; }; + 466C385F2F41D69000F75AF4 /* draw_batch.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = draw_batch.cpp; sourceTree = ""; }; 467318B82EEC213700714D4C /* font.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = font.h; sourceTree = ""; }; 467318B92EEC213700714D4C /* font.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = font.cpp; sourceTree = ""; }; 46964CE52EE5C23300B7BCF1 /* api_macos.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = api_macos.h; sourceTree = ""; }; @@ -93,13 +91,9 @@ 46E85DF82E39135B00B74FA3 /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; }; 46E85DFA2E39136800B74FA3 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 46E85DFC2E39137100B74FA3 /* Metal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metal.framework; path = System/Library/Frameworks/Metal.framework; sourceTree = SDKROOT; }; - 46FB04592F02F7DF00BFA86D /* draw2d_sw.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = draw2d_sw.cpp; sourceTree = ""; }; 46FD95922F2AA035004EAEC5 /* hw_keyboard.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = hw_keyboard.cpp; sourceTree = ""; }; 8998CE202E057F4E007DDD96 /* olcPGE3 */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = olcPGE3; sourceTree = BUILT_PRODUCTS_DIR; }; - 8998CE2B2E057F85007DDD96 /* test_vector2d.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = test_vector2d.cpp; sourceTree = ""; }; 8998CE2C2E057F85007DDD96 /* CMakeLists.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CMakeLists.txt; sourceTree = ""; }; - 8998CE2D2E057F85007DDD96 /* test_main.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = test_main.cpp; sourceTree = ""; }; - 8998CE2E2E057F85007DDD96 /* test_pixels.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = test_pixels.cpp; sourceTree = ""; }; 8998CE332E057F92007DDD96 /* pixel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pixel.h; sourceTree = ""; }; 8998CE342E057F92007DDD96 /* config.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = config.h; sourceTree = ""; }; 8998CE362E057F92007DDD96 /* vector2d.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vector2d.h; sourceTree = ""; }; @@ -157,12 +151,8 @@ 8998CE2A2E057F85007DDD96 /* tests */ = { isa = PBXGroup; children = ( - 460495FA2E300E0F0047E223 /* test_matrices.cpp */, 460495FB2E300E0F0047E223 /* test_mh.cpp */, - 8998CE2B2E057F85007DDD96 /* test_vector2d.cpp */, 8998CE2C2E057F85007DDD96 /* CMakeLists.txt */, - 8998CE2D2E057F85007DDD96 /* test_main.cpp */, - 8998CE2E2E057F85007DDD96 /* test_pixels.cpp */, ); name = tests; path = ../tests; @@ -171,12 +161,13 @@ 8998CE322E057F92007DDD96 /* src */ = { isa = PBXGroup; children = ( - 466C38472F3A0B0E00F75AF4 /* draw3d.h */, - 466C38482F3A0B0E00F75AF4 /* draw3d.cpp */, + 466C385F2F41D69000F75AF4 /* draw_batch.cpp */, + 466C385B2F41D5D400F75AF4 /* draw.h */, + 466C385C2F41D5D400F75AF4 /* draw.cpp */, + 466C385D2F41D5D400F75AF4 /* extension.h */, 466C38492F3A0B0E00F75AF4 /* matrix4d.h */, 466C384A2F3A0B0E00F75AF4 /* vector4d.h */, 46FD95922F2AA035004EAEC5 /* hw_keyboard.cpp */, - 46FB04592F02F7DF00BFA86D /* draw2d_sw.cpp */, 467318B82EEC213700714D4C /* font.h */, 467318B92EEC213700714D4C /* font.cpp */, 46964CE52EE5C23300B7BCF1 /* api_macos.h */, @@ -188,13 +179,11 @@ 469A920F2E79B03C007BB460 /* imload_iface.h */, 469A920E2E79B01E007BB460 /* hw_input.h */, 462680D02E6C3C4500799C36 /* gpu_iface.cpp */, - 462680A22E69CDED00799C36 /* draw2d.cpp */, 460495DA2E300E000047E223 /* api_opengl.h */, 460495DB2E300E000047E223 /* api_opengl.cpp */, 8998CE342E057F92007DDD96 /* config.h */, 460495DC2E300E000047E223 /* core.h */, 460495DD2E300E000047E223 /* core.cpp */, - 460495DE2E300E000047E223 /* draw2d.h */, 460495DF2E300E000047E223 /* gpu_iface.h */, 460495E02E300E000047E223 /* gpu_opengl33.h */, 460495E12E300E000047E223 /* gpu_opengl33.cpp */, @@ -280,12 +269,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 466C384B2F3A0B0E00F75AF4 /* draw3d.cpp in Sources */, 462680D12E6C3C4500799C36 /* gpu_iface.cpp in Sources */, + 466C38602F41D69000F75AF4 /* draw_batch.cpp in Sources */, 462680A42E69D03800799C36 /* image.cpp in Sources */, 46E85E0B2E39252B00B74FA3 /* gpu_opengl33.cpp in Sources */, - 46FB045A2F02F7DF00BFA86D /* draw2d_sw.cpp in Sources */, - 462680A32E69CDED00799C36 /* draw2d.cpp in Sources */, 46E85E0A2E39252200B74FA3 /* window.cpp in Sources */, 46E85E092E3924FB00B74FA3 /* core.cpp in Sources */, 467318BA2EEC213700714D4C /* font.cpp in Sources */, @@ -296,6 +283,7 @@ 46FD95932F2AA035004EAEC5 /* hw_keyboard.cpp in Sources */, 46C8F5B92E7DA67900C11019 /* imload_macos.cpp in Sources */, 460496082E362F190047E223 /* test_mh.cpp in Sources */, + 466C385E2F41D5D400F75AF4 /* draw.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; From 8748987aeb38b6bc0d0033a3379d0eced6ed76ac Mon Sep 17 00:00:00 2001 From: John Galvin <96908304+Johnnyg63@users.noreply.github.com> Date: Sun, 15 Feb 2026 10:56:20 +0000 Subject: [PATCH 55/58] 174-mouse-focus-not-implemented-on-macos-15-02-2026 --- dev/src/host_apple_macos.cpp | 18 ++++++++++++------ olcPixelGameEngine3.h | 26 +++++++++++++++++++------- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/dev/src/host_apple_macos.cpp b/dev/src/host_apple_macos.cpp index 40434483..51869e9f 100644 --- a/dev/src/host_apple_macos.cpp +++ b/dev/src/host_apple_macos.cpp @@ -468,10 +468,18 @@ bool Host_Apple_MacOS::SyncWithDesktopComposite() res = true; // Skip frame to allow resize to take effect break; } - case MINIMIZE_WINDOW: case DEMINIMIZE_WINDOW: case BECOME_ACTIVE: + { + pPGEwindow->olc_OnMouseFocus(true); + break; + } + case MINIMIZE_WINDOW: case RESIGN_ACTIVE: + { + pPGEwindow->olc_OnMouseFocus(false); + break; + } case NONE: default: { @@ -506,9 +514,7 @@ bool Host_Apple_MacOS::SyncWithDesktopComposite() pPGEwindow->keyboard.UseKeyboardLayout(GetKeyboardLayout()); }); - pMacApplication->setWillTerminateCallback([&]() { - // TODO: Johnngy63 - Implement olc_OnDestory in window.h/cpp - }); + pMacApplication->setWillTerminateCallback([&]() { }); pMacApplication->setDidBecomeActiveCallback([]() { }); @@ -523,17 +529,17 @@ bool Host_Apple_MacOS::SyncWithDesktopComposite() }); pMacOSWindow->setWindowWillCloseCallback([&]() { + // NOTE: Do not add this event to PendingMainThreadTasks as it will cause deadlock since the main thread is required to process the close event but the close event is waiting on the main thread tasks to process it pPGEwindow->olc_OnWindowClose(); pPGEwindow->olc_ShouldRemove(); }); pMacOSWindow->setWindowDidBecomeKeyCallback([&]() { - //TODO: Johnngy63 - Implement olc_OnWindowFocus in window.h/cpp AddPendingMainThreadTask(BECOME_ACTIVE); }); pMacOSWindow->setWindowDidResignKeyCallback([&]() { - //TODO: Johnngy63 - Implement olc_OnWindowFocus in window.h/cpp + AddPendingMainThreadTask(RESIGN_ACTIVE); }); pMacOSWindow->setWindowDidMiniaturizeCallback([&]() { diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 1e22ab05..9f34363d 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -5916,10 +5916,14 @@ namespace olc::host #if OLC_GPU == OLC_GPU_OPENGL33 #if OLC_HOST == OLC_HOST_WINDOWS - #include + #include #pragma comment(lib, "gdi32.lib") #pragma comment(lib, "opengl32.lib") +#if defined(__MINGW32__) || defined(__MINGW64__) + #include +#else #include +#endif #define CALLSTYLE __stdcall // ooof... was getting a bunch of spurious C4191 from MSVC 17.14.9, so round trip via void-town #define OGL_LOAD(t) reinterpret_cast(reinterpret_cast(wglGetProcAddress(#t))) @@ -6385,7 +6389,9 @@ namespace olc #pragma comment(lib, "Shlwapi.lib") #include #include +#if !defined(__MINGW32__) && !defined(__MINGW64__) #include +#endif #include #undef _WINSOCKAPI_ @@ -7588,10 +7594,18 @@ bool Host_Apple_MacOS::SyncWithDesktopComposite() res = true; // Skip frame to allow resize to take effect break; } - case MINIMIZE_WINDOW: case DEMINIMIZE_WINDOW: case BECOME_ACTIVE: + { + pPGEwindow->olc_OnMouseFocus(true); + break; + } + case MINIMIZE_WINDOW: case RESIGN_ACTIVE: + { + pPGEwindow->olc_OnMouseFocus(false); + break; + } case NONE: default: { @@ -7626,9 +7640,7 @@ bool Host_Apple_MacOS::SyncWithDesktopComposite() pPGEwindow->keyboard.UseKeyboardLayout(GetKeyboardLayout()); }); - pMacApplication->setWillTerminateCallback([&]() { - // TODO: Johnngy63 - Implement olc_OnDestory in window.h/cpp - }); + pMacApplication->setWillTerminateCallback([&]() { }); pMacApplication->setDidBecomeActiveCallback([]() { }); @@ -7643,17 +7655,17 @@ bool Host_Apple_MacOS::SyncWithDesktopComposite() }); pMacOSWindow->setWindowWillCloseCallback([&]() { + // NOTE: Do not add this event to PendingMainThreadTasks as it will cause deadlock since the main thread is required to process the close event but the close event is waiting on the main thread tasks to process it pPGEwindow->olc_OnWindowClose(); pPGEwindow->olc_ShouldRemove(); }); pMacOSWindow->setWindowDidBecomeKeyCallback([&]() { - //TODO: Johnngy63 - Implement olc_OnWindowFocus in window.h/cpp AddPendingMainThreadTask(BECOME_ACTIVE); }); pMacOSWindow->setWindowDidResignKeyCallback([&]() { - //TODO: Johnngy63 - Implement olc_OnWindowFocus in window.h/cpp + AddPendingMainThreadTask(RESIGN_ACTIVE); }); pMacOSWindow->setWindowDidMiniaturizeCallback([&]() { From 0af41eea2a2657d2be2d6322f6a647e7306d18ce Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Sun, 15 Feb 2026 12:49:34 +0000 Subject: [PATCH 56/58] added MousePosition and MouseVisibilty functions in host, updated mouse demo to illustrate features --- .../olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj | 16 ++-- dev/src/core.cpp | 7 ++ dev/src/core.h | 4 + dev/src/host_iface.h | 6 ++ dev/src/host_win_winapi.cpp | 38 ++++++++- dev/src/host_win_winapi.h | 6 ++ dev/src/window.cpp | 10 +++ dev/src/window.h | 6 ++ dev/tests/test_mh.cpp | 21 ++++- examples/olcPGE3_Mouse.cpp | 24 +++++- olcPixelGameEngine3.h | 77 ++++++++++++++++++- 11 files changed, 202 insertions(+), 13 deletions(-) diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj index ea978033..12d3777f 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj @@ -32,10 +32,10 @@ true - false - false - false - false + true + true + true + true true @@ -80,10 +80,10 @@ true - true - true - true - true + false + false + false + false true diff --git a/dev/src/core.cpp b/dev/src/core.cpp index 794fec0a..b32b8d9b 100644 --- a/dev/src/core.cpp +++ b/dev/src/core.cpp @@ -304,6 +304,13 @@ namespace olc return GetScreen().Size(); } + void PGEWindow::SetMousePosition(const olc::vi2d& vPos) + { + // Scale mouse from view coordinates into window coordinates + olc::vi2d pos = (olc::vf2d(vPos) / olc::vf2d(GetScreen().Size()) * olc::vf2d(vViewSize)) + vViewPos; + SetWindowMousePosition(pos); + } + bool PGEWindow::olc_OnMouseMove(const olc::vi2d& vMousePos) { olc::vi2d pos = vMousePos; diff --git a/dev/src/core.h b/dev/src/core.h index 3c538557..9de6114b 100644 --- a/dev/src/core.h +++ b/dev/src/core.h @@ -107,6 +107,10 @@ namespace olc // Returns the current size of the "screen" in pixels const olc::vi2d& ScreenSize(); + public: // Mouse manipulation + // Force the mouse position, in "PGE Screen" coordinates + void SetMousePosition(const olc::vi2d& vPos); + protected: bool olc_OnMouseMove(const olc::vi2d& vMousePos) override; diff --git a/dev/src/host_iface.h b/dev/src/host_iface.h index 1eda37d3..fc550b5b 100644 --- a/dev/src/host_iface.h +++ b/dev/src/host_iface.h @@ -94,6 +94,12 @@ namespace olc // Wait for OS desktop refresh (for smooooth vsync) virtual bool SyncWithDesktopComposite() = 0; + public: // Platform specific Mouse Control + // Force the mouse position in pixels relative to window + virtual bool SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) = 0; + // Show or hide mouse cursor for given window + virtual bool SetMouseVisible(olc::Window* pWindow, const bool bVisible) = 0; + public: // OS Specific Environment Information virtual olc::KeyboardLayout GetKeyboardLayout() const = 0; diff --git a/dev/src/host_win_winapi.cpp b/dev/src/host_win_winapi.cpp index 68dc16a3..5a525afd 100644 --- a/dev/src/host_win_winapi.cpp +++ b/dev/src/host_win_winapi.cpp @@ -265,12 +265,13 @@ namespace olc::host olc::vi2d vWinPos = vWindowPos; olc::vi2d vWinSize = vWindowSize; + hCursorNow = hCursorDefault = LoadCursor(NULL, IDC_ARROW); // Define WindowClass WNDCLASSEX wc = { 0 }; wc.cbSize = sizeof(WNDCLASSEX); wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); - wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.hCursor = hCursorDefault; wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; wc.hInstance = GetModuleHandle(nullptr); wc.lpfnWndProc = WINAPI_EventHandler; @@ -371,6 +372,30 @@ namespace olc::host return DwmFlush() == S_OK; } + bool Host_Windows_WinAPI::SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) + { + POINT pt; + pt.x = vPos.x; + pt.y = vPos.y; + ClientToScreen(mapUID2HWND.at(pWindow->GetUID()), &pt); + SetCursorPos(pt.x, pt.y); + return true; + } + + bool Host_Windows_WinAPI::SetMouseVisible(olc::Window* pWindow, const bool bVisible) + { + olc_IgnoreUnused(pWindow); + + hCursorNow = bVisible ? hCursorDefault : NULL; + + // Fire fake move event to update cursor visibility immediately + POINT p; + GetCursorPos(&p); + SetCursorPos(p.x, p.y + 1); + SetCursorPos(p.x, p.y); + return true; + } + LRESULT Host_Windows_WinAPI::OnWindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { if (!mapHWND2PTR.contains(hWnd)) @@ -552,6 +577,17 @@ namespace olc::host //return DefWindowProc(hWnd, uMsg, wParam, lParam); } + case WM_SETCURSOR: + { + if (LOWORD(lParam) == HTCLIENT) + { + SetCursor(hCursorNow); + return TRUE; // Sigh ffs microsoft... + } + + break; + } + case WM_DESTROY: PostQuitMessage(0); DestroyWindow(hWnd); diff --git a/dev/src/host_win_winapi.h b/dev/src/host_win_winapi.h index db55025f..98b3413c 100644 --- a/dev/src/host_win_winapi.h +++ b/dev/src/host_win_winapi.h @@ -74,6 +74,10 @@ namespace olc // Wait for OS desktop refresh (for smooooth vsync) bool SyncWithDesktopComposite() override; + public: // Platform specific Mouse Control + bool SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) override; + bool SetMouseVisible(olc::Window* pWindow, const bool bVisible) override; + public: // OS Specific Environment Information olc::KeyboardLayout GetKeyboardLayout() const override; @@ -100,6 +104,8 @@ namespace olc std::unordered_map mapHWND2PTR; std::wstring ConvertS2W(std::string s); std::atomic systemActive = false; + HCURSOR hCursorDefault = nullptr; + HCURSOR hCursorNow = nullptr; public: LRESULT OnWindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); diff --git a/dev/src/window.cpp b/dev/src/window.cpp index 2145b829..6c5a9311 100644 --- a/dev/src/window.cpp +++ b/dev/src/window.cpp @@ -122,5 +122,15 @@ namespace olc return false; } + void Window::SetWindowMousePosition(const olc::vi2d& vPos) + { + pHost->SetMousePosition(this, vPos); + } + + void Window::ShowMouseCursor(const bool bShow) + { + pHost->SetMouseVisible(this, bShow); + } + }; //! END IMPLEMENTATION \ No newline at end of file diff --git a/dev/src/window.h b/dev/src/window.h index 30e2c248..3e890929 100644 --- a/dev/src/window.h +++ b/dev/src/window.h @@ -90,6 +90,12 @@ namespace olc const std::string& GetWindowTitle() const; bool SetWindowTitle(const std::string& sTitle); + // Force the mouse position, in "screen" coordinates + void SetWindowMousePosition(const olc::vi2d& vPos); + + // Show or hide mouse cursor + void ShowMouseCursor(const bool bShow); + protected: bool bRequestToClose = false; bool bShouldRemove = false; diff --git a/dev/tests/test_mh.cpp b/dev/tests/test_mh.cpp index 754d3d58..a58b860f 100644 --- a/dev/tests/test_mh.cpp +++ b/dev/tests/test_mh.cpp @@ -368,6 +368,23 @@ class Example : public olc::PixelGameEngine + if (keyboard.GetKey(olc::Key::SPACE).bHeld) + { + SetMousePosition({ 50,50 }); + } + + if (keyboard.GetKey(olc::Key::P).bPressed) + { + ShowMouseCursor(false); + } + + if (keyboard.GetKey(olc::Key::O).bPressed) + { + ShowMouseCursor(true); + } + + + // Testing matrices olc::mf4d t1, t2, t3; t1.translate(0.0f, 3.0f, 5.0f); @@ -760,8 +777,8 @@ int main() cfg.vPixelSize = { 1,1 }; cfg.vScreenSize = { 1024, 960 }; - //cfg.vPixelSize = { 4,4 }; - //cfg.vScreenSize = { 256, 240 }; + cfg.vPixelSize = { 4,4 }; + cfg.vScreenSize = { 256, 240 }; //cfg.bAntiAliasMainScreen = true; cfg.bVSync = false; diff --git a/examples/olcPGE3_Mouse.cpp b/examples/olcPGE3_Mouse.cpp index bc3c36e2..2de5f707 100644 --- a/examples/olcPGE3_Mouse.cpp +++ b/examples/olcPGE3_Mouse.cpp @@ -80,6 +80,9 @@ class Example_Mouse : public olc::PixelGameEngine p.life -= fElapsedTime; p.pos += p.vel * fElapsedTime; p.vel *= 0.98f; // Friction + + // Little cosmetic fix to stop popping at the end of life + if (p.life < 0.0f) p.life = 0.0f; p.color.a = (uint8_t)((p.life / p.maxLife) * 255); draw.FilledCircle(p.pos, 3, p.color); @@ -110,9 +113,28 @@ class Example_Mouse : public olc::PixelGameEngine if(mouse.GetButton(4).bHeld) draw.Circle(mousePos, 28, olc::Colour::TANGERINE); + + + if (keyboard.GetKey(olc::Key::SPACE).bHeld) + { + SetMousePosition(ScreenSize() * 0.5f); + } + + if (keyboard.GetKey(olc::Key::K1).bPressed) + { + ShowMouseCursor(false); + } + + if (keyboard.GetKey(olc::Key::K2).bPressed) + { + ShowMouseCursor(true); + } // Instructions draw.String({10, 10}, "Mouse Example\n\nClick all the buttons!\nScroll the wheel!", olc::Colour::YELLOW); + + draw.String({ 10, 100 }, "Hold SPACE to lock\nmouse to center", olc::Colour::TANGERINE); + draw.String({ 10, 130 }, "1) Hide Mouse Cursor\n2) Show Mouse Cursor", olc::Colour::TANGERINE); // Successful frame return true; @@ -138,7 +160,7 @@ class Example_Mouse : public olc::PixelGameEngine { for(int i = 0; i < count; i++) { - float angle = (rand() / (float)RAND_MAX) * 2.0f * std::numbers::pi; + float angle = (rand() / (float)RAND_MAX) * 2.0f * std::numbers::pi_v; float speed = 50.0f + (rand() / (float)RAND_MAX) * 100.0f; Particle p; diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 4e3601c6..d0ba0697 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -3673,6 +3673,12 @@ namespace olc const std::string& GetWindowTitle() const; bool SetWindowTitle(const std::string& sTitle); + // Force the mouse position, in "screen" coordinates + void SetWindowMousePosition(const olc::vi2d& vPos); + + // Show or hide mouse cursor + void ShowMouseCursor(const bool bShow); + protected: bool bRequestToClose = false; bool bShouldRemove = false; @@ -3775,6 +3781,12 @@ namespace olc // Wait for OS desktop refresh (for smooooth vsync) virtual bool SyncWithDesktopComposite() = 0; + public: // Platform specific Mouse Control + // Force the mouse position in pixels relative to window + virtual bool SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) = 0; + // Show or hide mouse cursor for given window + virtual bool SetMouseVisible(olc::Window* pWindow, const bool bVisible) = 0; + public: // OS Specific Environment Information virtual olc::KeyboardLayout GetKeyboardLayout() const = 0; @@ -3951,6 +3963,10 @@ namespace olc // Returns the current size of the "screen" in pixels const olc::vi2d& ScreenSize(); + public: // Mouse manipulation + // Force the mouse position, in "PGE Screen" coordinates + void SetMousePosition(const olc::vi2d& vPos); + protected: bool olc_OnMouseMove(const olc::vi2d& vMousePos) override; @@ -4123,6 +4139,10 @@ namespace olc // Wait for OS desktop refresh (for smooooth vsync) bool SyncWithDesktopComposite() override; + public: // Platform specific Mouse Control + bool SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) override; + bool SetMouseVisible(olc::Window* pWindow, const bool bVisible) override; + public: // OS Specific Environment Information olc::KeyboardLayout GetKeyboardLayout() const override; @@ -4149,6 +4169,8 @@ namespace olc std::unordered_map mapHWND2PTR; std::wstring ConvertS2W(std::string s); std::atomic systemActive = false; + HCURSOR hCursorDefault = nullptr; + HCURSOR hCursorNow = nullptr; public: LRESULT OnWindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); @@ -6812,12 +6834,13 @@ namespace olc::host olc::vi2d vWinPos = vWindowPos; olc::vi2d vWinSize = vWindowSize; + hCursorNow = hCursorDefault = LoadCursor(NULL, IDC_ARROW); // Define WindowClass WNDCLASSEX wc = { 0 }; wc.cbSize = sizeof(WNDCLASSEX); wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); - wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.hCursor = hCursorDefault; wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; wc.hInstance = GetModuleHandle(nullptr); wc.lpfnWndProc = WINAPI_EventHandler; @@ -6918,6 +6941,30 @@ namespace olc::host return DwmFlush() == S_OK; } + bool Host_Windows_WinAPI::SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) + { + POINT pt; + pt.x = vPos.x; + pt.y = vPos.y; + ClientToScreen(mapUID2HWND.at(pWindow->GetUID()), &pt); + SetCursorPos(pt.x, pt.y); + return true; + } + + bool Host_Windows_WinAPI::SetMouseVisible(olc::Window* pWindow, const bool bVisible) + { + olc_IgnoreUnused(pWindow); + + hCursorNow = bVisible ? hCursorDefault : NULL; + + // Fire fake move event to update cursor visibility immediately + POINT p; + GetCursorPos(&p); + SetCursorPos(p.x, p.y + 1); + SetCursorPos(p.x, p.y); + return true; + } + LRESULT Host_Windows_WinAPI::OnWindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { if (!mapHWND2PTR.contains(hWnd)) @@ -7099,6 +7146,17 @@ namespace olc::host //return DefWindowProc(hWnd, uMsg, wParam, lParam); } + case WM_SETCURSOR: + { + if (LOWORD(lParam) == HTCLIENT) + { + SetCursor(hCursorNow); + return TRUE; // Sigh ffs microsoft... + } + + break; + } + case WM_DESTROY: PostQuitMessage(0); DestroyWindow(hWnd); @@ -16123,6 +16181,13 @@ namespace olc return GetScreen().Size(); } + void PGEWindow::SetMousePosition(const olc::vi2d& vPos) + { + // Scale mouse from view coordinates into window coordinates + olc::vi2d pos = (olc::vf2d(vPos) / olc::vf2d(GetScreen().Size()) * olc::vf2d(vViewSize)) + vViewPos; + SetWindowMousePosition(pos); + } + bool PGEWindow::olc_OnMouseMove(const olc::vi2d& vMousePos) { olc::vi2d pos = vMousePos; @@ -17090,6 +17155,16 @@ namespace olc return false; } + void Window::SetWindowMousePosition(const olc::vi2d& vPos) + { + pHost->SetMousePosition(this, vPos); + } + + void Window::ShowMouseCursor(const bool bShow) + { + pHost->SetMouseVisible(this, bShow); + } + }; #define PGE_WINDOW_IMPLEMENTED 1 #endif From 4e4b4d262dafaa3b7be5dc21f9df75aaa9bcf71f Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Sun, 15 Feb 2026 13:12:22 +0000 Subject: [PATCH 57/58] little bug with extensions demo --- dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj | 16 ++++++++-------- examples/olcPGE3_Extensions.cpp | 12 ++++++------ 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj index 12d3777f..fb620449 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj @@ -50,10 +50,10 @@ true - true - true - true - true + false + false + false + false true @@ -80,10 +80,10 @@ true - false - false - false - false + true + true + true + true true diff --git a/examples/olcPGE3_Extensions.cpp b/examples/olcPGE3_Extensions.cpp index d3b1856b..c540a1c3 100644 --- a/examples/olcPGE3_Extensions.cpp +++ b/examples/olcPGE3_Extensions.cpp @@ -84,7 +84,11 @@ class Example_Extensions : public olc::PixelGameEngine public: Example_Extensions() { - + // Installing an extension allows it to receive callbacks at various stages + // of the application and window lifecycle, and to modify behaviour if necessary. + // You can install as many extensions as you like, and they will be called in + // the order they were installed + InstallWindowExtension(&pgex); } protected: @@ -98,11 +102,7 @@ class Example_Extensions : public olc::PixelGameEngine // Called once at the start, so create things here bool OnUserCreate() override { - // Installing an extension allows it to receive callbacks at various stages - // of the application and window lifecycle, and to modify behaviour if necessary. - // You can install as many extensions as you like, and they will be called in - // the order they were installed - InstallWindowExtension(&pgex); + return true; } From ed09a78d4db0569c57fc36ebdbadff35f488a6ae Mon Sep 17 00:00:00 2001 From: tgd2 <59289427+tgd2@users.noreply.github.com> Date: Sat, 21 Feb 2026 17:19:01 +0000 Subject: [PATCH 58/58] Removing double scaling for olc::Draw::ImageRotated --- dev/src/draw.cpp | 2 +- dev/src/draw_batch.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dev/src/draw.cpp b/dev/src/draw.cpp index 91208319..8c4d3e5c 100644 --- a/dev/src/draw.cpp +++ b/dev/src/draw.cpp @@ -995,7 +995,7 @@ const GPUTask& olc::Draw::ImageRotated(olc::ImageRegion image, const olc::vf2d& PrepareImageForHW(image.image); PrepareTargetForHW(); - olc::vf2d size = image.regionsize * scale; + olc::vf2d size = image.regionsize; std::vector vPoints(4); vPoints[0] = olc::vf2d(0.0f, 0.0f) - (center * scale); diff --git a/dev/src/draw_batch.cpp b/dev/src/draw_batch.cpp index c632ca61..635600fb 100644 --- a/dev/src/draw_batch.cpp +++ b/dev/src/draw_batch.cpp @@ -358,7 +358,7 @@ const ImageBatch& olc::Draw::Image(ImageBatch& batch, olc::ImageRegion image, co const ImageBatch& olc::Draw::ImageRotated(olc::ImageBatch& batch, olc::ImageRegion image, const olc::vf2d& pos, const float theta, const olc::vf2d& center, const olc::vf2d& scale, const olc::Pixel tint) { // Add quad to existing task - olc::vf2d size = image.regionsize * scale; + olc::vf2d size = image.regionsize; std::array vPoints; vPoints[0] = (olc::vf2d(0.0f, 0.0f) - center) * scale;