From f0cec011447aa4334905a4e92eccc34e457e0795 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Sun, 1 Feb 2026 19:40:50 -0500 Subject: [PATCH 001/101] [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 002/101] [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 003/101] [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 004/101] [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 005/101] 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 006/101] 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 007/101] 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 008/101] 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 009/101] 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 010/101] 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 011/101] [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 012/101] 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 013/101] 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 014/101] 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 015/101] 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 016/101] 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 017/101] 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 018/101] 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 019/101] [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 020/101] [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 021/101] [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 022/101] [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 023/101] 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 024/101] 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 025/101] 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 026/101] [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 027/101] 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 028/101] [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 029/101] 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 030/101] 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 031/101] [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 032/101] [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 033/101] [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 034/101] [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 035/101] [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 036/101] [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 037/101] 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 038/101] [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 039/101] [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 040/101] [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 041/101] 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 042/101] [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 043/101] [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 044/101] [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 045/101] 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 046/101] 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 047/101] 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 048/101] 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 049/101] 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 050/101] 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 da1aff6cf9f21e2634dfdee6a5f2cfbb67e2220e Mon Sep 17 00:00:00 2001 From: dandistine <34634876+dandistine@users.noreply.github.com> Date: Sat, 14 Feb 2026 17:27:52 -0600 Subject: [PATCH 051/101] To keep javid from whinging --- dev/src/vector2d.h | 7 +++++++ olcPixelGameEngine3.h | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/dev/src/vector2d.h b/dev/src/vector2d.h index a626d67c..5b6a88bb 100644 --- a/dev/src/vector2d.h +++ b/dev/src/vector2d.h @@ -322,6 +322,13 @@ namespace olc return lhs; } + template + inline constexpr auto operator -= (v_2d& lhs, const v_2d& 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_2d& lhs, const v_2d& rhs) diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 1e22ab05..11e125df 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -920,6 +920,13 @@ namespace olc return lhs; } + template + inline constexpr auto operator -= (v_2d& lhs, const v_2d& 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_2d& lhs, const v_2d& rhs) From ad779163ace520ec2c68bde0e648a721aa772e91 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Sun, 15 Feb 2026 01:40:19 -0500 Subject: [PATCH 052/101] [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 053/101] [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 054/101] [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 055/101] 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 056/101] 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 057/101] 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 058/101] 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 8249e9e6b23a0e3dcaa3d9e701ed1c20b8084076 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Sun, 15 Feb 2026 10:57:06 -0500 Subject: [PATCH 059/101] [emscripten] add mouse cursor hide/show, stub SetMousePosition --- dev/src/host_web_emscripten.cpp | 301 +++++++++++++++-------------- dev/src/host_web_emscripten.h | 24 ++- olcPixelGameEngine3.h | 324 +++++++++++++++++--------------- 3 files changed, 347 insertions(+), 302 deletions(-) diff --git a/dev/src/host_web_emscripten.cpp b/dev/src/host_web_emscripten.cpp index 2a0a5ccd..9f345829 100644 --- a/dev/src/host_web_emscripten.cpp +++ b/dev/src/host_web_emscripten.cpp @@ -7,80 +7,6 @@ namespace olc::host std::unordered_map Host_Web_Emscripten::mapCanvasId2PTR; std::unordered_map> Host_Web_Emscripten::mapUID2CallbackData; - // Called at very start of application - bool Host_Web_Emscripten::OnApplicationStart(olc::PixelGameEngine* pPrimary) - { - std::cout << "Emscripten: OnApplicationStart.\n"; - pPrimaryPGE = pPrimary; - return true; - } - - void Host_Web_Emscripten::MainLoop(void* userData) - { - auto pHost = reinterpret_cast(userData); - - if(!pHost->OnSystemTick()) - { - pHost->StopSystem(); - } - } - - // Called to start the host - this may mean different things on different hosts - bool Host_Web_Emscripten::StartSystem() - { - // Pre-context start hook - pPrimaryPGE->OnPreContextStart(); - - if(!OnSystemThreadStart()) - { - // PGE->ContextStart() failed, or user aborted OnUserCreate() - return false; - } - - emscripten_set_main_loop_arg(Host_Web_Emscripten::MainLoop, reinterpret_cast(this), 0, 1); - - // EMSCRIPTEN QUIRK: this code is never reached, the main loop is simulating a while(true); - - return true; - } - - // Called to stop the host, and shutdown all resources - bool Host_Web_Emscripten::StopSystem() - { - std::cout << "Emscripten: StopSystem.\n"; - OnSystemThreadEnd(); - pPrimaryPGE->OnPostContextEnd(); - emscripten_cancel_main_loop(); - return true; - } - - // Called at start of system event loop - bool Host_Web_Emscripten::OnSystemThreadStart() - { - std::cout << "Emscripten: OnSystemThreadStart.\n"; - return pPrimaryPGE->OnContextStart(); - } - - // Called to perform primary window update - bool Host_Web_Emscripten::OnSystemTick() - { - return pPrimaryPGE->OnContextTick(); - } - - // Called at end of system event loop - bool Host_Web_Emscripten::OnSystemThreadEnd() - { - std::cout << "Emscripten: OnSystemThreadEnd.\n"; - return pPrimaryPGE->OnContextEnd(); - } - - // Called at very end of application - bool Host_Web_Emscripten::OnApplicationEnd() - { - std::cout << "Emscripten: OnApplicationEnd.\n"; - return true; - } - Host_Web_Emscripten::Host_Web_Emscripten() { std::cout << "Emscripten: host constructed.\n"; @@ -300,6 +226,141 @@ namespace olc::host return true; } + + bool Host_Web_Emscripten::CloseWindowFrame(olc::Window* pWindow) + { + std::cout << "Emscripten: CloseWindowFrame not implemented.\n"; + return true; + } + + bool Host_Web_Emscripten::UpdateWindowFrameTitle(olc::Window* pWindow) + { + // not implemented for emscripten platform + return true; + } + + std::vector Host_Web_Emscripten::GetHostWindowDescriptor(olc::Window* pWindow) + { + const auto window_handle = mapUID2CanvasId.find(pWindow->GetUID()); + if(window_handle != mapUID2CanvasId.end()) + { + return { (void*)(window_handle->second.c_str()) }; + } + + return {}; + } + + // Wait for entire host desktop refresh (for smooooth vsync) + bool Host_Web_Emscripten::SyncWithDesktopComposite() + { + // SyncWithDesktopComposite not implemented on this platform + return true; + } + + // Force the mouse position in pixels relative to window + bool Host_Web_Emscripten::SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) + { + // Not supported on the web platform + olc_IgnoreUnused(pWindow, vPos); + + static bool debounce = false; + if(debounce) + return false; + + debounce = true; + std::cout << "SetMousePosition is not supported on this platform.\n"; + return false; + } + + // Show or hide mouse cursor for given window + bool Host_Web_Emscripten::SetMouseVisible(olc::Window* pWindow, const bool bVisible) + { + if(!mapUID2CanvasId.contains(pWindow->GetUID())) + return false; + + auto canvasID = mapUID2CanvasId.at(pWindow->GetUID()); + if(bVisible) + EM_ASM({ document.querySelector(UTF8ToString($0)).style.cursor = 'default'; }, canvasID.c_str()); + else + EM_ASM({ document.querySelector(UTF8ToString($0)).style.cursor = 'none'; }, canvasID.c_str()); + + return true; + } + + olc::KeyboardLayout Host_Web_Emscripten::GetKeyboardLayout() const + { + return static_cast(EM_ASM_INT({ return Module.keyboardLayout || 0; })); + } + + // Called at very start of application + bool Host_Web_Emscripten::OnApplicationStart(olc::PixelGameEngine* pPrimary) + { + std::cout << "Emscripten: OnApplicationStart.\n"; + pPrimaryPGE = pPrimary; + return true; + } + + // Called to start the host - this may mean different things on different hosts + bool Host_Web_Emscripten::StartSystem() + { + // Pre-context start hook + pPrimaryPGE->OnPreContextStart(); + + if(!OnSystemThreadStart()) + { + // PGE->ContextStart() failed, or user aborted OnUserCreate() + return false; + } + + emscripten_set_main_loop_arg(Host_Web_Emscripten::MainLoop, reinterpret_cast(this), 0, 1); + + // EMSCRIPTEN QUIRK: this code is never reached, the main loop is simulating a while(true); + + return true; + } + + // Called to stop the host, and shutdown all resources + bool Host_Web_Emscripten::StopSystem() + { + OnSystemThreadEnd(); + pPrimaryPGE->OnPostContextEnd(); + emscripten_cancel_main_loop(); + return true; + } + + // Called at start of system event loop + bool Host_Web_Emscripten::OnSystemThreadStart() + { + return pPrimaryPGE->OnContextStart(); + } + + // Called to perform primary window update + bool Host_Web_Emscripten::OnSystemTick() + { + return pPrimaryPGE->OnContextTick(); + } + + // Called at end of system event loop + bool Host_Web_Emscripten::OnSystemThreadEnd() + { + return pPrimaryPGE->OnContextEnd(); + } + + // Called at very end of application + bool Host_Web_Emscripten::OnApplicationEnd() + { + return true; + } + + void Host_Web_Emscripten::MainLoop(void* userData) + { + auto pHost = reinterpret_cast(userData); + + if(!pHost->OnSystemTick()) + { + pHost->StopSystem(); + } + } //TY Moros EM_BOOL Host_Web_Emscripten::keyboard_callback(int eventType, const EmscriptenKeyboardEvent* e, void* userData) @@ -365,29 +426,6 @@ namespace olc::host return EM_TRUE; } - //TY Moros - EM_BOOL Host_Web_Emscripten::wheel_callback(int eventType, const EmscriptenWheelEvent* e, void* userData) - { - CallbackData* pCallbackData = reinterpret_cast(userData); - - if (eventType == EMSCRIPTEN_EVENT_WHEEL) - olc_OnMouseWheel(pCallbackData->pWindow, -1 * e->deltaY); - - return EM_TRUE; - } - - olc::Window* Host_Web_Emscripten::GetWindowFromCanvasId(std::string canvasId) - { - auto itr = mapCanvasId2PTR.find(canvasId); - if(itr != mapCanvasId2PTR.end()) - { - return itr->second; - } - - throw std::runtime_error("failed to get window for canvas id: " + canvasId); - return nullptr; - } - //TY Moros EM_BOOL Host_Web_Emscripten::mouse_callback(int eventType, const EmscriptenMouseEvent* e, void* userData) { @@ -429,6 +467,17 @@ namespace olc::host return EM_FALSE; } + + //TY Moros + EM_BOOL Host_Web_Emscripten::wheel_callback(int eventType, const EmscriptenWheelEvent* e, void* userData) + { + CallbackData* pCallbackData = reinterpret_cast(userData); + + if (eventType == EMSCRIPTEN_EVENT_WHEEL) + olc_OnMouseWheel(pCallbackData->pWindow, -1 * e->deltaY); + + return EM_TRUE; + } //TY Bispoo EM_BOOL Host_Web_Emscripten::touch_callback(int eventType, const EmscriptenTouchEvent* e, void* userData) @@ -459,22 +508,6 @@ namespace olc::host return EM_TRUE; } - //TY Gorbit - EM_BOOL Host_Web_Emscripten::focus_callback(int eventType, const EmscriptenFocusEvent* focusEvent, void* userData) - { - CallbackData* pCallbackData = reinterpret_cast(userData); - - if (eventType == EMSCRIPTEN_EVENT_BLUR) - { - olc_OnMouseFocus(pCallbackData->pWindow, false); - } - else if (eventType == EMSCRIPTEN_EVENT_FOCUS) - { - olc_OnMouseFocus(pCallbackData->pWindow, true); - } - - return 0; - } //TY Moros EM_BOOL Host_Web_Emscripten::fullscreen_change_callback(int eventType, const EmscriptenFullscreenChangeEvent *event, void *userData) @@ -525,39 +558,21 @@ namespace olc::host return 0; } - bool Host_Web_Emscripten::CloseWindowFrame(olc::Window* pWindow) - { - std::cout << "Emscripten: CloseWindowFrame not implemented.\n"; - return true; - } - - bool Host_Web_Emscripten::UpdateWindowFrameTitle(olc::Window* pWindow) - { - // not implemented for emscripten platform - return true; - } - - std::vector Host_Web_Emscripten::GetHostWindowDescriptor(olc::Window* pWindow) + //TY Gorbit + EM_BOOL Host_Web_Emscripten::focus_callback(int eventType, const EmscriptenFocusEvent* focusEvent, void* userData) { - const auto window_handle = mapUID2CanvasId.find(pWindow->GetUID()); - if(window_handle != mapUID2CanvasId.end()) + CallbackData* pCallbackData = reinterpret_cast(userData); + + if (eventType == EMSCRIPTEN_EVENT_BLUR) { - return { (void*)(window_handle->second.c_str()) }; + olc_OnMouseFocus(pCallbackData->pWindow, false); + } + else if (eventType == EMSCRIPTEN_EVENT_FOCUS) + { + olc_OnMouseFocus(pCallbackData->pWindow, true); } - - return {}; - } - // Wait for entire host desktop refresh (for smooooth vsync) - bool Host_Web_Emscripten::SyncWithDesktopComposite() - { - // SyncWithDesktopComposite not implemented on this platform - return true; - } - - olc::KeyboardLayout Host_Web_Emscripten::GetKeyboardLayout() const - { - return static_cast(EM_ASM_INT({ return Module.keyboardLayout || 0; })); + return 0; } bool Host_Web_Emscripten::olc_OnMouseButton(olc::Window* pWindow, const uint8_t nButton, const bool bPressed) @@ -599,7 +614,5 @@ namespace olc::host { return pWindow->olc_OnWindowClose(); } - - } //! END IMPLEMENTATION diff --git a/dev/src/host_web_emscripten.h b/dev/src/host_web_emscripten.h index 22d34605..595479cd 100644 --- a/dev/src/host_web_emscripten.h +++ b/dev/src/host_web_emscripten.h @@ -1,4 +1,5 @@ #pragma once +#include "core.h" //! START STDHEADER GLOBAL #include @@ -22,16 +23,28 @@ namespace olc::host { public: Host_Web_Emscripten(); + + public: // OS Window Handling + // Make OS Create a window frame, associated with olc::Window bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; + // Make OS Close a window frame, associated with olc::Window bool CloseWindowFrame(olc::Window* pWindow) override; + // Make OS Update a window frame title, associated with olc::Window bool UpdateWindowFrameTitle(olc::Window* pWindow) override; - + // Get OS-specific window descriptor(s) for given olc::Window std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; - // Wait for entire host desktop refresh (for smooooth vsync) bool SyncWithDesktopComposite() override; + + public: // Platform specific Mouse Control + // Force the mouse position in pixels relative to window + bool SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) override; + // Show or hide mouse cursor for given window + bool SetMouseVisible(olc::Window* pWindow, const bool bVisible) override; + + public: // OS Specific Environment Information olc::KeyboardLayout GetKeyboardLayout() const override; - + public: // Called at very start of application bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; @@ -52,8 +65,8 @@ namespace olc::host public: // event callbacks static EM_BOOL keyboard_callback(int eventType, const EmscriptenKeyboardEvent* e, void* userData); - static EM_BOOL wheel_callback(int eventType, const EmscriptenWheelEvent* e, void* userData); static EM_BOOL mouse_callback(int eventType, const EmscriptenMouseEvent* e, void* userData); + static EM_BOOL wheel_callback(int eventType, const EmscriptenWheelEvent* e, void* userData); static EM_BOOL touch_callback(int eventType, const EmscriptenTouchEvent* e, void* userData); static EM_BOOL fullscreen_change_callback(int eventType, const EmscriptenFullscreenChangeEvent *event, void *userData); static EM_BOOL resize_callback(int eventType, const EmscriptenUiEvent *event, void *userData); @@ -74,9 +87,6 @@ namespace olc::host static bool olc_OnWindowSize(olc::Window* pWindow, const olc::vi2d& vWindowSize); static bool olc_OnWindowClose(olc::Window* pWindow); - private: // helpers - static olc::Window* GetWindowFromCanvasId(std::string canvasId); - public: // Callback data type struct CallbackData { Host_Web_Emscripten* pHost; diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index d0ba0697..e76e6db4 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -5615,16 +5615,28 @@ namespace olc::host { public: Host_Web_Emscripten(); + + public: // OS Window Handling + // Make OS Create a window frame, associated with olc::Window bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; + // Make OS Close a window frame, associated with olc::Window bool CloseWindowFrame(olc::Window* pWindow) override; + // Make OS Update a window frame title, associated with olc::Window bool UpdateWindowFrameTitle(olc::Window* pWindow) override; - + // Get OS-specific window descriptor(s) for given olc::Window std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; - // Wait for entire host desktop refresh (for smooooth vsync) bool SyncWithDesktopComposite() override; + + public: // Platform specific Mouse Control + // Force the mouse position in pixels relative to window + bool SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) override; + // Show or hide mouse cursor for given window + bool SetMouseVisible(olc::Window* pWindow, const bool bVisible) override; + + public: // OS Specific Environment Information olc::KeyboardLayout GetKeyboardLayout() const override; - + public: // Called at very start of application bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; @@ -5645,8 +5657,8 @@ namespace olc::host public: // event callbacks static EM_BOOL keyboard_callback(int eventType, const EmscriptenKeyboardEvent* e, void* userData); - static EM_BOOL wheel_callback(int eventType, const EmscriptenWheelEvent* e, void* userData); static EM_BOOL mouse_callback(int eventType, const EmscriptenMouseEvent* e, void* userData); + static EM_BOOL wheel_callback(int eventType, const EmscriptenWheelEvent* e, void* userData); static EM_BOOL touch_callback(int eventType, const EmscriptenTouchEvent* e, void* userData); static EM_BOOL fullscreen_change_callback(int eventType, const EmscriptenFullscreenChangeEvent *event, void *userData); static EM_BOOL resize_callback(int eventType, const EmscriptenUiEvent *event, void *userData); @@ -5667,9 +5679,6 @@ namespace olc::host static bool olc_OnWindowSize(olc::Window* pWindow, const olc::vi2d& vWindowSize); static bool olc_OnWindowClose(olc::Window* pWindow); - private: // helpers - static olc::Window* GetWindowFromCanvasId(std::string canvasId); - public: // Callback data type struct CallbackData { Host_Web_Emscripten* pHost; @@ -11211,80 +11220,6 @@ namespace olc::host std::unordered_map Host_Web_Emscripten::mapCanvasId2PTR; std::unordered_map> Host_Web_Emscripten::mapUID2CallbackData; - // Called at very start of application - bool Host_Web_Emscripten::OnApplicationStart(olc::PixelGameEngine* pPrimary) - { - std::cout << "Emscripten: OnApplicationStart.\n"; - pPrimaryPGE = pPrimary; - return true; - } - - void Host_Web_Emscripten::MainLoop(void* userData) - { - auto pHost = reinterpret_cast(userData); - - if(!pHost->OnSystemTick()) - { - pHost->StopSystem(); - } - } - - // Called to start the host - this may mean different things on different hosts - bool Host_Web_Emscripten::StartSystem() - { - // Pre-context start hook - pPrimaryPGE->OnPreContextStart(); - - if(!OnSystemThreadStart()) - { - // PGE->ContextStart() failed, or user aborted OnUserCreate() - return false; - } - - emscripten_set_main_loop_arg(Host_Web_Emscripten::MainLoop, reinterpret_cast(this), 0, 1); - - // EMSCRIPTEN QUIRK: this code is never reached, the main loop is simulating a while(true); - - return true; - } - - // Called to stop the host, and shutdown all resources - bool Host_Web_Emscripten::StopSystem() - { - std::cout << "Emscripten: StopSystem.\n"; - OnSystemThreadEnd(); - pPrimaryPGE->OnPostContextEnd(); - emscripten_cancel_main_loop(); - return true; - } - - // Called at start of system event loop - bool Host_Web_Emscripten::OnSystemThreadStart() - { - std::cout << "Emscripten: OnSystemThreadStart.\n"; - return pPrimaryPGE->OnContextStart(); - } - - // Called to perform primary window update - bool Host_Web_Emscripten::OnSystemTick() - { - return pPrimaryPGE->OnContextTick(); - } - - // Called at end of system event loop - bool Host_Web_Emscripten::OnSystemThreadEnd() - { - std::cout << "Emscripten: OnSystemThreadEnd.\n"; - return pPrimaryPGE->OnContextEnd(); - } - - // Called at very end of application - bool Host_Web_Emscripten::OnApplicationEnd() - { - std::cout << "Emscripten: OnApplicationEnd.\n"; - return true; - } - Host_Web_Emscripten::Host_Web_Emscripten() { std::cout << "Emscripten: host constructed.\n"; @@ -11504,6 +11439,141 @@ namespace olc::host return true; } + + bool Host_Web_Emscripten::CloseWindowFrame(olc::Window* pWindow) + { + std::cout << "Emscripten: CloseWindowFrame not implemented.\n"; + return true; + } + + bool Host_Web_Emscripten::UpdateWindowFrameTitle(olc::Window* pWindow) + { + // not implemented for emscripten platform + return true; + } + + std::vector Host_Web_Emscripten::GetHostWindowDescriptor(olc::Window* pWindow) + { + const auto window_handle = mapUID2CanvasId.find(pWindow->GetUID()); + if(window_handle != mapUID2CanvasId.end()) + { + return { (void*)(window_handle->second.c_str()) }; + } + + return {}; + } + + // Wait for entire host desktop refresh (for smooooth vsync) + bool Host_Web_Emscripten::SyncWithDesktopComposite() + { + // SyncWithDesktopComposite not implemented on this platform + return true; + } + + // Force the mouse position in pixels relative to window + bool Host_Web_Emscripten::SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) + { + // Not supported on the web platform + olc_IgnoreUnused(pWindow, vPos); + + static bool debounce = false; + if(debounce) + return false; + + debounce = true; + std::cout << "SetMousePosition is not supported on this platform.\n"; + return false; + } + + // Show or hide mouse cursor for given window + bool Host_Web_Emscripten::SetMouseVisible(olc::Window* pWindow, const bool bVisible) + { + if(!mapUID2CanvasId.contains(pWindow->GetUID())) + return false; + + auto canvasID = mapUID2CanvasId.at(pWindow->GetUID()); + if(bVisible) + EM_ASM({ document.querySelector(UTF8ToString($0)).style.cursor = 'default'; }, canvasID.c_str()); + else + EM_ASM({ document.querySelector(UTF8ToString($0)).style.cursor = 'none'; }, canvasID.c_str()); + + return true; + } + + olc::KeyboardLayout Host_Web_Emscripten::GetKeyboardLayout() const + { + return static_cast(EM_ASM_INT({ return Module.keyboardLayout || 0; })); + } + + // Called at very start of application + bool Host_Web_Emscripten::OnApplicationStart(olc::PixelGameEngine* pPrimary) + { + std::cout << "Emscripten: OnApplicationStart.\n"; + pPrimaryPGE = pPrimary; + return true; + } + + // Called to start the host - this may mean different things on different hosts + bool Host_Web_Emscripten::StartSystem() + { + // Pre-context start hook + pPrimaryPGE->OnPreContextStart(); + + if(!OnSystemThreadStart()) + { + // PGE->ContextStart() failed, or user aborted OnUserCreate() + return false; + } + + emscripten_set_main_loop_arg(Host_Web_Emscripten::MainLoop, reinterpret_cast(this), 0, 1); + + // EMSCRIPTEN QUIRK: this code is never reached, the main loop is simulating a while(true); + + return true; + } + + // Called to stop the host, and shutdown all resources + bool Host_Web_Emscripten::StopSystem() + { + OnSystemThreadEnd(); + pPrimaryPGE->OnPostContextEnd(); + emscripten_cancel_main_loop(); + return true; + } + + // Called at start of system event loop + bool Host_Web_Emscripten::OnSystemThreadStart() + { + return pPrimaryPGE->OnContextStart(); + } + + // Called to perform primary window update + bool Host_Web_Emscripten::OnSystemTick() + { + return pPrimaryPGE->OnContextTick(); + } + + // Called at end of system event loop + bool Host_Web_Emscripten::OnSystemThreadEnd() + { + return pPrimaryPGE->OnContextEnd(); + } + + // Called at very end of application + bool Host_Web_Emscripten::OnApplicationEnd() + { + return true; + } + + void Host_Web_Emscripten::MainLoop(void* userData) + { + auto pHost = reinterpret_cast(userData); + + if(!pHost->OnSystemTick()) + { + pHost->StopSystem(); + } + } //TY Moros EM_BOOL Host_Web_Emscripten::keyboard_callback(int eventType, const EmscriptenKeyboardEvent* e, void* userData) @@ -11569,29 +11639,6 @@ namespace olc::host return EM_TRUE; } - //TY Moros - EM_BOOL Host_Web_Emscripten::wheel_callback(int eventType, const EmscriptenWheelEvent* e, void* userData) - { - CallbackData* pCallbackData = reinterpret_cast(userData); - - if (eventType == EMSCRIPTEN_EVENT_WHEEL) - olc_OnMouseWheel(pCallbackData->pWindow, -1 * e->deltaY); - - return EM_TRUE; - } - - olc::Window* Host_Web_Emscripten::GetWindowFromCanvasId(std::string canvasId) - { - auto itr = mapCanvasId2PTR.find(canvasId); - if(itr != mapCanvasId2PTR.end()) - { - return itr->second; - } - - throw std::runtime_error("failed to get window for canvas id: " + canvasId); - return nullptr; - } - //TY Moros EM_BOOL Host_Web_Emscripten::mouse_callback(int eventType, const EmscriptenMouseEvent* e, void* userData) { @@ -11633,6 +11680,17 @@ namespace olc::host return EM_FALSE; } + + //TY Moros + EM_BOOL Host_Web_Emscripten::wheel_callback(int eventType, const EmscriptenWheelEvent* e, void* userData) + { + CallbackData* pCallbackData = reinterpret_cast(userData); + + if (eventType == EMSCRIPTEN_EVENT_WHEEL) + olc_OnMouseWheel(pCallbackData->pWindow, -1 * e->deltaY); + + return EM_TRUE; + } //TY Bispoo EM_BOOL Host_Web_Emscripten::touch_callback(int eventType, const EmscriptenTouchEvent* e, void* userData) @@ -11663,22 +11721,6 @@ namespace olc::host return EM_TRUE; } - //TY Gorbit - EM_BOOL Host_Web_Emscripten::focus_callback(int eventType, const EmscriptenFocusEvent* focusEvent, void* userData) - { - CallbackData* pCallbackData = reinterpret_cast(userData); - - if (eventType == EMSCRIPTEN_EVENT_BLUR) - { - olc_OnMouseFocus(pCallbackData->pWindow, false); - } - else if (eventType == EMSCRIPTEN_EVENT_FOCUS) - { - olc_OnMouseFocus(pCallbackData->pWindow, true); - } - - return 0; - } //TY Moros EM_BOOL Host_Web_Emscripten::fullscreen_change_callback(int eventType, const EmscriptenFullscreenChangeEvent *event, void *userData) @@ -11729,39 +11771,21 @@ namespace olc::host return 0; } - bool Host_Web_Emscripten::CloseWindowFrame(olc::Window* pWindow) - { - std::cout << "Emscripten: CloseWindowFrame not implemented.\n"; - return true; - } - - bool Host_Web_Emscripten::UpdateWindowFrameTitle(olc::Window* pWindow) - { - // not implemented for emscripten platform - return true; - } - - std::vector Host_Web_Emscripten::GetHostWindowDescriptor(olc::Window* pWindow) + //TY Gorbit + EM_BOOL Host_Web_Emscripten::focus_callback(int eventType, const EmscriptenFocusEvent* focusEvent, void* userData) { - const auto window_handle = mapUID2CanvasId.find(pWindow->GetUID()); - if(window_handle != mapUID2CanvasId.end()) + CallbackData* pCallbackData = reinterpret_cast(userData); + + if (eventType == EMSCRIPTEN_EVENT_BLUR) { - return { (void*)(window_handle->second.c_str()) }; + olc_OnMouseFocus(pCallbackData->pWindow, false); + } + else if (eventType == EMSCRIPTEN_EVENT_FOCUS) + { + olc_OnMouseFocus(pCallbackData->pWindow, true); } - - return {}; - } - // Wait for entire host desktop refresh (for smooooth vsync) - bool Host_Web_Emscripten::SyncWithDesktopComposite() - { - // SyncWithDesktopComposite not implemented on this platform - return true; - } - - olc::KeyboardLayout Host_Web_Emscripten::GetKeyboardLayout() const - { - return static_cast(EM_ASM_INT({ return Module.keyboardLayout || 0; })); + return 0; } bool Host_Web_Emscripten::olc_OnMouseButton(olc::Window* pWindow, const uint8_t nButton, const bool bPressed) @@ -11803,8 +11827,6 @@ namespace olc::host { return pWindow->olc_OnWindowClose(); } - - } #endif From aeb2013bcc391d654c76f1042db660447391f2c2 Mon Sep 17 00:00:00 2001 From: dandistine <34634876+dandistine@users.noreply.github.com> Date: Sun, 15 Feb 2026 10:18:55 -0600 Subject: [PATCH 060/101] Add pointer warp support --- dev/src/host_lin_wayland.cpp | 28 ++++++++++++++++++++++++++-- dev/src/host_lin_wayland.h | 5 +++++ examples/CMakeLists.txt | 23 +++++++++++++++++++++++ olcPixelGameEngine3.h | 34 ++++++++++++++++++++++++++++++++-- 4 files changed, 86 insertions(+), 4 deletions(-) diff --git a/dev/src/host_lin_wayland.cpp b/dev/src/host_lin_wayland.cpp index cc67dcf2..c91245e1 100644 --- a/dev/src/host_lin_wayland.cpp +++ b/dev/src/host_lin_wayland.cpp @@ -321,6 +321,25 @@ namespace olc::host return keyboardLayout; } + bool Host_Linux_Wayland::SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) + { + if(pointer_warp) { + auto itr = mapUID2Window.find(pWindow->GetUID()); + if(itr != mapUID2Window.end()) { + wp_pointer_warp_v1_warp_pointer(pointer_warp, itr->second.surface, pointer, wl_fixed_from_int(vPos.x), wl_fixed_from_int(vPos.y), enter_serial); + pWindow->olc_OnMouseMove(vPos); + return true; + } + } + + return false; + } + + bool Host_Linux_Wayland::SetMouseVisible(olc::Window* pWindow, const bool bVisible) + { + return false; + } + void Host_Linux_Wayland::registry_handle_global(wl_registry* registry, uint32_t name, const char* interface, uint32_t version) { @@ -339,6 +358,9 @@ namespace olc::host if(std::strcmp(interface, wl_keyboard_interface.name) == 0) { keyboard = static_cast(wl_registry_bind(registry, name, &wl_keyboard_interface, version)); } + if(std::strcmp(interface, wp_pointer_warp_v1_interface.name) == 0) { + pointer_warp = static_cast(wl_registry_bind(registry, name, &wp_pointer_warp_v1_interface, version)); + } } void Host_Linux_Wayland::registry_handle_global_remove(wl_registry* registry, uint32_t name) @@ -350,7 +372,7 @@ namespace olc::host { if (capabilities & WL_SEAT_CAPABILITY_POINTER && pointer == nullptr) { pointer = wl_seat_get_pointer(seat); - wl_pointer_add_listener(pointer, &wayland::pointer_listener, this); + wl_pointer_add_listener(pointer, &wayland::pointer_listener, this); } if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD && keyboard == nullptr) { @@ -438,6 +460,8 @@ namespace olc::host { pointer_state.event_mask |= wayland::PointerEventMask::PointerEventEnter; pointer_state.serial = serial; + // Save so we can reuse the serial for pointer warping + enter_serial = serial; pointer_state.surface_x = surface_x; pointer_state.surface_y = surface_y; } @@ -595,7 +619,7 @@ namespace olc::host //auto* host = reinterpret_cast(data); //host->pointer_axis_relative_direction(pointer, axis, direction); } - + // Keyboard Callbacks void Host_Linux_Wayland::keyboard_keymap_callback(void* data, wl_keyboard* keyboard, uint32_t format, int fd, uint32_t size) { diff --git a/dev/src/host_lin_wayland.h b/dev/src/host_lin_wayland.h index 0b343870..460c2caa 100644 --- a/dev/src/host_lin_wayland.h +++ b/dev/src/host_lin_wayland.h @@ -20,6 +20,7 @@ #include #include "xdg-shell.h" #include "xdg-decoration.h" +#include "pointer-warp.h" #include #include #include @@ -91,6 +92,8 @@ namespace olc::host uint32_t kb_group{0}; xdg_wm_base* xdg_wm{nullptr}; zxdg_decoration_manager_v1* decoration_manager{nullptr}; + wp_pointer_warp_v1* pointer_warp{nullptr}; + uint32_t enter_serial{0}; wayland::PointerState pointer_state; @@ -111,6 +114,8 @@ namespace olc::host // Wait for entire host desktop refresh (for smooooth vsync) bool SyncWithDesktopComposite() override; + bool SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) override; + bool SetMouseVisible(olc::Window* pWindow, const bool bVisible) override; public: bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 65fbdf8c..4646ac3d 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -36,6 +36,7 @@ if(UNIX AND BUILD_WAYLAND) # 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") + set(POINTER_WARP_PROTOCOL "/usr/share/wayland-protocols/staging/pointer-warp/pointer-warp-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") @@ -45,11 +46,17 @@ if(UNIX AND BUILD_WAYLAND) message(FATAL_ERROR "xdg-decoration protocol not found. You may need to install wayland-protocols via your package manager") endif() + if(NOT EXISTS ${POINTER_WARP_PROTOCOL}) + message(FATAL_ERROR "pointer-warp 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) + set(POINTER_WARP_C ${CMAKE_CURRENT_BINARY_DIR}/pointer-warp.c) + set(POINTER_WARP_H ${CMAKE_CURRENT_BINARY_DIR}/pointer-warp.h) add_custom_command( OUTPUT ${XDG_SHELL_C} @@ -79,12 +86,28 @@ if(UNIX AND BUILD_WAYLAND) VERBATIM ) + add_custom_command( + OUTPUT ${POINTER_WARP_C} + COMMAND ${WAYLAND_SCANNER} private-code ${POINTER_WARP_PROTOCOL} ${POINTER_WARP_C} + DEPENDS ${POINTER_WARP_PROTOCOL} + VERBATIM + ) + + add_custom_command( + OUTPUT ${POINTER_WARP_H} + COMMAND ${WAYLAND_SCANNER} client-header ${POINTER_WARP_PROTOCOL} ${POINTER_WARP_H} + DEPENDS ${POINTER_WARP_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} + ${POINTER_WARP_C} + ${POINTER_WARP_H} ) target_include_directories(wayland_protocols PUBLIC diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index d0ba0697..66c1371e 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -5423,6 +5423,7 @@ namespace olc::host #include #include "xdg-shell.h" #include "xdg-decoration.h" +#include "pointer-warp.h" #include #include #include @@ -5494,6 +5495,8 @@ namespace olc::host uint32_t kb_group{0}; xdg_wm_base* xdg_wm{nullptr}; zxdg_decoration_manager_v1* decoration_manager{nullptr}; + wp_pointer_warp_v1* pointer_warp{nullptr}; + uint32_t enter_serial{0}; wayland::PointerState pointer_state; @@ -5514,6 +5517,8 @@ namespace olc::host // Wait for entire host desktop refresh (for smooooth vsync) bool SyncWithDesktopComposite() override; + bool SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) override; + bool SetMouseVisible(olc::Window* pWindow, const bool bVisible) override; public: bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; @@ -10734,9 +10739,29 @@ namespace olc::host return keyboardLayout; } + bool Host_Linux_Wayland::SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) + { + if(pointer_warp) { + auto itr = mapUID2Window.find(pWindow->GetUID()); + if(itr != mapUID2Window.end()) { + wp_pointer_warp_v1_warp_pointer(pointer_warp, itr->second.surface, pointer, wl_fixed_from_int(vPos.x), wl_fixed_from_int(vPos.y), enter_serial); + pWindow->olc_OnMouseMove(vPos); + return true; + } + } + + return false; + } + + bool Host_Linux_Wayland::SetMouseVisible(olc::Window* pWindow, const bool bVisible) + { + return false; + } + void Host_Linux_Wayland::registry_handle_global(wl_registry* registry, uint32_t name, const char* interface, uint32_t version) { + std::cout << "Global registry event: " << interface << std::endl; if(std::strcmp(interface, wl_compositor_interface.name) == 0) { compositor = static_cast(wl_registry_bind(registry, name, &wl_compositor_interface, version)); } @@ -10752,6 +10777,9 @@ namespace olc::host if(std::strcmp(interface, wl_keyboard_interface.name) == 0) { keyboard = static_cast(wl_registry_bind(registry, name, &wl_keyboard_interface, version)); } + if(std::strcmp(interface, wp_pointer_warp_v1_interface.name) == 0) { + pointer_warp = static_cast(wl_registry_bind(registry, name, &wp_pointer_warp_v1_interface, version)); + } } void Host_Linux_Wayland::registry_handle_global_remove(wl_registry* registry, uint32_t name) @@ -10763,7 +10791,7 @@ namespace olc::host { if (capabilities & WL_SEAT_CAPABILITY_POINTER && pointer == nullptr) { pointer = wl_seat_get_pointer(seat); - wl_pointer_add_listener(pointer, &wayland::pointer_listener, this); + wl_pointer_add_listener(pointer, &wayland::pointer_listener, this); } if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD && keyboard == nullptr) { @@ -10851,6 +10879,8 @@ namespace olc::host { pointer_state.event_mask |= wayland::PointerEventMask::PointerEventEnter; pointer_state.serial = serial; + // Save so we can reuse the serial for pointer warping + enter_serial = serial; pointer_state.surface_x = surface_x; pointer_state.surface_y = surface_y; } @@ -11008,7 +11038,7 @@ namespace olc::host //auto* host = reinterpret_cast(data); //host->pointer_axis_relative_direction(pointer, axis, direction); } - + // Keyboard Callbacks void Host_Linux_Wayland::keyboard_keymap_callback(void* data, wl_keyboard* keyboard, uint32_t format, int fd, uint32_t size) { From 94722d166a6b1e4f018e2dce85d67459ac7cdc31 Mon Sep 17 00:00:00 2001 From: John Galvin <96908304+Johnnyg63@users.noreply.github.com> Date: Thu, 19 Feb 2026 16:26:57 +0000 Subject: [PATCH 061/101] 187 Update MacOS to support Mouse Capture and Show Mouse Cursor. --- dev/src/api_macos.cpp | 214 ++++++++++++++--- dev/src/api_macos.h | 7 +- dev/src/api_macos_wrapper.hpp | 49 ++++ dev/src/host_apple_macos.cpp | 112 +++++---- dev/src/host_apple_macos.h | 48 ++-- olcPixelGameEngine3.h | 427 ++++++++++++++++++++++++++-------- 6 files changed, 656 insertions(+), 201 deletions(-) diff --git a/dev/src/api_macos.cpp b/dev/src/api_macos.cpp index 31744b5f..850ea8ec 100644 --- a/dev/src/api_macos.cpp +++ b/dev/src/api_macos.cpp @@ -1,5 +1,6 @@ #include "api_macos.h" #include +#include //! START IMPLEMENTATION @@ -17,6 +18,15 @@ static constexpr const char* kWindowDelegateClass = "WindowDelegat static constexpr const char* kCustomOpenGLViewClass = "CustomOpenGLView"; static constexpr const char* kGeneralWindowDelegateClass = "GeneralWindowDelegate"; +// Application Screen management selectors +static constexpr const char* kNSScreenClass = "NSScreen"; +static constexpr const char* kScreenSel = "screen"; +static constexpr const char* kNSCursorClass = "NSCursor"; +static constexpr const char* kUnhideSel = "unhide"; +static constexpr const char* kHideSel = "hide"; +static constexpr const char* kIsHiddenSel = "isHidden"; + + // Application memory management selectors static constexpr const char* kAllocSel = "alloc"; static constexpr const char* kInitSel = "init"; @@ -76,6 +86,13 @@ static constexpr const char* kOtherMouseDraggedSel = "otherMouseDra static constexpr const char* kScrollWheelSel = "scrollWheel:"; static constexpr const char* kDeltaXSel = "deltaX"; static constexpr const char* kDeltaYSel = "deltaY"; +static constexpr const char* kUpdateTrackingAreasSel = "updateTrackingAreas"; +static constexpr const char* kMouseEnteredSel = "mouseEntered:"; +static constexpr const char* kMouseExitedSel = "mouseExited:"; +static constexpr const char* kTrackingAreaClass = "NSTrackingArea"; +static constexpr const char* kAddTrackingAreaSel = "addTrackingArea:"; +static constexpr const char* kInitWithRectSel = "initWithRect:options:owner:userInfo:"; + // Managing first responder status and keyboard focus selectors static constexpr const char* kAcceptsFirstResponderSel = "acceptsFirstResponder"; @@ -142,7 +159,9 @@ static constexpr int kZeroWidth = 0; static constexpr int kZeroHeight = 0; static constexpr int kFlippedOffset = 1; static constexpr int kNoButton = -1; - +static constexpr int kDefaultScreenNumber = 1; +bool bAllowHideCursor = true; +bool bHideCursor = false; // Objective-C method type encoding constants // Type encoding for methods returning BOOL with no parameters: "c@:" @@ -173,6 +192,12 @@ namespace ObjectiveCSEL { static SEL runSel = nullptr; static SEL terminateSEL = nullptr; + // Application Screen management selectors + static SEL screenSel = nullptr; + static SEL unhideSel = nullptr; + static SEL hideSel = nullptr; + static SEL isHiddenSel = nullptr; + // NSApplicationDelegate lifecycle methods static SEL applicationWillFinishLaunchingSel = nullptr; static SEL applicationDidFinishLaunchingSel = nullptr; @@ -202,22 +227,29 @@ namespace ObjectiveCSEL { static SEL windowDidDeminiaturizeSel = nullptr; // 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; - 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; + static SEL keyDownSel = nullptr; + static SEL keyUpSel = nullptr; + static SEL flagsChangedSel = 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; + static SEL updateTrackingAreasSel = nullptr; + static SEL mouseEnteredSel = nullptr; + static SEL mouseExitedSel = nullptr; + + // Mouse Tracking + static SEL addTrackingAreaSel = nullptr; + static SEL initWithRectSel = nullptr; // Managing first responder status and keyboard focus selectors static SEL acceptsFirstResponderSel = nullptr; @@ -287,6 +319,12 @@ namespace ObjectiveCSEL { setActivationPolicySel = sel_registerName(kSetActivationPolicySel); runSel = sel_registerName(kRunSel); terminateSEL = sel_registerName(kTerminateSel); + + // Application Screen management selectors + screenSel = sel_registerName(kScreenSel); + unhideSel = sel_registerName(kUnhideSel); + hideSel = sel_registerName(kHideSel); + isHiddenSel = sel_registerName(kIsHiddenSel); // NSApplicationDelegate lifecycle methods applicationWillFinishLaunchingSel = sel_registerName(kApplicationWillFinishLaunchingSel); @@ -333,7 +371,12 @@ namespace ObjectiveCSEL { scrollWheelSel = sel_registerName(kScrollWheelSel); deltaXSel = sel_registerName(kDeltaXSel); deltaYSel = sel_registerName(kDeltaYSel); - + updateTrackingAreasSel = sel_registerName(kUpdateTrackingAreasSel); + mouseEnteredSel = sel_registerName(kMouseEnteredSel); + mouseExitedSel = sel_registerName(kMouseExitedSel); + addTrackingAreaSel = sel_registerName(kAddTrackingAreaSel); + initWithRectSel = sel_registerName(kInitWithRectSel); + // Managing first responder status and keyboard focus selectors acceptsFirstResponderSel = sel_registerName(kAcceptsFirstResponderSel); becomeFirstResponderSel = sel_registerName(kBecomeFirstResponderSel); @@ -426,14 +469,6 @@ class AutoreleasePool { void* pool_; }; -// CGPoint structure for 2D points -struct CGPoint { - double x{kMinValidDimension}; - double y{kMinValidDimension}; - - constexpr CGPoint() = default; - constexpr CGPoint(double x_val, double y_val) noexcept : x(x_val), y(y_val) {} -}; using NSPoint = CGPoint; using NSInteger = long; @@ -579,6 +614,11 @@ enum class NSApplicationActivationPolicy : uint8_t { // Backward compatibility static constexpr int NSApplicationActivationPolicyRegular = static_cast(NSApplicationActivationPolicy::Regular); +// Mouse Tracking const +static constexpr const NSUInteger NSTrackingMouseEnteredAndExited = 0x01; +static constexpr const NSUInteger NSTrackingActiveInKeyWindow = 0x20; +static constexpr const NSUInteger NSTrackingInVisibleRect = 0x200; + /* * WARNING: Global delegate pointers for Objective-C callbacks * These global pointers are used to route Objective-C delegate callbacks @@ -672,7 +712,11 @@ struct Window { void (*otherMouseDownCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; void (*otherMouseUpCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; void (*otherMouseDraggedCallback)(double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; + void (*mouseEnteredCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; + void (*mouseExitedCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; void (*scrollWheelCallback) (double x, double y, double deltaX, double deltaY, unsigned int modifierFlags, void* userData){nullptr}; + + void* eventUserData{nullptr}; // User data for event callbacks BOOL acceptsInputEvents{NO}; // Whether the window accepts input events @@ -724,7 +768,7 @@ struct OpenGLRenderer { void (*renderYellowBackground)(struct OpenGLRenderer* self){nullptr}; void (*renderTexturedQuad) (struct OpenGLRenderer* self, unsigned int textureID){nullptr}; void* (*getOpenGLContext) (const struct OpenGLRenderer* self){nullptr}; - void* (*getCGLContextObj)(struct OpenGLRenderer* self){nullptr}; + void* (*getCGLContextObj) (struct OpenGLRenderer* self){nullptr}; void* (*getCGLContextObjPtr) (struct OpenGLRenderer* self){nullptr}; void (*makeCurrentContext) (struct OpenGLRenderer* self){nullptr}; void (*setVsync) (struct OpenGLRenderer* self, BOOL enabled){nullptr}; @@ -918,7 +962,6 @@ MouseEventData extractMouseEventData(id event) { // Convert to content view coordinates and flip Y coordinate convertToContentViewCoordinates(data.location); - return data; } @@ -1046,6 +1089,61 @@ void view_scrollWheel(id self, SEL _cmd, id event) { } } +// New events for mouse entered/exited +void view_mouseEntered(id self, SEL _cmd, id event) { + (void)self;(void)_cmd;(void)event; + + // Allow hiding cursor when mouse is inside window + bAllowHideCursor = true; + + // If the cursor is currently hidden, hide it again to ensure it stays hidden while inside the window + if(bHideCursor) + ((void (*)(Class, SEL))objc_msgSend)(objc_getClass(kNSCursorClass), ObjectiveCSEL::hideSel); + + NSPoint location = ((NSPoint(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::locationInWindowSel); + NSUInteger modifierFlags = ((NSUInteger(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); + + convertToContentViewCoordinates(location); + + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseEnteredCallback) [[likely]] { + gptrNSWindowEvents->mouseEnteredCallback(location.x, location.y, kNoButton, (unsigned int)modifierFlags, gptrNSWindowEvents->eventUserData); + } +} + +void view_mouseExited(id self, SEL _cmd, id event) { + (void)self;(void)_cmd;(void)event; + + // Ensure cursor is visible when leaving window + bAllowHideCursor = false; + ((void (*)(Class, SEL))objc_msgSend)(objc_getClass(kNSCursorClass), ObjectiveCSEL::unhideSel); + + NSPoint location = ((NSPoint(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::locationInWindowSel); + NSUInteger modifierFlags = ((NSUInteger(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); + + convertToContentViewCoordinates(location); + + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseExitedCallback) [[likely]] { + gptrNSWindowEvents->mouseExitedCallback(location.x, location.y, kNoButton, (unsigned int)modifierFlags, gptrNSWindowEvents->eventUserData); + } +} + +void view_updateTrackingAreas(id self, SEL _cmd) { + (void)self;(void)_cmd; + + // Create new tracking area + NSRect bounds = ((NSRect(*)(id, SEL))objc_msgSend)(self, ObjectiveCSEL::boundsSel); + + // Correct tracking options: mouse entered/exited + active in key window + NSUInteger options = NSTrackingMouseEnteredAndExited | NSTrackingActiveInKeyWindow | NSTrackingInVisibleRect; + + Class NSTrackingAreaClass = objc_getClass(kTrackingAreaClass); + id trackingArea = ((id(*)(id, SEL, NSRect, NSUInteger, id, id))objc_msgSend)( + ((id(*)(Class, SEL))objc_msgSend)(NSTrackingAreaClass, ObjectiveCSEL::allocSel), + ObjectiveCSEL::initWithRectSel, bounds, options, self, nil); + + ((void(*)(id, SEL, id))objc_msgSend)(self, ObjectiveCSEL::addTrackingAreaSel, trackingArea); +} + // Determine if view can accept first responder status id view_acceptsFirstResponder(id self, SEL _cmd) { (void)self;(void)_cmd; @@ -1121,7 +1219,12 @@ Class createCustomOpenGLViewClass() { class_addMethod(CustomViewClass, ObjectiveCSEL::otherMouseUpSel, (IMP)view_otherMouseUp, kEventHandlerMethodTypeEncoding); class_addMethod(CustomViewClass, ObjectiveCSEL::otherMouseDraggedSel, (IMP)view_otherMouseDragged, kEventHandlerMethodTypeEncoding); class_addMethod(CustomViewClass, ObjectiveCSEL::scrollWheelSel, (IMP)view_scrollWheel, kEventHandlerMethodTypeEncoding); - + + // Area tracking for Mouse entered/exited event handler methods + class_addMethod(CustomViewClass, ObjectiveCSEL::updateTrackingAreasSel, (IMP)view_updateTrackingAreas, kVoidMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::mouseEnteredSel, (IMP)view_mouseEntered, kEventHandlerMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::mouseExitedSel, (IMP)view_mouseExited, kEventHandlerMethodTypeEncoding); + // First responder methods class_addMethod(CustomViewClass, ObjectiveCSEL::acceptsFirstResponderSel, (IMP)view_acceptsFirstResponder, kBoolMethodTypeEncoding); class_addMethod(CustomViewClass, ObjectiveCSEL::becomeFirstResponderSel, (IMP)view_becomeFirstResponder, kBoolMethodTypeEncoding); @@ -1451,8 +1554,7 @@ extern "C" { // Window title setter void window_setTitle(Window* self, const char* title) { self->title = title; - - // If window is already created, update the NSWindow title + if (self->nsWindow) { Class NSStringClass = objc_getClass(kNSStringClass); @@ -1572,6 +1674,46 @@ extern "C" { window_setWindowFrame(self, self->windowFrame.x, self->windowFrame.y, width, height); } + // Set the cusor position relative to the content view + void window_setCursorPosition(Window* self, double x, double y) { + if (!self || !self->nsWindow) return; + + // Get current screen the window is on + id currentScreen = ((id(*)(id, SEL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::screenSel); + NSRect screenFrame = ((NSRect(*)(id, SEL))objc_msgSend)(currentScreen, ObjectiveCSEL::frameSel); + + // Update internal frame representation to get the latest window position + window_updateFrameFromOSX(self); + NSPoint location = {self->windowFrame.x, self->windowFrame.y}; + + // Get the content view + id contentView = ((id(*)(id, SEL))objc_msgSend)(gptrNSWindowEvents->nsWindow, ObjectiveCSEL::contentViewSel); + if (contentView) { + + // Get the content view bounds to flip Y coordinate + NSRect contentBounds = ((NSRect(*)(id, SEL))objc_msgSend)(contentView, ObjectiveCSEL::boundsSel); + + // NOTE: we need to ensure the new cursor position is within the window bounds to prevent unexpected behavior + auto posX = std::clamp(location.x +x, location.x, location.x + contentBounds.width); + auto posY = std::clamp(screenFrame.height - location.y - contentBounds.height + y, + screenFrame.height - location.y - contentBounds.height, + screenFrame.height - location.y); + CGWarpMouseCursorPosition(CGPointMake(posX, posY)); + } + + } + + // Set cursor visibility + void window_setCursorVisibility(Window* self, BOOL visible) { + if (!self || !self->nsWindow) return; + bHideCursor = !visible; + if (bHideCursor && bAllowHideCursor) { + ((void (*)(Class, SEL))objc_msgSend)(objc_getClass(kNSCursorClass), ObjectiveCSEL::hideSel); + } else { + ((void (*)(Class, SEL))objc_msgSend)(objc_getClass(kNSCursorClass), ObjectiveCSEL::unhideSel); + } + } + // Initialize OpenGL renderer void opengl_initialize(OpenGLRenderer* self, Window* window) { self->window = window; @@ -2071,6 +2213,16 @@ extern "C" { self->eventUserData = userData; } + void window_setMouseEnteredCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->mouseEnteredCallback = callback; + self->eventUserData = userData; + } + + void window_setMouseExitedCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->mouseExitedCallback = callback; + self->eventUserData = userData; + } + void window_enableEventHandling(Window* self) { if (self) { self->acceptsInputEvents = YES; diff --git a/dev/src/api_macos.h b/dev/src/api_macos.h index 66e561d1..50c47024 100644 --- a/dev/src/api_macos.h +++ b/dev/src/api_macos.h @@ -23,6 +23,7 @@ #include #include #include +#include extern "C" { // NSRect (OSX rectangle structure same as GCRect C structure) @@ -69,7 +70,9 @@ extern "C" { void window_setWindowSize (struct Window* self, double width, double height); void window_getContentViewFrame (const struct Window* self, double* x, double* y, double* width, double* height); void window_setContentViewFrame (struct Window* self, double* x, double* y, double* width, double* height); - + void window_setCursorVisibility (struct Window* self, BOOL visible); + void window_setCursorPosition (struct Window* self, double x, double y); + // OpenGL Renderer API - as implemented in api_macos.c struct OpenGLRenderer* opengl_init (void); void opengl_initialize (struct OpenGLRenderer* self, struct Window* window); @@ -118,6 +121,8 @@ extern "C" { void window_setOtherMouseUpCallback (struct Window* self, MouseEventCallback callback, void* userData); void window_setOtherMouseDraggedCallback(struct Window* self, MouseEventCallback callback, void* userData); void window_setScrollWheelCallback (struct Window* self, void (*callback)(double, double, double, double, unsigned int, void*), void* userData); + void window_setMouseEnteredCallback (struct Window* self, MouseEventCallback callback, void* userData); + void window_setMouseExitedCallback (struct Window* self, MouseEventCallback callback, void* userData); // Event system management void window_enableEventHandling (struct Window* self); diff --git a/dev/src/api_macos_wrapper.hpp b/dev/src/api_macos_wrapper.hpp index f72f5d04..52be9a2b 100644 --- a/dev/src/api_macos_wrapper.hpp +++ b/dev/src/api_macos_wrapper.hpp @@ -459,6 +459,29 @@ namespace olc { setCallback(window_setWindowDidDeminiaturizeCallback, std::move(callback)); } + // Set the cusror position within the window + void setCursorPosition(int32_t x, int32_t y) noexcept { + setCursorPosition(static_cast(x), static_cast(y)); + } + + void setCursorPosition(float x, float y) noexcept { + setCursorPosition(static_cast(x), static_cast(y)); + } + + void setCursorPosition(double x, double y) noexcept { + if (window_) { + window_setCursorPosition(window_, x, y); + } + } + + // Set cursor visibility + void setCursorVisibility(bool visible) noexcept { + if (window_) { + window_setCursorVisibility(window_, visible); + } + } + + // Non-copyable but movable Window(const Window&) = delete; Window& operator=(const Window&) = delete; @@ -729,6 +752,8 @@ namespace olc { std::function otherMouseUpHandler_; std::function otherMouseDraggedHandler_; std::function scrollWheelHandler_; + std::function mouseMovedEnteredHandler_; + std::function mouseMovedExitedHandler_; // Template helpers for static callbacks to reduce code duplication template @@ -782,6 +807,20 @@ namespace olc { eventHandler->mouseMovedHandler_(MouseEvent(x, y, buttonNumber, modifierFlags)); } } + + static void mouseEnteredCallback(double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData) { + auto* eventHandler = static_cast(userData); + if (eventHandler && eventHandler->mouseMovedEnteredHandler_) { + eventHandler->mouseMovedEnteredHandler_(MouseEvent(x, y, buttonNumber, modifierFlags)); + } + } + + static void mouseExitedCallback(double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData) { + auto* eventHandler = static_cast(userData); + if (eventHandler && eventHandler->mouseMovedExitedHandler_) { + eventHandler->mouseMovedExitedHandler_(MouseEvent(x, y, buttonNumber, modifierFlags)); + } + } static void mouseDraggedCallback(double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData) { auto* eventHandler = static_cast(userData); @@ -911,6 +950,16 @@ namespace olc { scrollWheelHandler_ = std::move(handler); window_setScrollWheelCallback(window_.getCHandle(), scrollWheelCallback, this); } + + void onMouseEnteredWindow(std::function handler) { + mouseMovedEnteredHandler_ = std::move(handler); + window_setMouseEnteredCallback(window_.getCHandle(), mouseEnteredCallback, this); + } + + void onMouseExitWindow(std::function handler) { + mouseMovedExitedHandler_ = std::move(handler); + window_setMouseExitedCallback(window_.getCHandle(), mouseExitedCallback, this); + } // Enable/disable event handling void enable() noexcept { diff --git a/dev/src/host_apple_macos.cpp b/dev/src/host_apple_macos.cpp index 51869e9f..a24e5c75 100644 --- a/dev/src/host_apple_macos.cpp +++ b/dev/src/host_apple_macos.cpp @@ -137,60 +137,76 @@ namespace olc::host { } -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::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::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; -} + 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(); + std::vector Host_Apple_MacOS::GetHostWindowDescriptor(olc::Window* pWindow){ + + // Ensure OpenGL renderer is created + if(pMacOSOpenGLRenderer == nullptr) + CreateCGLContextObj(); - return vMacOSWindowDescriptors; - -} + 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) + bool Host_Apple_MacOS::SyncWithDesktopComposite() { - pMacOSOpenGLRenderer->enableVsync(); - enableVSync = true; + /* + 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::SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) + { + dispatch_sync(dispatch_get_main_queue(), ^{ + pMacOSWindow->setCursorPosition(vPos.x, vPos.y); + }); + return false; + } + + bool Host_Apple_MacOS::SetMouseVisible(olc::Window* pWindow, const bool bVisible) + { + dispatch_sync(dispatch_get_main_queue(), ^{ + pMacOSWindow->setCursorVisibility(bVisible); + }); + return true; } - - return enableVSync; -} bool Host_Apple_MacOS::OnApplicationStart(olc::PixelGameEngine* pPrimary){ pPrimaryPGE = pPrimary; @@ -198,7 +214,7 @@ bool Host_Apple_MacOS::SyncWithDesktopComposite() } bool Host_Apple_MacOS::StartSystem(){ - + // Create MacOS Application instance pMacApplication = std::make_unique(); @@ -638,7 +654,7 @@ bool Host_Apple_MacOS::SyncWithDesktopComposite() }); pMacOSEventHandler->onMouseMoved([&](const olc::apis::macos::MouseEvent& event) { - pPGEwindow->olc_OnMouseMove({static_cast(event.x), static_cast(event.y)}); + pPGEwindow->olc_OnMouseMove({static_cast(event.x), static_cast(event.y)}); }); pMacOSEventHandler->onMouseDragged([&](const olc::apis::macos::MouseEvent& event) { diff --git a/dev/src/host_apple_macos.h b/dev/src/host_apple_macos.h index 38bdf8cb..15d75c09 100644 --- a/dev/src/host_apple_macos.h +++ b/dev/src/host_apple_macos.h @@ -56,26 +56,32 @@ namespace olc // Wait for entire host desktop refresh (for smooooth vsync), virtual bool SyncWithDesktopComposite() 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; + + public: // Platform specific Mouse Control + // Force the mouse position in pixels relative to window + virtual bool SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) override; + // Show or hide mouse cursor for given window + virtual bool SetMouseVisible(olc::Window* pWindow, const bool bVisible) 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; @@ -139,7 +145,7 @@ namespace olc 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 + bool bNumLockActive = true; // Num Lock state, we assume it's active at start }; } diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index bdb3972e..3d089a6c 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -4190,6 +4190,7 @@ namespace olc #include #include #include +#include extern "C" { // NSRect (OSX rectangle structure same as GCRect C structure) @@ -4236,7 +4237,9 @@ extern "C" { void window_setWindowSize (struct Window* self, double width, double height); void window_getContentViewFrame (const struct Window* self, double* x, double* y, double* width, double* height); void window_setContentViewFrame (struct Window* self, double* x, double* y, double* width, double* height); - + void window_setCursorVisibility (struct Window* self, BOOL visible); + void window_setCursorPosition (struct Window* self, double x, double y); + // OpenGL Renderer API - as implemented in api_macos.c struct OpenGLRenderer* opengl_init (void); void opengl_initialize (struct OpenGLRenderer* self, struct Window* window); @@ -4285,6 +4288,8 @@ extern "C" { void window_setOtherMouseUpCallback (struct Window* self, MouseEventCallback callback, void* userData); void window_setOtherMouseDraggedCallback(struct Window* self, MouseEventCallback callback, void* userData); void window_setScrollWheelCallback (struct Window* self, void (*callback)(double, double, double, double, unsigned int, void*), void* userData); + void window_setMouseEnteredCallback (struct Window* self, MouseEventCallback callback, void* userData); + void window_setMouseExitedCallback (struct Window* self, MouseEventCallback callback, void* userData); // Event system management void window_enableEventHandling (struct Window* self); @@ -4757,6 +4762,29 @@ namespace olc { setCallback(window_setWindowDidDeminiaturizeCallback, std::move(callback)); } + // Set the cusror position within the window + void setCursorPosition(int32_t x, int32_t y) noexcept { + setCursorPosition(static_cast(x), static_cast(y)); + } + + void setCursorPosition(float x, float y) noexcept { + setCursorPosition(static_cast(x), static_cast(y)); + } + + void setCursorPosition(double x, double y) noexcept { + if (window_) { + window_setCursorPosition(window_, x, y); + } + } + + // Set cursor visibility + void setCursorVisibility(bool visible) noexcept { + if (window_) { + window_setCursorVisibility(window_, visible); + } + } + + // Non-copyable but movable Window(const Window&) = delete; Window& operator=(const Window&) = delete; @@ -5027,6 +5055,8 @@ namespace olc { std::function otherMouseUpHandler_; std::function otherMouseDraggedHandler_; std::function scrollWheelHandler_; + std::function mouseMovedEnteredHandler_; + std::function mouseMovedExitedHandler_; // Template helpers for static callbacks to reduce code duplication template @@ -5080,6 +5110,20 @@ namespace olc { eventHandler->mouseMovedHandler_(MouseEvent(x, y, buttonNumber, modifierFlags)); } } + + static void mouseEnteredCallback(double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData) { + auto* eventHandler = static_cast(userData); + if (eventHandler && eventHandler->mouseMovedEnteredHandler_) { + eventHandler->mouseMovedEnteredHandler_(MouseEvent(x, y, buttonNumber, modifierFlags)); + } + } + + static void mouseExitedCallback(double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData) { + auto* eventHandler = static_cast(userData); + if (eventHandler && eventHandler->mouseMovedExitedHandler_) { + eventHandler->mouseMovedExitedHandler_(MouseEvent(x, y, buttonNumber, modifierFlags)); + } + } static void mouseDraggedCallback(double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData) { auto* eventHandler = static_cast(userData); @@ -5209,6 +5253,16 @@ namespace olc { scrollWheelHandler_ = std::move(handler); window_setScrollWheelCallback(window_.getCHandle(), scrollWheelCallback, this); } + + void onMouseEnteredWindow(std::function handler) { + mouseMovedEnteredHandler_ = std::move(handler); + window_setMouseEnteredCallback(window_.getCHandle(), mouseEnteredCallback, this); + } + + void onMouseExitWindow(std::function handler) { + mouseMovedExitedHandler_ = std::move(handler); + window_setMouseExitedCallback(window_.getCHandle(), mouseExitedCallback, this); + } // Enable/disable event handling void enable() noexcept { @@ -5261,26 +5315,32 @@ namespace olc // Wait for entire host desktop refresh (for smooooth vsync), virtual bool SyncWithDesktopComposite() 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; + + public: // Platform specific Mouse Control + // Force the mouse position in pixels relative to window + virtual bool SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) override; + // Show or hide mouse cursor for given window + virtual bool SetMouseVisible(olc::Window* pWindow, const bool bVisible) 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; @@ -5344,7 +5404,7 @@ namespace olc 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 + bool bNumLockActive = true; // Num Lock state, we assume it's active at start }; } @@ -7319,60 +7379,76 @@ namespace olc::host { } -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); + 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; -} + 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::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; -} + 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(); + std::vector Host_Apple_MacOS::GetHostWindowDescriptor(olc::Window* pWindow){ + + // Ensure OpenGL renderer is created + if(pMacOSOpenGLRenderer == nullptr) + CreateCGLContextObj(); - return vMacOSWindowDescriptors; - -} + 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) + bool Host_Apple_MacOS::SyncWithDesktopComposite() { - pMacOSOpenGLRenderer->enableVsync(); - enableVSync = true; + /* + 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::SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) + { + dispatch_sync(dispatch_get_main_queue(), ^{ + pMacOSWindow->setCursorPosition(vPos.x, vPos.y); + }); + return false; + } + + bool Host_Apple_MacOS::SetMouseVisible(olc::Window* pWindow, const bool bVisible) + { + dispatch_sync(dispatch_get_main_queue(), ^{ + pMacOSWindow->setCursorVisibility(bVisible); + }); + return true; } - - return enableVSync; -} bool Host_Apple_MacOS::OnApplicationStart(olc::PixelGameEngine* pPrimary){ pPrimaryPGE = pPrimary; @@ -7380,7 +7456,7 @@ bool Host_Apple_MacOS::SyncWithDesktopComposite() } bool Host_Apple_MacOS::StartSystem(){ - + // Create MacOS Application instance pMacApplication = std::make_unique(); @@ -7820,7 +7896,7 @@ bool Host_Apple_MacOS::SyncWithDesktopComposite() }); pMacOSEventHandler->onMouseMoved([&](const olc::apis::macos::MouseEvent& event) { - pPGEwindow->olc_OnMouseMove({static_cast(event.x), static_cast(event.y)}); + pPGEwindow->olc_OnMouseMove({static_cast(event.x), static_cast(event.y)}); }); pMacOSEventHandler->onMouseDragged([&](const olc::apis::macos::MouseEvent& event) { @@ -7880,6 +7956,15 @@ static constexpr const char* kWindowDelegateClass = "WindowDelegat static constexpr const char* kCustomOpenGLViewClass = "CustomOpenGLView"; static constexpr const char* kGeneralWindowDelegateClass = "GeneralWindowDelegate"; +// Application Screen management selectors +static constexpr const char* kNSScreenClass = "NSScreen"; +static constexpr const char* kScreenSel = "screen"; +static constexpr const char* kNSCursorClass = "NSCursor"; +static constexpr const char* kUnhideSel = "unhide"; +static constexpr const char* kHideSel = "hide"; +static constexpr const char* kIsHiddenSel = "isHidden"; + + // Application memory management selectors static constexpr const char* kAllocSel = "alloc"; static constexpr const char* kInitSel = "init"; @@ -7939,6 +8024,13 @@ static constexpr const char* kOtherMouseDraggedSel = "otherMouseDra static constexpr const char* kScrollWheelSel = "scrollWheel:"; static constexpr const char* kDeltaXSel = "deltaX"; static constexpr const char* kDeltaYSel = "deltaY"; +static constexpr const char* kUpdateTrackingAreasSel = "updateTrackingAreas"; +static constexpr const char* kMouseEnteredSel = "mouseEntered:"; +static constexpr const char* kMouseExitedSel = "mouseExited:"; +static constexpr const char* kTrackingAreaClass = "NSTrackingArea"; +static constexpr const char* kAddTrackingAreaSel = "addTrackingArea:"; +static constexpr const char* kInitWithRectSel = "initWithRect:options:owner:userInfo:"; + // Managing first responder status and keyboard focus selectors static constexpr const char* kAcceptsFirstResponderSel = "acceptsFirstResponder"; @@ -8005,7 +8097,9 @@ static constexpr int kZeroWidth = 0; static constexpr int kZeroHeight = 0; static constexpr int kFlippedOffset = 1; static constexpr int kNoButton = -1; - +static constexpr int kDefaultScreenNumber = 1; +bool bAllowHideCursor = true; +bool bHideCursor = false; // Objective-C method type encoding constants // Type encoding for methods returning BOOL with no parameters: "c@:" @@ -8036,6 +8130,12 @@ namespace ObjectiveCSEL { static SEL runSel = nullptr; static SEL terminateSEL = nullptr; + // Application Screen management selectors + static SEL screenSel = nullptr; + static SEL unhideSel = nullptr; + static SEL hideSel = nullptr; + static SEL isHiddenSel = nullptr; + // NSApplicationDelegate lifecycle methods static SEL applicationWillFinishLaunchingSel = nullptr; static SEL applicationDidFinishLaunchingSel = nullptr; @@ -8065,22 +8165,29 @@ namespace ObjectiveCSEL { static SEL windowDidDeminiaturizeSel = nullptr; // 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; - 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; + static SEL keyDownSel = nullptr; + static SEL keyUpSel = nullptr; + static SEL flagsChangedSel = 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; + static SEL updateTrackingAreasSel = nullptr; + static SEL mouseEnteredSel = nullptr; + static SEL mouseExitedSel = nullptr; + + // Mouse Tracking + static SEL addTrackingAreaSel = nullptr; + static SEL initWithRectSel = nullptr; // Managing first responder status and keyboard focus selectors static SEL acceptsFirstResponderSel = nullptr; @@ -8150,6 +8257,12 @@ namespace ObjectiveCSEL { setActivationPolicySel = sel_registerName(kSetActivationPolicySel); runSel = sel_registerName(kRunSel); terminateSEL = sel_registerName(kTerminateSel); + + // Application Screen management selectors + screenSel = sel_registerName(kScreenSel); + unhideSel = sel_registerName(kUnhideSel); + hideSel = sel_registerName(kHideSel); + isHiddenSel = sel_registerName(kIsHiddenSel); // NSApplicationDelegate lifecycle methods applicationWillFinishLaunchingSel = sel_registerName(kApplicationWillFinishLaunchingSel); @@ -8196,7 +8309,12 @@ namespace ObjectiveCSEL { scrollWheelSel = sel_registerName(kScrollWheelSel); deltaXSel = sel_registerName(kDeltaXSel); deltaYSel = sel_registerName(kDeltaYSel); - + updateTrackingAreasSel = sel_registerName(kUpdateTrackingAreasSel); + mouseEnteredSel = sel_registerName(kMouseEnteredSel); + mouseExitedSel = sel_registerName(kMouseExitedSel); + addTrackingAreaSel = sel_registerName(kAddTrackingAreaSel); + initWithRectSel = sel_registerName(kInitWithRectSel); + // Managing first responder status and keyboard focus selectors acceptsFirstResponderSel = sel_registerName(kAcceptsFirstResponderSel); becomeFirstResponderSel = sel_registerName(kBecomeFirstResponderSel); @@ -8289,14 +8407,6 @@ class AutoreleasePool { void* pool_; }; -// CGPoint structure for 2D points -struct CGPoint { - double x{kMinValidDimension}; - double y{kMinValidDimension}; - - constexpr CGPoint() = default; - constexpr CGPoint(double x_val, double y_val) noexcept : x(x_val), y(y_val) {} -}; using NSPoint = CGPoint; using NSInteger = long; @@ -8442,6 +8552,11 @@ enum class NSApplicationActivationPolicy : uint8_t { // Backward compatibility static constexpr int NSApplicationActivationPolicyRegular = static_cast(NSApplicationActivationPolicy::Regular); +// Mouse Tracking const +static constexpr const NSUInteger NSTrackingMouseEnteredAndExited = 0x01; +static constexpr const NSUInteger NSTrackingActiveInKeyWindow = 0x20; +static constexpr const NSUInteger NSTrackingInVisibleRect = 0x200; + /* * WARNING: Global delegate pointers for Objective-C callbacks * These global pointers are used to route Objective-C delegate callbacks @@ -8535,7 +8650,11 @@ struct Window { void (*otherMouseDownCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; void (*otherMouseUpCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; void (*otherMouseDraggedCallback)(double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; + void (*mouseEnteredCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; + void (*mouseExitedCallback) (double x, double y, int buttonNumber, unsigned int modifierFlags, void* userData){nullptr}; void (*scrollWheelCallback) (double x, double y, double deltaX, double deltaY, unsigned int modifierFlags, void* userData){nullptr}; + + void* eventUserData{nullptr}; // User data for event callbacks BOOL acceptsInputEvents{NO}; // Whether the window accepts input events @@ -8587,7 +8706,7 @@ struct OpenGLRenderer { void (*renderYellowBackground)(struct OpenGLRenderer* self){nullptr}; void (*renderTexturedQuad) (struct OpenGLRenderer* self, unsigned int textureID){nullptr}; void* (*getOpenGLContext) (const struct OpenGLRenderer* self){nullptr}; - void* (*getCGLContextObj)(struct OpenGLRenderer* self){nullptr}; + void* (*getCGLContextObj) (struct OpenGLRenderer* self){nullptr}; void* (*getCGLContextObjPtr) (struct OpenGLRenderer* self){nullptr}; void (*makeCurrentContext) (struct OpenGLRenderer* self){nullptr}; void (*setVsync) (struct OpenGLRenderer* self, BOOL enabled){nullptr}; @@ -8781,7 +8900,6 @@ MouseEventData extractMouseEventData(id event) { // Convert to content view coordinates and flip Y coordinate convertToContentViewCoordinates(data.location); - return data; } @@ -8909,6 +9027,61 @@ void view_scrollWheel(id self, SEL _cmd, id event) { } } +// New events for mouse entered/exited +void view_mouseEntered(id self, SEL _cmd, id event) { + (void)self;(void)_cmd;(void)event; + + // Allow hiding cursor when mouse is inside window + bAllowHideCursor = true; + + // If the cursor is currently hidden, hide it again to ensure it stays hidden while inside the window + if(bHideCursor) + ((void (*)(Class, SEL))objc_msgSend)(objc_getClass(kNSCursorClass), ObjectiveCSEL::hideSel); + + NSPoint location = ((NSPoint(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::locationInWindowSel); + NSUInteger modifierFlags = ((NSUInteger(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); + + convertToContentViewCoordinates(location); + + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseEnteredCallback) [[likely]] { + gptrNSWindowEvents->mouseEnteredCallback(location.x, location.y, kNoButton, (unsigned int)modifierFlags, gptrNSWindowEvents->eventUserData); + } +} + +void view_mouseExited(id self, SEL _cmd, id event) { + (void)self;(void)_cmd;(void)event; + + // Ensure cursor is visible when leaving window + bAllowHideCursor = false; + ((void (*)(Class, SEL))objc_msgSend)(objc_getClass(kNSCursorClass), ObjectiveCSEL::unhideSel); + + NSPoint location = ((NSPoint(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::locationInWindowSel); + NSUInteger modifierFlags = ((NSUInteger(*)(id, SEL))objc_msgSend)(event, ObjectiveCSEL::modifierFlagsSel); + + convertToContentViewCoordinates(location); + + if (gptrNSWindowEvents && gptrNSWindowEvents->acceptsInputEvents && gptrNSWindowEvents->mouseExitedCallback) [[likely]] { + gptrNSWindowEvents->mouseExitedCallback(location.x, location.y, kNoButton, (unsigned int)modifierFlags, gptrNSWindowEvents->eventUserData); + } +} + +void view_updateTrackingAreas(id self, SEL _cmd) { + (void)self;(void)_cmd; + + // Create new tracking area + NSRect bounds = ((NSRect(*)(id, SEL))objc_msgSend)(self, ObjectiveCSEL::boundsSel); + + // Correct tracking options: mouse entered/exited + active in key window + NSUInteger options = NSTrackingMouseEnteredAndExited | NSTrackingActiveInKeyWindow | NSTrackingInVisibleRect; + + Class NSTrackingAreaClass = objc_getClass(kTrackingAreaClass); + id trackingArea = ((id(*)(id, SEL, NSRect, NSUInteger, id, id))objc_msgSend)( + ((id(*)(Class, SEL))objc_msgSend)(NSTrackingAreaClass, ObjectiveCSEL::allocSel), + ObjectiveCSEL::initWithRectSel, bounds, options, self, nil); + + ((void(*)(id, SEL, id))objc_msgSend)(self, ObjectiveCSEL::addTrackingAreaSel, trackingArea); +} + // Determine if view can accept first responder status id view_acceptsFirstResponder(id self, SEL _cmd) { (void)self;(void)_cmd; @@ -8984,7 +9157,12 @@ Class createCustomOpenGLViewClass() { class_addMethod(CustomViewClass, ObjectiveCSEL::otherMouseUpSel, (IMP)view_otherMouseUp, kEventHandlerMethodTypeEncoding); class_addMethod(CustomViewClass, ObjectiveCSEL::otherMouseDraggedSel, (IMP)view_otherMouseDragged, kEventHandlerMethodTypeEncoding); class_addMethod(CustomViewClass, ObjectiveCSEL::scrollWheelSel, (IMP)view_scrollWheel, kEventHandlerMethodTypeEncoding); - + + // Area tracking for Mouse entered/exited event handler methods + class_addMethod(CustomViewClass, ObjectiveCSEL::updateTrackingAreasSel, (IMP)view_updateTrackingAreas, kVoidMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::mouseEnteredSel, (IMP)view_mouseEntered, kEventHandlerMethodTypeEncoding); + class_addMethod(CustomViewClass, ObjectiveCSEL::mouseExitedSel, (IMP)view_mouseExited, kEventHandlerMethodTypeEncoding); + // First responder methods class_addMethod(CustomViewClass, ObjectiveCSEL::acceptsFirstResponderSel, (IMP)view_acceptsFirstResponder, kBoolMethodTypeEncoding); class_addMethod(CustomViewClass, ObjectiveCSEL::becomeFirstResponderSel, (IMP)view_becomeFirstResponder, kBoolMethodTypeEncoding); @@ -9314,8 +9492,7 @@ extern "C" { // Window title setter void window_setTitle(Window* self, const char* title) { self->title = title; - - // If window is already created, update the NSWindow title + if (self->nsWindow) { Class NSStringClass = objc_getClass(kNSStringClass); @@ -9435,6 +9612,46 @@ extern "C" { window_setWindowFrame(self, self->windowFrame.x, self->windowFrame.y, width, height); } + // Set the cusor position relative to the content view + void window_setCursorPosition(Window* self, double x, double y) { + if (!self || !self->nsWindow) return; + + // Get current screen the window is on + id currentScreen = ((id(*)(id, SEL))objc_msgSend)(self->nsWindow, ObjectiveCSEL::screenSel); + NSRect screenFrame = ((NSRect(*)(id, SEL))objc_msgSend)(currentScreen, ObjectiveCSEL::frameSel); + + // Update internal frame representation to get the latest window position + window_updateFrameFromOSX(self); + NSPoint location = {self->windowFrame.x, self->windowFrame.y}; + + // Get the content view + id contentView = ((id(*)(id, SEL))objc_msgSend)(gptrNSWindowEvents->nsWindow, ObjectiveCSEL::contentViewSel); + if (contentView) { + + // Get the content view bounds to flip Y coordinate + NSRect contentBounds = ((NSRect(*)(id, SEL))objc_msgSend)(contentView, ObjectiveCSEL::boundsSel); + + // NOTE: we need to ensure the new cursor position is within the window bounds to prevent unexpected behavior + auto posX = std::clamp(location.x +x, location.x, location.x + contentBounds.width); + auto posY = std::clamp(screenFrame.height - location.y - contentBounds.height + y, + screenFrame.height - location.y - contentBounds.height, + screenFrame.height - location.y); + CGWarpMouseCursorPosition(CGPointMake(posX, posY)); + } + + } + + // Set cursor visibility + void window_setCursorVisibility(Window* self, BOOL visible) { + if (!self || !self->nsWindow) return; + bHideCursor = !visible; + if (bHideCursor && bAllowHideCursor) { + ((void (*)(Class, SEL))objc_msgSend)(objc_getClass(kNSCursorClass), ObjectiveCSEL::hideSel); + } else { + ((void (*)(Class, SEL))objc_msgSend)(objc_getClass(kNSCursorClass), ObjectiveCSEL::unhideSel); + } + } + // Initialize OpenGL renderer void opengl_initialize(OpenGLRenderer* self, Window* window) { self->window = window; @@ -9934,6 +10151,16 @@ extern "C" { self->eventUserData = userData; } + void window_setMouseEnteredCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->mouseEnteredCallback = callback; + self->eventUserData = userData; + } + + void window_setMouseExitedCallback(Window* self, void (*callback)(double, double, int, unsigned int, void*), void* userData) { + self->mouseExitedCallback = callback; + self->eventUserData = userData; + } + void window_enableEventHandling(Window* self) { if (self) { self->acceptsInputEvents = YES; From f216026af79d58da465fbed3a9eafb7cca8997cd Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Thu, 19 Feb 2026 18:21:15 -0500 Subject: [PATCH 062/101] [x11] show/hide mouse cursor --- dev/src/host_lin_x11.cpp | 43 +++++++++++++++++++++++++++++++- dev/src/host_lin_x11.h | 10 ++++++++ olcPixelGameEngine3.h | 53 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 104 insertions(+), 2 deletions(-) diff --git a/dev/src/host_lin_x11.cpp b/dev/src/host_lin_x11.cpp index 2ad72944..74caaa76 100644 --- a/dev/src/host_lin_x11.cpp +++ b/dev/src/host_lin_x11.cpp @@ -309,13 +309,26 @@ namespace olc::host mapUID2X11Window.insert_or_assign(pWindow->GetUID(), olc_Window); mapX11Window2PTR.insert_or_assign(olc_Window, pWindow); + // Create invisible cursor + char data[1] = {0}; + X11::Pixmap blank = XCreateBitmapFromData(olc_Display, olc_Window, data, 1, 1); + X11::XColor dummy = {0}; + X11::Cursor cursor = XCreatePixmapCursor(olc_Display, blank, blank, &dummy, &dummy, 0, 0); + XFreePixmap(olc_Display, blank); + + // Add invisible cursor for this window + mapUID2X11Cursor.insert_or_assign(pWindow->GetUID(), cursor); + return true; } bool Host_Linux_X11::CloseWindowFrame(olc::Window* pWindow) { const auto window_handle = mapUID2X11Window.find(pWindow->GetUID()); - if (window_handle != mapUID2X11Window.end()) { + const auto invisible_cursor = mapUID2X11Cursor.find(pWindow->GetUID()); + + if (window_handle != mapUID2X11Window.end() && invisible_cursor != mapUID2X11Cursor.end()) { + X11::XFreeCursor(olc_Display, invisible_cursor->second); X11::XDestroyWindow(olc_Display, window_handle->second); mapUID2X11Window.erase(window_handle); } @@ -404,5 +417,33 @@ namespace olc::host { return true; } + + bool Host_Linux_X11::SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) + { + return false; + } + + bool Host_Linux_X11::SetMouseVisible(olc::Window* pWindow, const bool bVisible) + { + // nothing to change, do nothing + if(bMouseIsVisible == bVisible) + return true; + + bMouseIsVisible = bVisible; + + auto win = mapUID2X11Window.at(pWindow->GetUID()); + auto cursor = mapUID2X11Cursor.at(pWindow->GetUID()); + + if(bMouseIsVisible) + { + X11::XUndefineCursor(olc_Display, win); + return true; + } + + X11::XDefineCursor(olc_Display, win, cursor); + return true; + } + + } //! END IMPLEMENTATION diff --git a/dev/src/host_lin_x11.h b/dev/src/host_lin_x11.h index 4af2ff65..0d39373f 100644 --- a/dev/src/host_lin_x11.h +++ b/dev/src/host_lin_x11.h @@ -20,6 +20,7 @@ namespace X11 #include #include #include +#include #include #undef None constexpr int None = 0L; @@ -50,6 +51,11 @@ namespace olc::host // Wait for entire host desktop refresh (for smooooth vsync) bool SyncWithDesktopComposite() override; + // Force the mouse position in pixels relative to window + bool SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) override; + // Show or hide mouse cursor for given window + bool SetMouseVisible(olc::Window* pWindow, const bool bVisible) override; + public: bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; bool StartSystem() override; @@ -72,6 +78,10 @@ namespace olc::host bool kbExtensionsFound = false; int xkbEventBase = 0; int xkbErrorBase = 0; + + // Mouse Variables + std::unordered_map mapUID2X11Cursor; + bool bMouseIsVisible = true; }; } diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index bdb3972e..beff48d6 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -5360,6 +5360,7 @@ namespace X11 #include #include #include +#include #include #undef None constexpr int None = 0L; @@ -5390,6 +5391,11 @@ namespace olc::host // Wait for entire host desktop refresh (for smooooth vsync) bool SyncWithDesktopComposite() override; + // Force the mouse position in pixels relative to window + bool SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) override; + // Show or hide mouse cursor for given window + bool SetMouseVisible(olc::Window* pWindow, const bool bVisible) override; + public: bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; bool StartSystem() override; @@ -5412,6 +5418,10 @@ namespace olc::host bool kbExtensionsFound = false; int xkbEventBase = 0; int xkbErrorBase = 0; + + // Mouse Variables + std::unordered_map mapUID2X11Cursor; + bool bMouseIsVisible = true; }; } @@ -10322,13 +10332,26 @@ namespace olc::host mapUID2X11Window.insert_or_assign(pWindow->GetUID(), olc_Window); mapX11Window2PTR.insert_or_assign(olc_Window, pWindow); + // Create invisible cursor + char data[1] = {0}; + X11::Pixmap blank = XCreateBitmapFromData(olc_Display, olc_Window, data, 1, 1); + X11::XColor dummy = {0}; + X11::Cursor cursor = XCreatePixmapCursor(olc_Display, blank, blank, &dummy, &dummy, 0, 0); + XFreePixmap(olc_Display, blank); + + // Add invisible cursor for this window + mapUID2X11Cursor.insert_or_assign(pWindow->GetUID(), cursor); + return true; } bool Host_Linux_X11::CloseWindowFrame(olc::Window* pWindow) { const auto window_handle = mapUID2X11Window.find(pWindow->GetUID()); - if (window_handle != mapUID2X11Window.end()) { + const auto invisible_cursor = mapUID2X11Cursor.find(pWindow->GetUID()); + + if (window_handle != mapUID2X11Window.end() && invisible_cursor != mapUID2X11Cursor.end()) { + X11::XFreeCursor(olc_Display, invisible_cursor->second); X11::XDestroyWindow(olc_Display, window_handle->second); mapUID2X11Window.erase(window_handle); } @@ -10417,6 +10440,34 @@ namespace olc::host { return true; } + + bool Host_Linux_X11::SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) + { + return false; + } + + bool Host_Linux_X11::SetMouseVisible(olc::Window* pWindow, const bool bVisible) + { + // nothing to change, do nothing + if(bMouseIsVisible == bVisible) + return true; + + bMouseIsVisible = bVisible; + + auto win = mapUID2X11Window.at(pWindow->GetUID()); + auto cursor = mapUID2X11Cursor.at(pWindow->GetUID()); + + if(bMouseIsVisible) + { + X11::XDefineCursor(olc_Display, win, X11::None); + return true; + } + + X11::XDefineCursor(olc_Display, win, cursor); + return true; + } + + } #endif From 089178146432d07e64836fd25cf46ab44f37c0a9 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Thu, 19 Feb 2026 19:44:36 -0500 Subject: [PATCH 063/101] [x11] add pointer grab/warping --- dev/src/host_lin_x11.cpp | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/dev/src/host_lin_x11.cpp b/dev/src/host_lin_x11.cpp index 74caaa76..dce60163 100644 --- a/dev/src/host_lin_x11.cpp +++ b/dev/src/host_lin_x11.cpp @@ -226,13 +226,14 @@ namespace olc::host } else if (xev.type == FocusIn) { - if(auto* pge_window = get_pge_window(xev.xbutton.window); pge_window) { + + if(auto* pge_window = get_pge_window(xev.xfocus.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) { + if(auto* pge_window = get_pge_window(xev.xfocus.window); pge_window) { pge_window->olc_OnMouseFocus(false); } } @@ -420,11 +421,31 @@ namespace olc::host bool Host_Linux_X11::SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) { - return false; + auto win = mapUID2X11Window.at(pWindow->GetUID()); + + // NOTE: xwayland will only allow warping when we have an active grab on a + // hidden mouse cursor. + + X11::XGrabPointer( + olc_Display, win, True, + ButtonPressMask | ButtonReleaseMask | PointerMotionMask, + GrabModeAsync, GrabModeAsync, + win, X11::None, CurrentTime + ); + + X11::XWarpPointer(olc_Display, X11::None, win, 0, 0, 0, 0, vPos.x, vPos.y); + X11::XFlush(olc_Display); + + X11::XUngrabPointer(olc_Display, CurrentTime); + X11::XFlush(olc_Display); + + return true; } bool Host_Linux_X11::SetMouseVisible(olc::Window* pWindow, const bool bVisible) { + // NOTE: works on X11 and Xwayland, but does not work correctly in WSL2 + // nothing to change, do nothing if(bMouseIsVisible == bVisible) return true; From a31f328aebe2e78bbb76c13ebeaf1c9dd864f04d Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Sat, 21 Feb 2026 02:19:43 -0500 Subject: [PATCH 064/101] [wayland[ define WL_KEYBOARD_STATE_REPEATED if not defined --- dev/src/host_lin_wayland.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/dev/src/host_lin_wayland.h b/dev/src/host_lin_wayland.h index 0b343870..e57ac6e5 100644 --- a/dev/src/host_lin_wayland.h +++ b/dev/src/host_lin_wayland.h @@ -29,6 +29,10 @@ #include #include +#ifndef WL_KEYBOARD_KEY_STATE_REPEATED +#define WL_KEYBOARD_KEY_STATE_REPEATED 2 +#endif + namespace olc::host { struct WaylandWindow { From 1459a076166c95c719e58d04513847955b611b86 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Sat, 21 Feb 2026 02:20:24 -0500 Subject: [PATCH 065/101] [wayland] allow wayland keyboard version to be detected entirely at runtime --- dev/src/host_lin_wayland.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/dev/src/host_lin_wayland.cpp b/dev/src/host_lin_wayland.cpp index cc67dcf2..488c49fc 100644 --- a/dev/src/host_lin_wayland.cpp +++ b/dev/src/host_lin_wayland.cpp @@ -679,7 +679,6 @@ 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) { @@ -695,7 +694,6 @@ 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 3caaa81c9cae72d9c1d909deab2c76c11ecfffde Mon Sep 17 00:00:00 2001 From: dandistine <34634876+dandistine@users.noreply.github.com> Date: Sun, 22 Feb 2026 09:19:01 -0600 Subject: [PATCH 066/101] Implement showing / hiding the cursor on wayland --- dev/src/host_lin_wayland.cpp | 25 ++++++++++++++- dev/src/host_lin_wayland.h | 4 +++ examples/CMakeLists.txt | 46 ++++++++++++++++++++++++++++ olcPixelGameEngine3.h | 59 ++++++++++++++++++++++++++++++++---- 4 files changed, 127 insertions(+), 7 deletions(-) diff --git a/dev/src/host_lin_wayland.cpp b/dev/src/host_lin_wayland.cpp index c91245e1..8c61b54c 100644 --- a/dev/src/host_lin_wayland.cpp +++ b/dev/src/host_lin_wayland.cpp @@ -337,7 +337,18 @@ namespace olc::host bool Host_Linux_Wayland::SetMouseVisible(olc::Window* pWindow, const bool bVisible) { - return false; + auto itr = mapUID2Window.find(pWindow->GetUID()); + if(itr != mapUID2Window.end()) { + itr->second.cursor_visible = bVisible; + + if(bVisible) { + wp_cursor_shape_device_v1_set_shape(cursor_shape_device, enter_serial, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT); + } else { + wl_pointer_set_cursor(pointer, enter_serial, nullptr, 0, 0); + } + } + + return true; } @@ -361,6 +372,9 @@ namespace olc::host if(std::strcmp(interface, wp_pointer_warp_v1_interface.name) == 0) { pointer_warp = static_cast(wl_registry_bind(registry, name, &wp_pointer_warp_v1_interface, version)); } + if(std::strcmp(interface, wp_cursor_shape_manager_v1_interface.name) == 0) { + cursor_shape_manager = static_cast(wl_registry_bind(registry, name, &wp_cursor_shape_manager_v1_interface, version)); + } } void Host_Linux_Wayland::registry_handle_global_remove(wl_registry* registry, uint32_t name) @@ -372,6 +386,7 @@ namespace olc::host { if (capabilities & WL_SEAT_CAPABILITY_POINTER && pointer == nullptr) { pointer = wl_seat_get_pointer(seat); + cursor_shape_device = wp_cursor_shape_manager_v1_get_pointer(cursor_shape_manager, pointer); wl_pointer_add_listener(pointer, &wayland::pointer_listener, this); } @@ -462,6 +477,7 @@ namespace olc::host pointer_state.serial = serial; // Save so we can reuse the serial for pointer warping enter_serial = serial; + pointer_state.surface = surface; pointer_state.surface_x = surface_x; pointer_state.surface_y = surface_y; } @@ -537,6 +553,13 @@ namespace olc::host for(auto& itr : mapUID2Window) { if (itr.second.surface == event->surface) { pointer_window = itr.first; + + // Need to set the mouse back to the correct hidden / not hidden state when it enters the window + if(itr.second.cursor_visible) { + wp_cursor_shape_device_v1_set_shape(cursor_shape_device, enter_serial, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT); + } else { + wl_pointer_set_cursor(pointer, enter_serial, nullptr, 0, 0); + } } } } diff --git a/dev/src/host_lin_wayland.h b/dev/src/host_lin_wayland.h index 460c2caa..c916a953 100644 --- a/dev/src/host_lin_wayland.h +++ b/dev/src/host_lin_wayland.h @@ -21,6 +21,7 @@ #include "xdg-shell.h" #include "xdg-decoration.h" #include "pointer-warp.h" +#include "cursor-shape.h" #include #include #include @@ -41,6 +42,7 @@ namespace olc::host size_t olc_window_uid{0}; int32_t bounds_x{0}; int32_t bounds_y{0}; + bool cursor_visible{true}; }; namespace wayland { @@ -94,6 +96,8 @@ namespace olc::host zxdg_decoration_manager_v1* decoration_manager{nullptr}; wp_pointer_warp_v1* pointer_warp{nullptr}; uint32_t enter_serial{0}; + wp_cursor_shape_device_v1* cursor_shape_device{nullptr}; + wp_cursor_shape_manager_v1* cursor_shape_manager{nullptr}; wayland::PointerState pointer_state; diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 4646ac3d..34c7227d 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -37,6 +37,8 @@ if(UNIX AND BUILD_WAYLAND) 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") set(POINTER_WARP_PROTOCOL "/usr/share/wayland-protocols/staging/pointer-warp/pointer-warp-v1.xml") + set(CURSOR_SHAPE_PROTOCOL "/usr/share/wayland-protocols/staging/cursor-shape/cursor-shape-v1.xml") + set(TABLET_PROTOCOL "/usr/share/wayland-protocols/stable/tablet/tablet-v2.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") @@ -50,6 +52,14 @@ if(UNIX AND BUILD_WAYLAND) message(FATAL_ERROR "pointer-warp protocol not found. You may need to install wayland-protocols via your package manager") endif() + if(NOT EXISTS ${CURSOR_SHAPE_PROTOCOL}) + message(FATAL_ERROR "cursor-shape protocol not found. You may need to install wayland-protocols via your package manager") + endif() + + if(NOT EXISTS ${TABLET_PROTOCOL}) + message(FATAL_ERROR "tablet 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) @@ -57,6 +67,10 @@ if(UNIX AND BUILD_WAYLAND) set(XDG_DECORATION_H ${CMAKE_CURRENT_BINARY_DIR}/xdg-decoration.h) set(POINTER_WARP_C ${CMAKE_CURRENT_BINARY_DIR}/pointer-warp.c) set(POINTER_WARP_H ${CMAKE_CURRENT_BINARY_DIR}/pointer-warp.h) + set(CURSOR_SHAPE_C ${CMAKE_CURRENT_BINARY_DIR}/cursor-shape.c) + set(CURSOR_SHAPE_H ${CMAKE_CURRENT_BINARY_DIR}/cursor-shape.h) + set(TABLET_C ${CMAKE_CURRENT_BINARY_DIR}/tablet.c) + set(TABLET_H ${CMAKE_CURRENT_BINARY_DIR}/tablet.h) add_custom_command( OUTPUT ${XDG_SHELL_C} @@ -100,6 +114,34 @@ if(UNIX AND BUILD_WAYLAND) VERBATIM ) + add_custom_command( + OUTPUT ${CURSOR_SHAPE_C} + COMMAND ${WAYLAND_SCANNER} private-code ${CURSOR_SHAPE_PROTOCOL} ${CURSOR_SHAPE_C} + DEPENDS ${CURSOR_SHAPE_PROTOCOL} + VERBATIM + ) + + add_custom_command( + OUTPUT ${CURSOR_SHAPE_H} + COMMAND ${WAYLAND_SCANNER} client-header ${CURSOR_SHAPE_PROTOCOL} ${CURSOR_SHAPE_H} + DEPENDS ${CURSOR_SHAPE_PROTOCOL} + VERBATIM + ) + + add_custom_command( + OUTPUT ${TABLET_C} + COMMAND ${WAYLAND_SCANNER} private-code ${TABLET_PROTOCOL} ${TABLET_C} + DEPENDS ${TABLET_PROTOCOL} + VERBATIM + ) + + add_custom_command( + OUTPUT ${TABLET_H} + COMMAND ${WAYLAND_SCANNER} client-header ${TABLET_PROTOCOL} ${TABLET_H} + DEPENDS ${TABLET_PROTOCOL} + VERBATIM + ) + # Create a library for the wayland protocol files add_library(wayland_protocols STATIC ${XDG_SHELL_C} @@ -108,6 +150,10 @@ if(UNIX AND BUILD_WAYLAND) ${XDG_DECORATION_H} ${POINTER_WARP_C} ${POINTER_WARP_H} + ${CURSOR_SHAPE_C} + ${CURSOR_SHAPE_H} + ${TABLET_C} + ${TABLET_H} ) target_include_directories(wayland_protocols PUBLIC diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index fc34ff50..d597a6ec 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -5494,6 +5494,7 @@ namespace olc::host #include "xdg-shell.h" #include "xdg-decoration.h" #include "pointer-warp.h" +#include "cursor-shape.h" #include #include #include @@ -5514,6 +5515,7 @@ namespace olc::host size_t olc_window_uid{0}; int32_t bounds_x{0}; int32_t bounds_y{0}; + bool cursor_visible{true}; }; namespace wayland { @@ -5567,6 +5569,8 @@ namespace olc::host zxdg_decoration_manager_v1* decoration_manager{nullptr}; wp_pointer_warp_v1* pointer_warp{nullptr}; uint32_t enter_serial{0}; + wp_cursor_shape_device_v1* cursor_shape_device{nullptr}; + wp_cursor_shape_manager_v1* cursor_shape_manager{nullptr}; wayland::PointerState pointer_state; @@ -10490,13 +10494,14 @@ namespace olc::host } else if (xev.type == FocusIn) { - if(auto* pge_window = get_pge_window(xev.xbutton.window); pge_window) { + + if(auto* pge_window = get_pge_window(xev.xfocus.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) { + if(auto* pge_window = get_pge_window(xev.xfocus.window); pge_window) { pge_window->olc_OnMouseFocus(false); } } @@ -10684,11 +10689,31 @@ namespace olc::host bool Host_Linux_X11::SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) { - return false; + auto win = mapUID2X11Window.at(pWindow->GetUID()); + + // NOTE: xwayland will only allow warping when we have an active grab on a + // hidden mouse cursor. + + X11::XGrabPointer( + olc_Display, win, True, + ButtonPressMask | ButtonReleaseMask | PointerMotionMask, + GrabModeAsync, GrabModeAsync, + win, X11::None, CurrentTime + ); + + X11::XWarpPointer(olc_Display, X11::None, win, 0, 0, 0, 0, vPos.x, vPos.y); + X11::XFlush(olc_Display); + + X11::XUngrabPointer(olc_Display, CurrentTime); + X11::XFlush(olc_Display); + + return true; } bool Host_Linux_X11::SetMouseVisible(olc::Window* pWindow, const bool bVisible) { + // NOTE: works on X11 and Xwayland, but does not work correctly in WSL2 + // nothing to change, do nothing if(bMouseIsVisible == bVisible) return true; @@ -10700,7 +10725,7 @@ namespace olc::host if(bMouseIsVisible) { - X11::XDefineCursor(olc_Display, win, X11::None); + X11::XUndefineCursor(olc_Display, win); return true; } @@ -11048,13 +11073,23 @@ namespace olc::host bool Host_Linux_Wayland::SetMouseVisible(olc::Window* pWindow, const bool bVisible) { - return false; + auto itr = mapUID2Window.find(pWindow->GetUID()); + if(itr != mapUID2Window.end()) { + itr->second.cursor_visible = bVisible; + + if(bVisible) { + wp_cursor_shape_device_v1_set_shape(cursor_shape_device, enter_serial, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT); + } else { + wl_pointer_set_cursor(pointer, enter_serial, nullptr, 0, 0); + } + } + + return true; } void Host_Linux_Wayland::registry_handle_global(wl_registry* registry, uint32_t name, const char* interface, uint32_t version) { - std::cout << "Global registry event: " << interface << std::endl; if(std::strcmp(interface, wl_compositor_interface.name) == 0) { compositor = static_cast(wl_registry_bind(registry, name, &wl_compositor_interface, version)); } @@ -11073,6 +11108,9 @@ namespace olc::host if(std::strcmp(interface, wp_pointer_warp_v1_interface.name) == 0) { pointer_warp = static_cast(wl_registry_bind(registry, name, &wp_pointer_warp_v1_interface, version)); } + if(std::strcmp(interface, wp_cursor_shape_manager_v1_interface.name) == 0) { + cursor_shape_manager = static_cast(wl_registry_bind(registry, name, &wp_cursor_shape_manager_v1_interface, version)); + } } void Host_Linux_Wayland::registry_handle_global_remove(wl_registry* registry, uint32_t name) @@ -11084,6 +11122,7 @@ namespace olc::host { if (capabilities & WL_SEAT_CAPABILITY_POINTER && pointer == nullptr) { pointer = wl_seat_get_pointer(seat); + cursor_shape_device = wp_cursor_shape_manager_v1_get_pointer(cursor_shape_manager, pointer); wl_pointer_add_listener(pointer, &wayland::pointer_listener, this); } @@ -11174,6 +11213,7 @@ namespace olc::host pointer_state.serial = serial; // Save so we can reuse the serial for pointer warping enter_serial = serial; + pointer_state.surface = surface; pointer_state.surface_x = surface_x; pointer_state.surface_y = surface_y; } @@ -11249,6 +11289,13 @@ namespace olc::host for(auto& itr : mapUID2Window) { if (itr.second.surface == event->surface) { pointer_window = itr.first; + + // Need to set the mouse back to the correct hidden / not hidden state when it enters the window + if(itr.second.cursor_visible) { + wp_cursor_shape_device_v1_set_shape(cursor_shape_device, enter_serial, WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT); + } else { + wl_pointer_set_cursor(pointer, enter_serial, nullptr, 0, 0); + } } } } From 1e37b0e92c64f1b35c5beb15fcb04fd41643ebef Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Sun, 22 Feb 2026 16:51:53 -0500 Subject: [PATCH 067/101] [sh] update single header --- olcPixelGameEngine3.h | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index fc34ff50..3362e1ff 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -10490,13 +10490,14 @@ namespace olc::host } else if (xev.type == FocusIn) { - if(auto* pge_window = get_pge_window(xev.xbutton.window); pge_window) { + + if(auto* pge_window = get_pge_window(xev.xfocus.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) { + if(auto* pge_window = get_pge_window(xev.xfocus.window); pge_window) { pge_window->olc_OnMouseFocus(false); } } @@ -10684,11 +10685,31 @@ namespace olc::host bool Host_Linux_X11::SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) { - return false; + auto win = mapUID2X11Window.at(pWindow->GetUID()); + + // NOTE: xwayland will only allow warping when we have an active grab on a + // hidden mouse cursor. + + X11::XGrabPointer( + olc_Display, win, True, + ButtonPressMask | ButtonReleaseMask | PointerMotionMask, + GrabModeAsync, GrabModeAsync, + win, X11::None, CurrentTime + ); + + X11::XWarpPointer(olc_Display, X11::None, win, 0, 0, 0, 0, vPos.x, vPos.y); + X11::XFlush(olc_Display); + + X11::XUngrabPointer(olc_Display, CurrentTime); + X11::XFlush(olc_Display); + + return true; } bool Host_Linux_X11::SetMouseVisible(olc::Window* pWindow, const bool bVisible) { + // NOTE: works on X11 and Xwayland, but does not work correctly in WSL2 + // nothing to change, do nothing if(bMouseIsVisible == bVisible) return true; @@ -10700,7 +10721,7 @@ namespace olc::host if(bMouseIsVisible) { - X11::XDefineCursor(olc_Display, win, X11::None); + X11::XUndefineCursor(olc_Display, win); return true; } @@ -11054,7 +11075,6 @@ namespace olc::host void Host_Linux_Wayland::registry_handle_global(wl_registry* registry, uint32_t name, const char* interface, uint32_t version) { - std::cout << "Global registry event: " << interface << std::endl; if(std::strcmp(interface, wl_compositor_interface.name) == 0) { compositor = static_cast(wl_registry_bind(registry, name, &wl_compositor_interface, version)); } From 5e177e1b4f968c5cae854da1ff1750a78d9fcc18 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Mon, 23 Feb 2026 05:23:03 -0500 Subject: [PATCH 068/101] [x11][api_opengl][gpu_opengl33] implement glXSwapIntervalEXT and use it. --- dev/src/api_opengl.cpp | 3 +++ dev/src/api_opengl.h | 8 ++++++++ dev/src/gpu_opengl33.cpp | 17 +++++++++++------ olcPixelGameEngine3.h | 28 ++++++++++++++++++++++------ 4 files changed, 44 insertions(+), 12 deletions(-) diff --git a/dev/src/api_opengl.cpp b/dev/src/api_opengl.cpp index 9f1795bc..4923ef82 100644 --- a/dev/src/api_opengl.cpp +++ b/dev/src/api_opengl.cpp @@ -85,6 +85,9 @@ namespace olc::apis::opengl bLoaded &= (_wglSwapIntervalEXT = OGL_LOAD(wglSwapIntervalEXT)) != nullptr; #endif +#if OLC_HOST == OLC_HOST_LINUX_X11 + bLoaded &= (XSwapIntervalEXT = OGL_LOAD(glXSwapIntervalEXT)) != nullptr; +#endif return bLoaded; } diff --git a/dev/src/api_opengl.h b/dev/src/api_opengl.h index 8b3f6e30..4ba39a28 100644 --- a/dev/src/api_opengl.h +++ b/dev/src/api_opengl.h @@ -183,6 +183,10 @@ namespace olc typedef void CALLSTYLE wglSwapIntervalEXT_t(GLsizei n); #endif +#if OLC_HOST == OLC_HOST_LINUX_X11 + typedef GLint CALLSTYLE glXSwapIntervalEXT_t(X11::Display* dpy, X11::GLXDrawable drawable, GLint interval); +#endif + // A little GL class (singleton) class gl { @@ -245,6 +249,10 @@ namespace olc wglSwapIntervalEXT_t* _wglSwapIntervalEXT = nullptr; #endif +#if OLC_HOST == OLC_HOST_LINUX_X11 + public: + glXSwapIntervalEXT_t* XSwapIntervalEXT = nullptr; +#endif public: // Proxies allow switchable, clutter-free error checking diff --git a/dev/src/gpu_opengl33.cpp b/dev/src/gpu_opengl33.cpp index 52efb394..873ab79c 100644 --- a/dev/src/gpu_opengl33.cpp +++ b/dev/src/gpu_opengl33.cpp @@ -352,19 +352,24 @@ void main() // Enable VSync - lock to display refresh // May also be governed by OS / driver settings // and desktop compositor settings -#if !((OLC_HOST == OLC_HOST_EMSCRIPTEN) || (OLC_HOST == OLC_HOST_LINUX_WAYLAND)) - gl.glSwapInterval(1); -#else +#if OLC_HOST == OLC_HOST_EMSCRIPTEN || OLC_HOST == OLC_HOST_LINUX_WAYLAND eglSwapInterval(glRenderContext.display, 1); +#elif OLC_HOST == OLC_HOST_LINUX_X11 + gl.XSwapIntervalEXT(display, window_handle, 1); +#else + gl.glSwapInterval(1); #endif + } else { // Disable VSync - run like the clappers! -#if !((OLC_HOST == OLC_HOST_EMSCRIPTEN) || (OLC_HOST == OLC_HOST_LINUX_WAYLAND)) - gl.glSwapInterval(0); -#else +#if OLC_HOST == OLC_HOST_EMSCRIPTEN || OLC_HOST == OLC_HOST_LINUX_WAYLAND eglSwapInterval(glRenderContext.display, 0); +#elif OLC_HOST == OLC_HOST_LINUX_X11 + gl.XSwapIntervalEXT(display, window_handle, 0); +#else + gl.glSwapInterval(0); #endif } diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 3362e1ff..4fbc47af 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -6184,6 +6184,10 @@ namespace olc typedef void CALLSTYLE wglSwapIntervalEXT_t(GLsizei n); #endif +#if OLC_HOST == OLC_HOST_LINUX_X11 + typedef GLint CALLSTYLE glXSwapIntervalEXT_t(X11::Display* dpy, X11::GLXDrawable drawable, GLint interval); +#endif + // A little GL class (singleton) class gl { @@ -6246,6 +6250,10 @@ namespace olc wglSwapIntervalEXT_t* _wglSwapIntervalEXT = nullptr; #endif +#if OLC_HOST == OLC_HOST_LINUX_X11 + public: + glXSwapIntervalEXT_t* XSwapIntervalEXT = nullptr; +#endif public: // Proxies allow switchable, clutter-free error checking @@ -12959,6 +12967,9 @@ namespace olc::apis::opengl bLoaded &= (_wglSwapIntervalEXT = OGL_LOAD(wglSwapIntervalEXT)) != nullptr; #endif +#if OLC_HOST == OLC_HOST_LINUX_X11 + bLoaded &= (XSwapIntervalEXT = OGL_LOAD(glXSwapIntervalEXT)) != nullptr; +#endif return bLoaded; } @@ -13746,19 +13757,24 @@ void main() // Enable VSync - lock to display refresh // May also be governed by OS / driver settings // and desktop compositor settings -#if !((OLC_HOST == OLC_HOST_EMSCRIPTEN) || (OLC_HOST == OLC_HOST_LINUX_WAYLAND)) - gl.glSwapInterval(1); -#else +#if OLC_HOST == OLC_HOST_EMSCRIPTEN || OLC_HOST == OLC_HOST_LINUX_WAYLAND eglSwapInterval(glRenderContext.display, 1); +#elif OLC_HOST == OLC_HOST_LINUX_X11 + gl.XSwapIntervalEXT(display, window_handle, 1); +#else + gl.glSwapInterval(1); #endif + } else { // Disable VSync - run like the clappers! -#if !((OLC_HOST == OLC_HOST_EMSCRIPTEN) || (OLC_HOST == OLC_HOST_LINUX_WAYLAND)) - gl.glSwapInterval(0); -#else +#if OLC_HOST == OLC_HOST_EMSCRIPTEN || OLC_HOST == OLC_HOST_LINUX_WAYLAND eglSwapInterval(glRenderContext.display, 0); +#elif OLC_HOST == OLC_HOST_LINUX_X11 + gl.XSwapIntervalEXT(display, window_handle, 0); +#else + gl.glSwapInterval(0); #endif } From 36781ebfb66ae65a65b2e59c06adf0e70b805788 Mon Sep 17 00:00:00 2001 From: John Galvin <96908304+Johnnyg63@users.noreply.github.com> Date: Tue, 24 Feb 2026 15:09:45 +0000 Subject: [PATCH 069/101] 198-macos-closing-a-window-when-vsync-is-disabled-causes-forever-hang --- dev/src/api_macos.cpp | 14 ++++++++++++++ olcPixelGameEngine3.h | 14 ++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/dev/src/api_macos.cpp b/dev/src/api_macos.cpp index 850ea8ec..fe6df724 100644 --- a/dev/src/api_macos.cpp +++ b/dev/src/api_macos.cpp @@ -740,6 +740,7 @@ struct Window { void (*show) (struct Window* self){nullptr}; void (*destroy) (struct Window* self){nullptr}; void (*setDelegate) (struct Window* self, id delegate){nullptr}; + void (*removeDelegate) (struct Window* self){nullptr}; const char* (*getTitle) (const struct Window* self){nullptr}; void (*setTitle) (struct Window* self, const char* title){nullptr}; @@ -1270,6 +1271,8 @@ void windowDidResize(id self, SEL _cmd, id notification) { // handle window will close events void windowWillClose(id self, SEL _cmd, id notification) { (void)self;(void)_cmd;(void)notification; + gptrWindowDelegate->acceptsInputEvents = NO; // Stop accepting input events immediately to prevent processing events for a closing window + gptrWindowDelegate->removeDelegate(gptrWindowDelegate); // remove delegate to ensure no more events are processed for this window if (gptrWindowDelegate && gptrWindowDelegate->windowWillCloseCallback) { gptrWindowDelegate->windowWillCloseCallback(gptrWindowDelegate->windowWillCloseUserData); } @@ -1546,6 +1549,16 @@ extern "C" { } } + // Removes the delegate from the window and clears the reference to prevent accidental use after close + void window_removeDelegate(Window* self) { + + if (self->nsWindow) { + ((void(*)(id, SEL, id))objc_msgSend)(self->nsWindow, ObjectiveCSEL::setDelegateSel, nil); + } + self->delegate = NULL; + + } + // Window title getter and setter const char* window_getTitle(const Window* self) { return self->title; @@ -2127,6 +2140,7 @@ extern "C" { window->show = window_show; window->destroy = window_destroy; window->setDelegate = window_setDelegate; + window->removeDelegate = window_removeDelegate; window->getTitle = window_getTitle; window->setTitle = window_setTitle; window->setWindowSize = window_setWindowSize; diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 3362e1ff..a74d5b47 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -8702,6 +8702,7 @@ struct Window { void (*show) (struct Window* self){nullptr}; void (*destroy) (struct Window* self){nullptr}; void (*setDelegate) (struct Window* self, id delegate){nullptr}; + void (*removeDelegate) (struct Window* self){nullptr}; const char* (*getTitle) (const struct Window* self){nullptr}; void (*setTitle) (struct Window* self, const char* title){nullptr}; @@ -9232,6 +9233,8 @@ void windowDidResize(id self, SEL _cmd, id notification) { // handle window will close events void windowWillClose(id self, SEL _cmd, id notification) { (void)self;(void)_cmd;(void)notification; + gptrWindowDelegate->acceptsInputEvents = NO; // Stop accepting input events immediately to prevent processing events for a closing window + gptrWindowDelegate->removeDelegate(gptrWindowDelegate); // remove delegate to ensure no more events are processed for this window if (gptrWindowDelegate && gptrWindowDelegate->windowWillCloseCallback) { gptrWindowDelegate->windowWillCloseCallback(gptrWindowDelegate->windowWillCloseUserData); } @@ -9508,6 +9511,16 @@ extern "C" { } } + // Removes the delegate from the window and clears the reference to prevent accidental use after close + void window_removeDelegate(Window* self) { + + if (self->nsWindow) { + ((void(*)(id, SEL, id))objc_msgSend)(self->nsWindow, ObjectiveCSEL::setDelegateSel, nil); + } + self->delegate = NULL; + + } + // Window title getter and setter const char* window_getTitle(const Window* self) { return self->title; @@ -10089,6 +10102,7 @@ extern "C" { window->show = window_show; window->destroy = window_destroy; window->setDelegate = window_setDelegate; + window->removeDelegate = window_removeDelegate; window->getTitle = window_getTitle; window->setTitle = window_setTitle; window->setWindowSize = window_setWindowSize; From 7336eb54101b5c27a08a66aaba6620389c7975a6 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Tue, 24 Feb 2026 15:01:17 -0500 Subject: [PATCH 070/101] [headless] add gpu_none/host_none and olcPGE3_PixelShaders-Headless example --- dev/src/config.h | 38 +- dev/src/core.cpp | 12 +- dev/src/gpu_none.cpp | 155 +++++++ dev/src/gpu_none.h | 79 ++++ dev/src/host_iface.h | 27 -- dev/src/host_none.cpp | 116 ++++++ dev/src/host_none.h | 60 +++ dev/src/sh_template.h | 21 +- examples/olcPGE3_PixelShaders-Headless.cpp | 228 ++++++++++ olcPixelGameEngine3.h | 464 +++++++++++++++++++-- 10 files changed, 1130 insertions(+), 70 deletions(-) create mode 100644 dev/src/gpu_none.cpp create mode 100644 dev/src/gpu_none.h create mode 100644 dev/src/host_none.cpp create mode 100644 dev/src/host_none.h create mode 100644 examples/olcPGE3_PixelShaders-Headless.cpp diff --git a/dev/src/config.h b/dev/src/config.h index 5b5f0b87..b193a529 100644 --- a/dev/src/config.h +++ b/dev/src/config.h @@ -26,13 +26,14 @@ // Choose "Operating System" -#define OLC_HOST_WINDOWS 1 -#define OLC_HOST_LINUX_X11 2 +#define OLC_HOST_NONE 1 +#define OLC_HOST_WINDOWS 2 #define OLC_HOST_LINUX_WAYLAND 3 -#define OLC_HOST_MACOS 4 -#define OLC_HOST_EMSCRIPTEN 5 -#define OLC_HOST_ANDROID 6 -#define OLC_HOST_IOS 7 +#define OLC_HOST_LINUX_X11 4 +#define OLC_HOST_MACOS 5 +#define OLC_HOST_EMSCRIPTEN 6 +#define OLC_HOST_ANDROID 7 +#define OLC_HOST_IOS 8 #if !defined(OLC_HOST) #if defined(_WIN32) @@ -67,10 +68,24 @@ #define OLC_GPU_NONE 1 #define OLC_GPU_OPENGL33 2 +#if defined(OLC_USE_HEADLESS) + #define OLC_GPU OLC_GPU_NONE +#endif + #if !defined(OLC_GPU) #define OLC_GPU OLC_GPU_OPENGL33 #endif +#if OLC_GPU == OLC_GPU_NONE + #define OLC_GPU_CLASS Renderer_None + #define OLC_SHADER_CLASS Shader_None +#endif + +#if OLC_GPU == OLC_GPU_OPENGL33 + #define OLC_GPU_CLASS Renderer_OGL33 + #define OLC_SHADER_CLASS Shader_GLSL33 +#endif + #define OLC_IMAGELOADER_NONE 1 #define OLC_IMAGELOADER_WINGDI 2 #define OLC_IMAGELOADER_MACOS 3 @@ -110,6 +125,13 @@ #endif #endif +// We wait until after the platform specific image loader is selected +// to lock in the headless host. +#if defined(OLC_USE_HEADLESS) + #undef OLC_HOST + #define OLC_HOST OLC_HOST_NONE +#endif + #define OLC_MULTIWINDOW_NO 1 #define OLC_MULTIWINDOW_YES 2 @@ -138,6 +160,10 @@ template inline constexpr void olc_IgnoreUnused(Args&&...) noexcept {} +#if OLC_HOST == OLC_HOST_NONE +#define OLC_FRIENDLY_HOST Host_None +#endif + #if OLC_HOST == OLC_HOST_WINDOWS #define OLC_FRIENDLY_HOST Host_Windows_WinAPI #endif diff --git a/dev/src/core.cpp b/dev/src/core.cpp index b32b8d9b..e3af3d40 100644 --- a/dev/src/core.cpp +++ b/dev/src/core.cpp @@ -1,6 +1,16 @@ #include "core.h" +#if OLC_GPU == OLC_GPU_NONE +#include "gpu_none.h" +#endif + +#if OLC_GPU == OLC_GPU_OPENGL33 #include "gpu_opengl33.h" +#endif + +#if OLC_HOST == OLC_HOST_NONE +#include "host_none.h" +#endif #if OLC_HOST == OLC_HOST_WINDOWS #include "host_win_winapi.h" @@ -404,7 +414,7 @@ namespace olc // Initialise GPU Interface olc::gpu::RendererConfig cfgRenderer; cfgRenderer.VerticalSync = config.bVSync; - gpu = std::make_unique(); + gpu = std::make_unique(); gpu->CreateDevice(host->GetHostWindowDescriptor(this), cfgRenderer); if (gpu->GetLastError() != olc::gpu::RendererError::NoError) diff --git a/dev/src/gpu_none.cpp b/dev/src/gpu_none.cpp new file mode 100644 index 00000000..2436928f --- /dev/null +++ b/dev/src/gpu_none.cpp @@ -0,0 +1,155 @@ +#include "gpu_none.h" + +//! START IMPLEMENTATION +namespace olc::gpu +{ + + std::string Shader::static_PS_DefaultHeader; + std::string Shader::static_PS_DefaultMain; + std::string Shader::static_VS_DefaultHeader; + std::string Shader::static_VS_DefaultMain; + std::string Shader::static_GS_DefaultHeader; + std::string Shader::static_GS_DefaultMain; + + std::string Shader_None::Compile() + { + return "OK"; + } + + int32_t Shader_None::CreateUniform(const std::string& name) + { + static int32_t uniformID = 0; + mapUniforms.insert({ name, uniformID++ }); + return GetUniform(name); + } + + // Constructs a GPU Device interface + bool Renderer_None::CreateDevice(std::vector os_win_id, const RendererConfig& cfg) + { + return true; + } + + // Destroys a GPU device interface + bool Renderer_None::DestroyDevice() + { + return false; + } + + // If applicable, relocate the rendering context + bool Renderer_None::RetargetDevice(std::vector os_win_id) + { + return true; + } + + // Prepare an OS rendering target + bool Renderer_None::PrepareWindowTarget(std::vector os_win_id) + { + return true; + } + + // Allocates a new texture resource in VRAM, returns handle + uint32_t Renderer_None::CreateTexture(const olc::vi2d& vSize, const olc::ImageConfig& cfg) + { + static uint32_t id = 0; + // deliberate post increment here to return 0 on the first call + return static_cast(id++); + } + + // Writes to / updates an existing texture resource in VRAM, using existing Image in SRAM + bool Renderer_None::WriteTexture(const uint32_t texid, olc::Image& image) + { + return true; + } + + // Writes to / updates an existing Image in SRAM, from existing texture resource in VRAM + bool Renderer_None::ReadTexture(const uint32_t texid, olc::Image& image) + { + return true; + } + + // Destroys and releases texture resource for given handle + bool Renderer_None::DeleteTexture(const uint32_t texid) + { + return true; + } + + // Makes active the given texture resource (for subsequent sampling operations) + bool Renderer_None::AssignTextureSource(const uint32_t slot, const uint32_t texid) + { + return true; + } + + // Makes active the given texture resource (for subsequent rendering operations) + bool Renderer_None::AssignTextureTarget(const uint32_t slot, const uint32_t texid) + { + return true; + } + + // Resolves an MSAA texture into a normal texture + bool Renderer_None::ResolveMSAA(const uint32_t msaaTexId) + { + return true; + } + + // Change the shader used for subsequent GPU drawing tasks + bool Renderer_None::ApplyShader(const Shader& shader) + { + return true; + } + + // Reset to default shader for subsequent GPU drawing tasks + bool Renderer_None::ApplyDefaultShader() + { + return ApplyShader(shaderDefault); + } + + // Set uniform variable for subsequent GPU drawing tasks + bool Renderer_None::SetUniform(const std::string& name, const float value) + { + return true; + } + + // Set uniform variable for subsequent GPU drawing tasks + bool Renderer_None::SetUniform(const std::string& name, const olc::vf2d& value) + { + return true; + } + + // Set uniform variable for subsequent GPU drawing tasks + bool Renderer_None::SetUniform(const std::string& name, const olc::Pixel value) + { + return true; + } + + + bool Renderer_None::DoGPUTask(const olc::GPUTask& task) + { + return true; + } + + // Clears the viewport to a specific colour and depth + bool Renderer_None::ClearViewport(const olc::Pixel col, bool bDepth, bool bStencil) + { + return true; + } + + // Sets the viewport area of the drawing space + bool Renderer_None::SetViewport(const olc::vf2d& pos, const olc::vf2d& size) + { + return true; + } + + // Configures defaults prior to drawing + bool Renderer_None::DisplayPrepare(const float fFrameElapsedTime, const float fTotalElapsedTime) + { + return true; + } + + // Displays the final output + bool Renderer_None::DisplayDraw(std::vector os_win_id, bool bVerticalSyncNow) + { + return true; + } + +} +//! END IMPLEMENTATION diff --git a/dev/src/gpu_none.h b/dev/src/gpu_none.h new file mode 100644 index 00000000..0708d7ee --- /dev/null +++ b/dev/src/gpu_none.h @@ -0,0 +1,79 @@ +#pragma once + +#include "gpu_iface.h" + +//! START DECLARATION + +#if !defined(PGE_RENDERER_NONE_DECLARED) +namespace olc::gpu +{ + class Shader_None : public olc::gpu::Shader + { + public: + std::string Compile() override; + int32_t CreateUniform(const std::string& name) override; + }; + + class Renderer_None : public olc::gpu::Renderer + { + public: // Device Stuff + // Constructs a GPU Device interface + bool CreateDevice(std::vector os_win_id, const RendererConfig& cfg) override; + // Destroys a GPU device interface + bool DestroyDevice() override; + // If applicable, relocate the rendering context + bool RetargetDevice(std::vector os_win_id) override; + // Prepare an OS rendering target + bool PrepareWindowTarget(std::vector os_win_id) override; + + + public: // Texture Resource Stuff + // Allocates a new texture resource in VRAM, returns handle + uint32_t CreateTexture(const olc::vi2d& vSize, const olc::ImageConfig& cfg = olc::ImageConfig()) override; + // Writes to / updates an existing texture resource in VRAM, using existing Image in SRAM + bool WriteTexture(const uint32_t texid, olc::Image& image) override; + // Writes to / updates an existing Image in SRAM, from existing texture resource in VRAM + bool ReadTexture(const uint32_t texid, olc::Image& image) override; + // Destroys and releases texture resource for given handle + bool DeleteTexture(const uint32_t texid) override; + // Makes active the given texture resource (for subsequent sampling operations) + bool AssignTextureSource(const uint32_t slot, const uint32_t texid) override; + // Makes active the given texture resource (for subsequent rendering operations) + bool AssignTextureTarget(const uint32_t slot, const uint32_t texid) override; + // Resolves an MSAA texture into a normal texture + bool ResolveMSAA(const uint32_t msaaTexId) override; + + public: // Shader Construction Stuff + // Change the shader used for subsequent GPU drawing tasks + bool ApplyShader(const Shader& shader) override; + // Reset to default shader for subsequent GPU drawing tasks + bool ApplyDefaultShader() override; + // Set uniform variable for subsequent GPU drawing tasks + bool SetUniform(const std::string& name, const float value) override; + // Set uniform variable for subsequent GPU drawing tasks + bool SetUniform(const std::string& name, const olc::vf2d& value) override; + // Set uniform variable for subsequent GPU drawing tasks + bool SetUniform(const std::string& name, const olc::Pixel value) override; + + public: // GPU Task Stuff + bool DoGPUTask(const olc::GPUTask& task) override; + + public: // Swap Chain Stuff + // Clears the viewport to a specific colour and depth + bool ClearViewport(const olc::Pixel col, bool bDepth, bool bStencil) override; + // Sets the viewport area of the drawing space + bool SetViewport(const olc::vf2d& pos, const olc::vf2d& size) override; + // Configures defaults prior to drawing + bool DisplayPrepare(const float fFrameElapsedTime, const float fTotalElapsedTime) override; + // Displays the final output + bool DisplayDraw(std::vector os_win_id, bool bVerticalSyncNow) override; + protected: + Shader_None shaderDefault; + }; +} + + + +#define PGE_RENDERER_NONE_DECLARED 1 +#endif +//! END DECLARATION \ No newline at end of file diff --git a/dev/src/host_iface.h b/dev/src/host_iface.h index fc550b5b..aa0a6cde 100644 --- a/dev/src/host_iface.h +++ b/dev/src/host_iface.h @@ -21,37 +21,10 @@ namespace olc { inline static size_t uuid = 0; - #if OLC_HOST == OLC_HOST_WINDOWS inline size_t CreateUID() { return uuid++; } - #endif - #if OLC_HOST == OLC_HOST_LINUX_X11 - inline size_t CreateUID() - { - return uuid++; - } - #endif - #if OLC_HOST == OLC_HOST_LINUX_WAYLAND - inline size_t CreateUID() - { - return uuid++; - } - #endif - #if OLC_HOST == OLC_HOST_MACOS - // Clang compiler on MacOS requires constexpr to be removed - inline size_t CreateUID() - { - return uuid++; - } - #endif - #if OLC_HOST == OLC_HOST_EMSCRIPTEN || OLC_HOST == OLC_HOST_ANDROID - inline size_t CreateUID() - { - return uuid++; - } - #endif } class PixelGameEngine; diff --git a/dev/src/host_none.cpp b/dev/src/host_none.cpp new file mode 100644 index 00000000..d62a82bc --- /dev/null +++ b/dev/src/host_none.cpp @@ -0,0 +1,116 @@ +#include "host_none.h" + +//! START IMPLEMENTATION +namespace olc::host +{ + // Make OS Create a window frame, associated with olc::Window + bool Host_None::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) + { + return true; + } + + // Make OS Close a window frame, associated with olc::Window + bool Host_None::CloseWindowFrame(olc::Window* pWindow) + { + return true; + } + + // Make OS Update a window frame title, associated with olc::Window + bool Host_None::UpdateWindowFrameTitle(olc::Window* pWindow) + { + std::cout << pWindow->GetWindowTitle() << "\n"; + return true; + } + + // Get OS-specific window descriptor(s) for given olc::Window + std::vector Host_None::GetHostWindowDescriptor(olc::Window* pWindow) + { + return {}; + } + + // Wait for OS desktop refresh (for smooooth vsync) + bool Host_None::SyncWithDesktopComposite() + { + return true; + } + + // Force the mouse position in pixels relative to window + bool Host_None::SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) + { + return true; + } + + // Show or hide mouse cursor for given window + bool Host_None::SetMouseVisible(olc::Window* pWindow, const bool bVisible) + { + return true; + } + + olc::KeyboardLayout Host_None::GetKeyboardLayout() const + { + return OLC_DEFAULT_KEYBOARD_LAYOUT; + } + + // Called at very start of application + bool Host_None::OnApplicationStart(olc::PixelGameEngine* pPrimary) + { + pPrimaryPGE = pPrimary; + return true; + } + + // Called to start the host - this may mean different things on different hosts + // It MUST block until system is requested to exit + bool Host_None::StartSystem() + { + pPrimaryPGE->OnPreContextStart(); + + systemActive = true; + + if(!OnSystemThreadStart()) + return pPrimaryPGE->OnPostContextEnd(); + + while(systemActive) + { + if(!OnSystemTick()) + { + StopSystem(); + } + } + + OnSystemThreadEnd(); + + return pPrimaryPGE->OnPostContextEnd(); + } + + // Called to stop the host, and shutdown all resources + bool Host_None::StopSystem() + { + systemActive = false; + return true; + } + + // Called at start of system event loop + bool Host_None::OnSystemThreadStart() + { + return pPrimaryPGE->OnContextStart(); + } + + // Called to perform primary window update + bool Host_None::OnSystemTick() + { + return pPrimaryPGE->OnContextTick(); + } + + // Called at end of system event loop + bool Host_None::OnSystemThreadEnd() + { + return pPrimaryPGE->OnContextEnd(); + } + + // Called at very end of application + bool Host_None::OnApplicationEnd() + { + return true; + } +} +//! END IMPLEMENTATION \ No newline at end of file diff --git a/dev/src/host_none.h b/dev/src/host_none.h new file mode 100644 index 00000000..7cb43ce8 --- /dev/null +++ b/dev/src/host_none.h @@ -0,0 +1,60 @@ +#pragma once +#include "core.h" + +//! START STDHEADER GLOBAL +#include +#include +#include +//! END STDHEADER + +//! START CUSTOMHEADER +#include "host_iface.h" +//! END CUSTOMHEADER + +//! START DECLARATION +namespace olc::host +{ + class Host_None : public olc::host::Host + { + public: // OS Window Handling + // Make OS Create a window frame, associated with olc::Window + bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; + // Make OS Close a window frame, associated with olc::Window + bool CloseWindowFrame(olc::Window* pWindow) override; + // Make OS Update a window frame title, associated with olc::Window + bool UpdateWindowFrameTitle(olc::Window* pWindow) override; + // Get OS-specific window descriptor(s) for given olc::Window + std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; + // Wait for OS desktop refresh (for smooooth vsync) + bool SyncWithDesktopComposite() override; + + public: // Platform specific Mouse Control + // Force the mouse position in pixels relative to window + bool SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) override; + // Show or hide mouse cursor for given window + bool SetMouseVisible(olc::Window* pWindow, const bool bVisible) override; + + public: // OS Specific Environment Information + virtual olc::KeyboardLayout GetKeyboardLayout() const override; + + public: // Platform Specific OS<->PGE Linkage + // 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; + private: + bool systemActive = false; + }; +} +//! END DECLARATION \ No newline at end of file diff --git a/dev/src/sh_template.h b/dev/src/sh_template.h index eea91eb6..96dc33e2 100644 --- a/dev/src/sh_template.h +++ b/dev/src/sh_template.h @@ -159,7 +159,11 @@ +#if OLC_HOST == OLC_HOST_NONE +//! GRAB host_none.h NOHOST_CONFIG +//! GRAB host_none.h DECLARATION +#endif #if OLC_HOST == OLC_HOST_WINDOWS //! GRAB host_win_winapi.h WINAPI_CONFIG @@ -201,6 +205,11 @@ //! GRAB host_android.h DECLARATION #endif +#if OLC_GPU == OLC_GPU_NONE +//! GRAB gpu_none.h DECLARATION +#endif + + #if OLC_GPU == OLC_GPU_OPENGL33 //! GRAB api_opengl.h OPENGL_CONFIG @@ -233,6 +242,10 @@ #if defined(OLC_PGE3_APPLICATION) && !defined(PGE_HOST_IMPLEMENTED) +#if OLC_HOST == OLC_HOST_NONE +//! GRAB host_none.cpp IMPLEMENTATION +#endif + #if OLC_HOST == OLC_HOST_WINDOWS //! GRAB host_win_winapi.cpp IMPLEMENTATION #endif @@ -263,8 +276,14 @@ #endif #if defined(OLC_PGE3_APPLICATION) && !defined(PGE_GPU_IMPLEMENTED) -#if OLC_GPU == OLC_GPU_OPENGL33 + //! GRAB gpu_iface.cpp IMPLEMENTATION + +#if OLC_GPU == OLC_GPU_NONE +//! GRAB gpu_none.cpp IMPLEMENTATION +#endif + +#if OLC_GPU == OLC_GPU_OPENGL33 //! GRAB api_opengl.cpp IMPLEMENTATION //! GRAB gpu_opengl33.cpp IMPLEMENTATION #endif diff --git a/examples/olcPGE3_PixelShaders-Headless.cpp b/examples/olcPGE3_PixelShaders-Headless.cpp new file mode 100644 index 00000000..a0695210 --- /dev/null +++ b/examples/olcPGE3_PixelShaders-Headless.cpp @@ -0,0 +1,228 @@ + +/* + olc::PixelGameEngine3 Example - Custom Pixel Shaders (HEADLESS MODE) + + Draws a few lines using different styles and techniques, AND THEN + renders THE ENTIRE SCENE USING A CUSTOM PIXEL SHADER! + + Licenced under the OLC-3 License +*/ + +/* + NOTE! This is an advanced example demonstrating custom pixel shaders. + + It assumes you are familiar with olcPixelGameEngine3 and GLSL shader + programming. If you are new to either of these topics, please refer + to the other examples and documentation first. +*/ + + +// Define OLC_PGE3_APPLICATION to include the implementation of +// the Pixel Game Engine as part of this translation unit +#define OLC_USE_HEADLESS +#define OLC_PGE3_APPLICATION +#include "../olcPixelGameEngine3.h" + +// Example application demonstrating a custom pixel shader effect. +// This class overrides the olc::PixelGameEngine base class by +// implementing the OnUserCreate() and OnUserUpdate() functions +class Example_CustomPixelShader : public olc::PixelGameEngine +{ +public: + Example_CustomPixelShader() + { + + } + +protected: + // We accumulate total time for some animation + float fTotalTime = 0.0f; + + olc::gpu::OLC_SHADER_CLASS shaderExample; + olc::Image imgWithoutFX; + + olc::Image imgMini; + +public: + // Called once at the start, so create things here + bool OnUserCreate() override + { + // Custom pixel shader main() function and uniforms + std::string custom_ps_main = + R"( + uniform float frequency; + uniform float amplitude; + + void main() + { + // 1) Horizontal Wobble + vec2 samplePos = vec2( + oTex.x + sin(oTex.y * frequency * pgeTargetSizeInPixels.y + pgeTotalTimeElapsed * 2.0) * amplitude, + oTex.y + ); + + + + // 2) Sobel Edge Detection + + // Sample 3x3 neighborhood for Sobel + vec2 pixelSize = pgeInverseTargetSizeInPixels; + + float tl = length(texture(pgeTexture0, samplePos + vec2(-pixelSize.x, -pixelSize.y)).rgb); + float t = length(texture(pgeTexture0, samplePos + vec2(0.0, -pixelSize.y)).rgb); + float tr = length(texture(pgeTexture0, samplePos + vec2(pixelSize.x, -pixelSize.y)).rgb); + float l = length(texture(pgeTexture0, samplePos + vec2(-pixelSize.x, 0.0)).rgb); + float r = length(texture(pgeTexture0, samplePos + vec2(pixelSize.x, 0.0)).rgb); + float bl = length(texture(pgeTexture0, samplePos + vec2(-pixelSize.x, pixelSize.y)).rgb); + float b = length(texture(pgeTexture0, samplePos + vec2(0.0, pixelSize.y)).rgb); + float br = length(texture(pgeTexture0, samplePos + vec2(pixelSize.x, pixelSize.y)).rgb); + + // Apply Sobel Kernels + float sobelX = -tl + tr - 2.0 * l + 2.0 * r - bl + br; + float sobelY = -tl - 2.0 * t - tr + bl + 2.0 * b + br; + + // Calculate edge magnitude + float edge = sqrt(sobelX * sobelX + sobelY * sobelY); + + // 3) Colour bias vertically + float bias = oTex.y; + vec4 newCol = vec4( + oCol.r * bias, + oCol.g, + oCol.b * (1.0 - bias), + oCol.a + ); + + + // 4) Combine effects + + // pgeTexture0 is the primary texture unit + // pgeTexture1 is some other image + + vec4 texColour = texture(pgeTexture0, samplePos) * oCol; + vec4 otherColour = texture(pgeTexture1, samplePos); + + vec4 edgeColour = vec4(newCol * edge) + otherColour * 0.5; + pixel = vec4(edgeColour.rgb * texColour.a, texColour.a); + } + )"; + + + // Construct pixel shader from default header, but use custom main() + shaderExample.SetPixelShaderSource( + olc::gpu::Shader::PS_DefaultHeader() + + custom_ps_main); + + // Just use the PGE3 default vertex shader + shaderExample.SetVertexShaderSource( + olc::gpu::Shader::VS_DefaultHeader() + + olc::gpu::Shader::VS_DefaultMain() + ); + + // Check for shader compile errors + std::string sResult = shaderExample.Compile(); + if (sResult != "OK") + { + std::cout << "Error compiling shader: " << sResult << std::endl; + return false; + } + + // Add custom uniforms used by custom shader. The "system" + // will have taken care of required PGE3 uniforms + shaderExample.CreateUniform("frequency"); + shaderExample.CreateUniform("amplitude"); + + // Create off-screen image to draw to + CreateImage(imgWithoutFX, GetScreen().Size()); + + // Load a small image to draw for fun + CreateImageFromFile(imgMini, "./assets/minsanity_texture.png"); + return true; + } + + // Called every frame, so update things here + bool OnUserUpdate(float fElapsedTime) override + { + // Set rendering target to off-screen image, we will draw to this + // entirely normally, then apply a custom shader when we draw the + // off-screen image to the screen + draw.SetTarget(imgWithoutFX); + + // === Stolen from olcPGE3_Lines.cpp == + + // Clear whole screen + draw.Clear(olc::Colour::VERY_DARK_BLUE); + + // Simple white line + draw.Line({ 10.0f, 10.0f }, { 246.0f, 40.0f }, olc::Colour::WHITE); + + // Gradient line + draw.Line({ 10.0f, 50.0f }, olc::Colour::RED, { 246.0f, 80.0f }, olc::Colour::YELLOW); + + // Rotating gradient lines + fTotalTime += fElapsedTime; + olc::vf2d p1 = { 64.0f, 160.0f }; + olc::vf2d p2 = olc::vf2d{ std::cos(fTotalTime), std::sin(fTotalTime) } * 64.0f; + + draw.Line(p1, olc::Colour::CYAN, p1 + p2, olc::Colour::MAGENTA); + draw.Line(p1, olc::Colour::CYAN, p1 - p2, olc::Colour::MAGENTA); + draw.Line(p1, olc::Colour::CYAN, p1 + olc::vf2d{ -p2.y, p2.x }, olc::Colour::MAGENTA); + draw.Line(p1, olc::Colour::CYAN, p1 + olc::vf2d{ p2.y, -p2.x }, olc::Colour::MAGENTA); + + // Line to mouse pointer with tint + float alpha = (std::sin(fTotalTime * 4.0f) + 1.0f) / 2.0f; + draw.Line(p1, mouse.GetPosition(), olc::Colour::WHITE, olc::PixelF(1.0f, 0.0f, 0.0f, alpha)); + + // Boundary box (with fixed coordinates) + draw.Line({ 0.0f, 0.0f }, { 255.0f, 0.0f }, olc::Colour::DARK_GREY); + draw.Line({ 255.0f, 0.0f }, { 255.0f, 239.0f }, olc::Colour::DARK_GREY); + draw.Line({ 255.0f, 239.0f }, { 0.0f, 239.0f }, olc::Colour::DARK_GREY); + draw.Line({ 0.0f, 239.0f }, { 0.0f, 0.0f }, olc::Colour::DARK_GREY); + // === End stolen code === + + // Draws an image for the fun of it + draw.Image(imgMini, mouse.GetPosition()); + + // Copy image to screen with new shader + draw.SetTarget(GetScreen()); + + // Set the custom shader + draw.SetShader(shaderExample); + + // Set custom parameters for the shader based + // on mouse position + float frequency = (mouse.GetPosition().x / 256.0f) * 0.1f; + float amplitude = (mouse.GetPosition().y / 240.0f) * 0.1f; + draw.SetShaderUniform("frequency", frequency); + draw.SetShaderUniform("amplitude", amplitude); + + // Our shader uses 2 texture units so we need to bind them here + // By default, unit 0 is bound to the primary texture (imgWithoutFX) + draw.SetShaderTexture(1, imgMini); // Bind imgMini to texture unit 1 + + // Present image with shader effect - this is required + // because here is where the shader is actually applied + draw.Image(imgWithoutFX, { 0, 0 }); + + // Successful frame + return true; + } +}; + + +// Main entry point for the application +int main() +{ + // Construct demo application + Example_CustomPixelShader 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 3362e1ff..eb57859e 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -163,13 +163,14 @@ // Choose "Operating System" -#define OLC_HOST_WINDOWS 1 -#define OLC_HOST_LINUX_X11 2 +#define OLC_HOST_NONE 1 +#define OLC_HOST_WINDOWS 2 #define OLC_HOST_LINUX_WAYLAND 3 -#define OLC_HOST_MACOS 4 -#define OLC_HOST_EMSCRIPTEN 5 -#define OLC_HOST_ANDROID 6 -#define OLC_HOST_IOS 7 +#define OLC_HOST_LINUX_X11 4 +#define OLC_HOST_MACOS 5 +#define OLC_HOST_EMSCRIPTEN 6 +#define OLC_HOST_ANDROID 7 +#define OLC_HOST_IOS 8 #if !defined(OLC_HOST) #if defined(_WIN32) @@ -204,10 +205,24 @@ #define OLC_GPU_NONE 1 #define OLC_GPU_OPENGL33 2 +#if defined(OLC_USE_HEADLESS) + #define OLC_GPU OLC_GPU_NONE +#endif + #if !defined(OLC_GPU) #define OLC_GPU OLC_GPU_OPENGL33 #endif +#if OLC_GPU == OLC_GPU_NONE + #define OLC_GPU_CLASS Renderer_None + #define OLC_SHADER_CLASS Shader_None +#endif + +#if OLC_GPU == OLC_GPU_OPENGL33 + #define OLC_GPU_CLASS Renderer_OGL33 + #define OLC_SHADER_CLASS Shader_GLSL33 +#endif + #define OLC_IMAGELOADER_NONE 1 #define OLC_IMAGELOADER_WINGDI 2 #define OLC_IMAGELOADER_MACOS 3 @@ -247,6 +262,13 @@ #endif #endif +// We wait until after the platform specific image loader is selected +// to lock in the headless host. +#if defined(OLC_USE_HEADLESS) + #undef OLC_HOST + #define OLC_HOST OLC_HOST_NONE +#endif + #define OLC_MULTIWINDOW_NO 1 #define OLC_MULTIWINDOW_YES 2 @@ -275,6 +297,10 @@ template inline constexpr void olc_IgnoreUnused(Args&&...) noexcept {} +#if OLC_HOST == OLC_HOST_NONE +#define OLC_FRIENDLY_HOST Host_None +#endif + #if OLC_HOST == OLC_HOST_WINDOWS #define OLC_FRIENDLY_HOST Host_Windows_WinAPI #endif @@ -3708,37 +3734,10 @@ namespace olc { inline static size_t uuid = 0; - #if OLC_HOST == OLC_HOST_WINDOWS - inline size_t CreateUID() - { - return uuid++; - } - #endif - #if OLC_HOST == OLC_HOST_LINUX_X11 inline size_t CreateUID() { return uuid++; } - #endif - #if OLC_HOST == OLC_HOST_LINUX_WAYLAND - inline size_t CreateUID() - { - return uuid++; - } - #endif - #if OLC_HOST == OLC_HOST_MACOS - // Clang compiler on MacOS requires constexpr to be removed - inline size_t CreateUID() - { - return uuid++; - } - #endif - #if OLC_HOST == OLC_HOST_EMSCRIPTEN || OLC_HOST == OLC_HOST_ANDROID - inline size_t CreateUID() - { - return uuid++; - } - #endif } class PixelGameEngine; @@ -4076,7 +4075,54 @@ namespace olc +#if OLC_HOST == OLC_HOST_NONE +namespace olc::host +{ + class Host_None : public olc::host::Host + { + public: // OS Window Handling + // Make OS Create a window frame, associated with olc::Window + bool AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) override; + // Make OS Close a window frame, associated with olc::Window + bool CloseWindowFrame(olc::Window* pWindow) override; + // Make OS Update a window frame title, associated with olc::Window + bool UpdateWindowFrameTitle(olc::Window* pWindow) override; + // Get OS-specific window descriptor(s) for given olc::Window + std::vector GetHostWindowDescriptor(olc::Window* pWindow) override; + // Wait for OS desktop refresh (for smooooth vsync) + bool SyncWithDesktopComposite() override; + + public: // Platform specific Mouse Control + // Force the mouse position in pixels relative to window + bool SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) override; + // Show or hide mouse cursor for given window + bool SetMouseVisible(olc::Window* pWindow, const bool bVisible) override; + + public: // OS Specific Environment Information + virtual olc::KeyboardLayout GetKeyboardLayout() const override; + + public: // Platform Specific OS<->PGE Linkage + // 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; + private: + bool systemActive = false; + }; +} +#endif #if OLC_HOST == OLC_HOST_WINDOWS #if defined(UNICODE) || defined(_UNICODE) @@ -6019,6 +6065,83 @@ namespace olc::host } #endif +#if OLC_GPU == OLC_GPU_NONE + +#if !defined(PGE_RENDERER_NONE_DECLARED) +namespace olc::gpu +{ + class Shader_None : public olc::gpu::Shader + { + public: + std::string Compile() override; + int32_t CreateUniform(const std::string& name) override; + }; + + class Renderer_None : public olc::gpu::Renderer + { + public: // Device Stuff + // Constructs a GPU Device interface + bool CreateDevice(std::vector os_win_id, const RendererConfig& cfg) override; + // Destroys a GPU device interface + bool DestroyDevice() override; + // If applicable, relocate the rendering context + bool RetargetDevice(std::vector os_win_id) override; + // Prepare an OS rendering target + bool PrepareWindowTarget(std::vector os_win_id) override; + + + public: // Texture Resource Stuff + // Allocates a new texture resource in VRAM, returns handle + uint32_t CreateTexture(const olc::vi2d& vSize, const olc::ImageConfig& cfg = olc::ImageConfig()) override; + // Writes to / updates an existing texture resource in VRAM, using existing Image in SRAM + bool WriteTexture(const uint32_t texid, olc::Image& image) override; + // Writes to / updates an existing Image in SRAM, from existing texture resource in VRAM + bool ReadTexture(const uint32_t texid, olc::Image& image) override; + // Destroys and releases texture resource for given handle + bool DeleteTexture(const uint32_t texid) override; + // Makes active the given texture resource (for subsequent sampling operations) + bool AssignTextureSource(const uint32_t slot, const uint32_t texid) override; + // Makes active the given texture resource (for subsequent rendering operations) + bool AssignTextureTarget(const uint32_t slot, const uint32_t texid) override; + // Resolves an MSAA texture into a normal texture + bool ResolveMSAA(const uint32_t msaaTexId) override; + + public: // Shader Construction Stuff + // Change the shader used for subsequent GPU drawing tasks + bool ApplyShader(const Shader& shader) override; + // Reset to default shader for subsequent GPU drawing tasks + bool ApplyDefaultShader() override; + // Set uniform variable for subsequent GPU drawing tasks + bool SetUniform(const std::string& name, const float value) override; + // Set uniform variable for subsequent GPU drawing tasks + bool SetUniform(const std::string& name, const olc::vf2d& value) override; + // Set uniform variable for subsequent GPU drawing tasks + bool SetUniform(const std::string& name, const olc::Pixel value) override; + + public: // GPU Task Stuff + bool DoGPUTask(const olc::GPUTask& task) override; + + public: // Swap Chain Stuff + // Clears the viewport to a specific colour and depth + bool ClearViewport(const olc::Pixel col, bool bDepth, bool bStencil) override; + // Sets the viewport area of the drawing space + bool SetViewport(const olc::vf2d& pos, const olc::vf2d& size) override; + // Configures defaults prior to drawing + bool DisplayPrepare(const float fFrameElapsedTime, const float fTotalElapsedTime) override; + // Displays the final output + bool DisplayDraw(std::vector os_win_id, bool bVerticalSyncNow) override; + protected: + Shader_None shaderDefault; + }; +} + + + +#define PGE_RENDERER_NONE_DECLARED 1 +#endif +#endif + + #if OLC_GPU == OLC_GPU_OPENGL33 #if OLC_HOST == OLC_HOST_WINDOWS @@ -6654,6 +6777,121 @@ namespace olc::imload #if defined(OLC_PGE3_APPLICATION) && !defined(PGE_HOST_IMPLEMENTED) +#if OLC_HOST == OLC_HOST_NONE +namespace olc::host +{ + // Make OS Create a window frame, associated with olc::Window + bool Host_None::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) + { + return true; + } + + // Make OS Close a window frame, associated with olc::Window + bool Host_None::CloseWindowFrame(olc::Window* pWindow) + { + return true; + } + + // Make OS Update a window frame title, associated with olc::Window + bool Host_None::UpdateWindowFrameTitle(olc::Window* pWindow) + { + std::cout << pWindow->GetWindowTitle() << "\n"; + return true; + } + + // Get OS-specific window descriptor(s) for given olc::Window + std::vector Host_None::GetHostWindowDescriptor(olc::Window* pWindow) + { + return {}; + } + + // Wait for OS desktop refresh (for smooooth vsync) + bool Host_None::SyncWithDesktopComposite() + { + return true; + } + + // Force the mouse position in pixels relative to window + bool Host_None::SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) + { + return true; + } + + // Show or hide mouse cursor for given window + bool Host_None::SetMouseVisible(olc::Window* pWindow, const bool bVisible) + { + return true; + } + + olc::KeyboardLayout Host_None::GetKeyboardLayout() const + { + return OLC_DEFAULT_KEYBOARD_LAYOUT; + } + + // Called at very start of application + bool Host_None::OnApplicationStart(olc::PixelGameEngine* pPrimary) + { + pPrimaryPGE = pPrimary; + return true; + } + + // Called to start the host - this may mean different things on different hosts + // It MUST block until system is requested to exit + bool Host_None::StartSystem() + { + pPrimaryPGE->OnPreContextStart(); + + systemActive = true; + + if(!OnSystemThreadStart()) + return pPrimaryPGE->OnPostContextEnd(); + + while(systemActive) + { + if(!OnSystemTick()) + { + StopSystem(); + } + } + + OnSystemThreadEnd(); + + return pPrimaryPGE->OnPostContextEnd(); + } + + // Called to stop the host, and shutdown all resources + bool Host_None::StopSystem() + { + systemActive = false; + return true; + } + + // Called at start of system event loop + bool Host_None::OnSystemThreadStart() + { + return pPrimaryPGE->OnContextStart(); + } + + // Called to perform primary window update + bool Host_None::OnSystemTick() + { + return pPrimaryPGE->OnContextTick(); + } + + // Called at end of system event loop + bool Host_None::OnSystemThreadEnd() + { + return pPrimaryPGE->OnContextEnd(); + } + + // Called at very end of application + bool Host_None::OnApplicationEnd() + { + return true; + } +} +#endif + #if OLC_HOST == OLC_HOST_WINDOWS namespace olc::host { @@ -12810,7 +13048,7 @@ void android_main(struct android_app* app) #endif #if defined(OLC_PGE3_APPLICATION) && !defined(PGE_GPU_IMPLEMENTED) -#if OLC_GPU == OLC_GPU_OPENGL33 + namespace olc { gpu::Shader::~Shader() @@ -12876,6 +13114,162 @@ namespace olc } } + +#if OLC_GPU == OLC_GPU_NONE +namespace olc::gpu +{ + + std::string Shader::static_PS_DefaultHeader; + std::string Shader::static_PS_DefaultMain; + std::string Shader::static_VS_DefaultHeader; + std::string Shader::static_VS_DefaultMain; + std::string Shader::static_GS_DefaultHeader; + std::string Shader::static_GS_DefaultMain; + + std::string Shader_None::Compile() + { + return "OK"; + } + + int32_t Shader_None::CreateUniform(const std::string& name) + { + static int32_t uniformID = 0; + mapUniforms.insert({ name, uniformID++ }); + return GetUniform(name); + } + + // Constructs a GPU Device interface + bool Renderer_None::CreateDevice(std::vector os_win_id, const RendererConfig& cfg) + { + return true; + } + + // Destroys a GPU device interface + bool Renderer_None::DestroyDevice() + { + return false; + } + + // If applicable, relocate the rendering context + bool Renderer_None::RetargetDevice(std::vector os_win_id) + { + return true; + } + + // Prepare an OS rendering target + bool Renderer_None::PrepareWindowTarget(std::vector os_win_id) + { + return true; + } + + // Allocates a new texture resource in VRAM, returns handle + uint32_t Renderer_None::CreateTexture(const olc::vi2d& vSize, const olc::ImageConfig& cfg) + { + static uint32_t id = 0; + // deliberate post increment here to return 0 on the first call + return static_cast(id++); + } + + // Writes to / updates an existing texture resource in VRAM, using existing Image in SRAM + bool Renderer_None::WriteTexture(const uint32_t texid, olc::Image& image) + { + return true; + } + + // Writes to / updates an existing Image in SRAM, from existing texture resource in VRAM + bool Renderer_None::ReadTexture(const uint32_t texid, olc::Image& image) + { + return true; + } + + // Destroys and releases texture resource for given handle + bool Renderer_None::DeleteTexture(const uint32_t texid) + { + return true; + } + + // Makes active the given texture resource (for subsequent sampling operations) + bool Renderer_None::AssignTextureSource(const uint32_t slot, const uint32_t texid) + { + return true; + } + + // Makes active the given texture resource (for subsequent rendering operations) + bool Renderer_None::AssignTextureTarget(const uint32_t slot, const uint32_t texid) + { + return true; + } + + // Resolves an MSAA texture into a normal texture + bool Renderer_None::ResolveMSAA(const uint32_t msaaTexId) + { + return true; + } + + // Change the shader used for subsequent GPU drawing tasks + bool Renderer_None::ApplyShader(const Shader& shader) + { + return true; + } + + // Reset to default shader for subsequent GPU drawing tasks + bool Renderer_None::ApplyDefaultShader() + { + return ApplyShader(shaderDefault); + } + + // Set uniform variable for subsequent GPU drawing tasks + bool Renderer_None::SetUniform(const std::string& name, const float value) + { + return true; + } + + // Set uniform variable for subsequent GPU drawing tasks + bool Renderer_None::SetUniform(const std::string& name, const olc::vf2d& value) + { + return true; + } + + // Set uniform variable for subsequent GPU drawing tasks + bool Renderer_None::SetUniform(const std::string& name, const olc::Pixel value) + { + return true; + } + + + bool Renderer_None::DoGPUTask(const olc::GPUTask& task) + { + return true; + } + + // Clears the viewport to a specific colour and depth + bool Renderer_None::ClearViewport(const olc::Pixel col, bool bDepth, bool bStencil) + { + return true; + } + + // Sets the viewport area of the drawing space + bool Renderer_None::SetViewport(const olc::vf2d& pos, const olc::vf2d& size) + { + return true; + } + + // Configures defaults prior to drawing + bool Renderer_None::DisplayPrepare(const float fFrameElapsedTime, const float fTotalElapsedTime) + { + return true; + } + + // Displays the final output + bool Renderer_None::DisplayDraw(std::vector os_win_id, bool bVerticalSyncNow) + { + return true; + } + +} +#endif + +#if OLC_GPU == OLC_GPU_OPENGL33 namespace olc::apis::opengl { bool gl::bLoaded = false; @@ -16637,7 +17031,7 @@ namespace olc // Initialise GPU Interface olc::gpu::RendererConfig cfgRenderer; cfgRenderer.VerticalSync = config.bVSync; - gpu = std::make_unique(); + gpu = std::make_unique(); gpu->CreateDevice(host->GetHostWindowDescriptor(this), cfgRenderer); if (gpu->GetLastError() != olc::gpu::RendererError::NoError) From 33edd9605a36cb128d2f580eef52d6f4117ee284 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Tue, 24 Feb 2026 15:22:53 -0500 Subject: [PATCH 071/101] [headless] make headless mode work in the multiheader test --- dev/src/olcpge3.h | 8 +- dev/tests/CMakeLists.txt | 82 ++-- dev/tests/test_mh.cpp | 890 +++++++-------------------------------- 3 files changed, 217 insertions(+), 763 deletions(-) diff --git a/dev/src/olcpge3.h b/dev/src/olcpge3.h index 56252716..216b7c21 100644 --- a/dev/src/olcpge3.h +++ b/dev/src/olcpge3.h @@ -22,8 +22,14 @@ #include "gputask.h" #include "gpu_iface.h" -#include "gpu_opengl33.h" +#if OLC_GPU == OLC_GPU_NONE +#include "gpu_none.h" +#endif + +#if OLC_GPU == OLC_GPU_OPENGL33 +#include "gpu_opengl33.h" +#endif diff --git a/dev/tests/CMakeLists.txt b/dev/tests/CMakeLists.txt index 81f2c7d9..54bc2351 100644 --- a/dev/tests/CMakeLists.txt +++ b/dev/tests/CMakeLists.txt @@ -7,8 +7,26 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) option(BUILD_WAYLAND "Build example programs with Wayland" OFF) +option(USE_HEADLESS "Build in headless mode" OFF) + +# GPU specific sources +set(GPU_HEADLESS_SOURCE_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/../src/gpu_none.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../src/gpu_none.h +) + +set(GPU_OPENGL33_SOURCE_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/../src/api_opengl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../src/api_opengl.h + ${CMAKE_CURRENT_SOURCE_DIR}/../src/gpu_opengl33.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../src/gpu_opengl33.h +) # Platform specific sources +set(HOST_HEADLESS_SOURCE_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/../src/host_none.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../src/host_none.h +) set(HOST_ANDROID_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/../src/host_android.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../src/host_android.h @@ -68,6 +86,9 @@ list( REMOVE_ITEM SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/../src/sh_template.h ${CMAKE_CURRENT_SOURCE_DIR}/../src/olcpge3.h + ${GPU_HEADLESS_SOURCE_FILES} + ${GPU_OPENGL33_SOURCE_FILES} + ${HOST_HEADLESS_SOURCE_FILES} ${HOST_ANDROID_SOURCE_FILES} ${HOST_MACOS_SOURCE_FILES} ${HOST_LINUX_WAYLAND_SOURCE_FILES} @@ -81,33 +102,41 @@ list( ${IMAGELOADER_WINGDI_SOURCE_FILES} ) -if(ANDROID) - set(HOST_SOURCE_FILES ${HOST_ANDROID_SOURCE_FILES}) -endif() # Android +if(USE_HEADLESS) + set(HOST_SOURCE_FILES ${HOST_HEADLESS_SOURCE_FILES}) + set(GPU_SOURCE_FILES ${GPU_HEADLESS_SOURCE_FILES}) +else() + + set(GPU_SOURCE_FILES ${GPU_OPENGL33_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(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(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(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 + if(WIN32 AND NOT EMSCRIPTEN) + set(HOST_SOURCE_FILES ${HOST_WIN_WINAPI_SOURCE_FILES}) + endif() # windows +endif() # Image Loader source files if(USE_STB) @@ -138,7 +167,6 @@ else() endif() endif() - if(EMSCRIPTEN) # build Cache: libpng, zlib execute_process(COMMAND "${EMSCRIPTEN_ROOT_PATH}/embuilder${EMCC_SUFFIX}" build libpng zlib) @@ -147,10 +175,14 @@ endif() set(EXE_NAME test_mh) -add_executable(${EXE_NAME} test_mh.cpp ${HOST_SOURCE_FILES} ${IMAGELOADER_SOURCE_FILES} ${SOURCES}) +add_executable(${EXE_NAME} test_mh.cpp ${HOST_SOURCE_FILES} ${GPU_SOURCE_FILES} ${IMAGELOADER_SOURCE_FILES} ${SOURCES}) target_include_directories(${EXE_NAME} PRIVATE ../src) +if(USE_HEADLESS) + target_compile_definitions(${EXE_NAME} PRIVATE OLC_USE_HEADLESS=1) +endif() + if(APPLE) if(CMAKE_SYSTEM_NAME STREQUAL "iOS") # TODO: Building for iOS diff --git a/dev/tests/test_mh.cpp b/dev/tests/test_mh.cpp index a58b860f..7a015f7b 100644 --- a/dev/tests/test_mh.cpp +++ b/dev/tests/test_mh.cpp @@ -1,790 +1,206 @@ #define OLC_PGE3_APPLICATION #include "olcpge3.h" - -class SecondWindow : public olc::PGEWindow +// Example application demonstrating a custom pixel shader effect. +// This class overrides the olc::PixelGameEngine base class by +// implementing the OnUserCreate() and OnUserUpdate() functions +class Example_CustomPixelShader : public olc::PixelGameEngine { public: - - // Return true if window is to continue - bool OnUserCreate() override - { - - return false; - } - - // Return true if window is to continue - bool OnUserUpdate(float fElapsedTime) override + Example_CustomPixelShader() { - fTotalTime += fElapsedTime; - draw.Clear(olc::Colour::RED); - draw.WorldRotate(fTotalTime, GetScreen().Size() / 2); - draw.FilledRect({ 10,10 }, { 20,20 }, olc::Colour::BLUE); - return true; } +protected: + // We accumulate total time for some animation + float fTotalTime = 0.0f; - float fTotalTime = 0; -}; + olc::gpu::OLC_SHADER_CLASS shaderExample; + olc::Image imgWithoutFX; + olc::Image imgMini; -class Example : public olc::PixelGameEngine -{ public: - Example() - { - - } - - olc::Image imTest; - - olc::Image imLogo; - olc::Image imTemp; - - olc::Image imSampleTest; - - olc::Image imLowRes; - olc::Image imHighResSprite; - - void CreateSampleTestImage(olc::Image& image, const olc::vi2d& s) + // Called once at the start, so create things here + bool OnUserCreate() override { - CreateImage(image, s); - draw.SetTarget(image); + // Custom pixel shader main() function and uniforms + std::string custom_ps_main = + R"( + uniform float frequency; + uniform float amplitude; - draw.Clear(olc::Colour::WHITE); - - std::vector vColours = { - olc::Colour::RED, - olc::Colour::YELLOW, - olc::Colour::GREEN, - olc::Colour::CYAN, - olc::Colour::BLUE, - olc::Colour::MAGENTA, - }; - - for (int i = 0; i < s.x/2; i++) + void main() { - draw.Rect(olc::vi2d{ i,i }, s - olc::vi2d{ (i * 2)+1 ,(i * 2)+1 }, vColours[i % vColours.size()]); + // 1) Horizontal Wobble + vec2 samplePos = vec2( + oTex.x + sin(oTex.y * frequency * pgeTargetSizeInPixels.y + pgeTotalTimeElapsed * 2.0) * amplitude, + oTex.y + ); + + + + // 2) Sobel Edge Detection + + // Sample 3x3 neighborhood for Sobel + vec2 pixelSize = pgeInverseTargetSizeInPixels; + + float tl = length(texture(pgeTexture0, samplePos + vec2(-pixelSize.x, -pixelSize.y)).rgb); + float t = length(texture(pgeTexture0, samplePos + vec2(0.0, -pixelSize.y)).rgb); + float tr = length(texture(pgeTexture0, samplePos + vec2(pixelSize.x, -pixelSize.y)).rgb); + float l = length(texture(pgeTexture0, samplePos + vec2(-pixelSize.x, 0.0)).rgb); + float r = length(texture(pgeTexture0, samplePos + vec2(pixelSize.x, 0.0)).rgb); + float bl = length(texture(pgeTexture0, samplePos + vec2(-pixelSize.x, pixelSize.y)).rgb); + float b = length(texture(pgeTexture0, samplePos + vec2(0.0, pixelSize.y)).rgb); + float br = length(texture(pgeTexture0, samplePos + vec2(pixelSize.x, pixelSize.y)).rgb); + + // Apply Sobel Kernels + float sobelX = -tl + tr - 2.0 * l + 2.0 * r - bl + br; + float sobelY = -tl - 2.0 * t - tr + bl + 2.0 * b + br; + + // Calculate edge magnitude + float edge = sqrt(sobelX * sobelX + sobelY * sobelY); + + // 3) Colour bias vertically + float bias = oTex.y; + vec4 newCol = vec4( + oCol.r * bias, + oCol.g, + oCol.b * (1.0 - bias), + oCol.a + ); + + + // 4) Combine effects + + // pgeTexture0 is the primary texture unit + // pgeTexture1 is some other image + + vec4 texColour = texture(pgeTexture0, samplePos) * oCol; + vec4 otherColour = texture(pgeTexture1, samplePos); + + vec4 edgeColour = vec4(newCol * edge) + otherColour * 0.5; + pixel = vec4(edgeColour.rgb * texColour.a, texColour.a); } - - draw.Pixel({ 0.0,1 }, olc::Colour::BLACK); - - } - - float fAngle = 0.0f; + )"; - struct logo - { - olc::vf2d pos; - olc::vf2d vel; - float ang = 0; - float angvel = 0; - }; - std::vector vecLogos; - - std::vector vecVerts; - - float fScale = 1.0f; - - - int nSelectedVert = -1; - - std::shared_ptr win2; - - olc::Image imBlend; - olc::Image imTempBuffer; - - std::vector vecTestPoints; - olc::vf2d vTestPointSize = { 6,6 }; - int nSelectedPoint = -1; - -public: - bool OnUserCreate() override - { - - - CreateImage(imLowRes, { 256, 240 }); - CreateImageFromFile(imHighResSprite, "e:/voxel.png"); + // Construct pixel shader from default header, but use custom main() + shaderExample.SetPixelShaderSource( + olc::gpu::Shader::PS_DefaultHeader() + + custom_ps_main); - //CreateImage(imTest, { 64,64 }); - CreateImageFromFile(imLogo, "../tests/olc.png"); - CreateImageFromFile(imBlend, "../tests/blend.png"); - - - olc::ImageConfig cfg; - cfg.MSAA = true; - CreateImage(imTempBuffer, { 128, 128 }, cfg); - - CreateSampleTestImage(imSampleTest, { 32, 32 }); - - CreateImage(imTemp, { 64, 64 }); + // Just use the PGE3 default vertex shader + shaderExample.SetVertexShaderSource( + olc::gpu::Shader::VS_DefaultHeader() + + olc::gpu::Shader::VS_DefaultMain() + ); - size_t x = 1; - vecLogos.resize(x); - for (auto& a : vecLogos) + // Check for shader compile errors + std::string sResult = shaderExample.Compile(); + if (sResult != "OK") { - 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; + std::cout << "Error compiling shader: " << sResult << std::endl; + return false; } - vecVerts = { - { 100.0f, 100.0f}, - { 200.0f, 100.0f}, - { 200.0f, 200.0f}, - { 100.0f, 200.0f} - }; - - - vecTestPoints = { - { 16, 16}, - { 64, 16}, - { 16, 32}, - { 64, 32 }, - /*{16, 48}, - {64, 48}, - { 16, 64}, - {64, 64 }*/ - }; + // Add custom uniforms used by custom shader. The "system" + // will have taken care of required PGE3 uniforms + shaderExample.CreateUniform("frequency"); + shaderExample.CreateUniform("amplitude"); - cube = CreateSanityCube(); - - CreateImageFromFile(imSanityCube, "../../examples/assets/sanity_cube.png"); + // Create off-screen image to draw to + CreateImage(imgWithoutFX, GetScreen().Size()); + // Load a small image to draw for fun + CreateImageFromFile(imgMini, "./assets/minsanity_texture.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 }; - + // Called every frame, so update things here bool OnUserUpdate(float fElapsedTime) override { - //draw.Line({ 0,0 }, mouse.GetPosition(), olc::Colour::WHITE); - //return true; - - - if (mouse.GetButton(1).bHeld) - { - fAngle += 0.5f * fElapsedTime; - } - - - - // SW Rastering test - draw.SetTarget(imTempBuffer); - draw.Clear(olc::Colour::TANGERINE); - draw.WorldRotate(fAngle, { 64.0f, 64.0f });// imLowRes.Size() / 2.0f); - - draw.Rect({ 0,0 }, draw.GetTargetSize()-1, olc::Colour::BLACK); - - - /*draw.FilledTriangle(vecTestPoints[0] - olc::vi2d{64, 48}, vecTestPoints[1] - olc::vi2d{ 64, 48 }, vecTestPoints[2] - olc::vi2d{ 64, 48 }, - olc::Colour::RED, - olc::Colour::GREEN, - olc::Colour::BLUE); - - draw.FilledTriangle(vecTestPoints[0] - olc::vi2d{ 64, 48 }, vecTestPoints[2] - olc::vi2d{ 64, 48 }, vecTestPoints[3] - olc::vi2d{ 64, 48 }, - olc::Colour::RED, - olc::Colour::BLUE, - olc::Colour::YELLOW);*/ + // Set rendering target to off-screen image, we will draw to this + // entirely normally, then apply a custom shader when we draw the + // off-screen image to the screen + draw.SetTarget(imgWithoutFX); + // === Stolen from olcPGE3_Lines.cpp == - std::vector vecOffsetPoints(vecTestPoints.size()); - std::transform( - vecTestPoints.begin(), vecTestPoints.end(), - vecOffsetPoints.begin(), - [](const olc::vf2d& v) { return v - olc::vf2d{ 64, 48 }; }); + // Clear whole screen + draw.Clear(olc::Colour::VERY_DARK_BLUE); - std::vector vecColours = { - olc::Colour::RED, - olc::Colour::GREEN, - olc::Colour::BLUE, - olc::Colour::DARK_YELLOW, - olc::Colour::DARK_MAGENTA, - olc::Colour::DARK_CYAN, - olc::Colour::BLACK, - olc::Colour::WHITE - }; + // Simple white line + draw.Line({ 10.0f, 10.0f }, { 246.0f, 40.0f }, olc::Colour::WHITE); + // Gradient line + draw.Line({ 10.0f, 50.0f }, olc::Colour::RED, { 246.0f, 80.0f }, olc::Colour::YELLOW); - - - - //draw.Triangle(vecTestPoints[0] - olc::vi2d{ 64, 48 }, vecTestPoints[1] - olc::vi2d{ 64, 48 }, vecTestPoints[2] - olc::vi2d{ 64, 48 }, - // olc::Colour::BLACK); - - //draw.Triangle(vecTestPoints[0] - olc::vi2d{ 64, 48 }, vecTestPoints[2] - olc::vi2d{ 64, 48 }, vecTestPoints[3] - olc::vi2d{ 64, 48 }, - // olc::Colour::BLACK); - - draw.FilledRoundedRect(olc::vf2d{ 10.0f, 10.0f }, olc::vf2d{ 108.0f, 108.0f }, 10.0f, olc::Colour::BLUE, olc::Colour::WHITE, 8); - draw.RoundedRect(olc::vf2d{ 10.0f, 10.0f }, olc::vf2d{ 108.0f, 108.0f }, 10.0f, olc::Colour::WHITE, olc::Colour::WHITE, 8); - - - draw.FilledEllipse(olc::vf2d{ 64.0f, 64.0f }, 30, 20, olc::Colour::RED, olc::Pixel(255, 0, 0, 0), olc::Colour::WHITE, 16); - draw.Ellipse(olc::vf2d{ 64.0f, 64.0f }, 30, 20, olc::Colour::BLACK, olc::Colour::WHITE, 16); - - - draw.FilledCircle(olc::vf2d{ 64.0f, 64.0f }, 15, olc::Colour::GREEN, olc::Pixel(0, 255, 0, 0), olc::Colour::WHITE, 16); - draw.Circle(olc::vf2d{ 64.0f, 64.0f }, 17, olc::Colour::BLACK, olc::Colour::WHITE, 16); - - - olc::vf2d vScaledSize = olc::vf2d{ 8, 8 } / draw.GetWorldTransform().scale(); - draw.FilledRect(olc::vf2d{ 64.0f, 64.0f } - vScaledSize * 0.5, vScaledSize, olc::Pixel(255, 255, 0, 25)); - draw.Rect(olc::vf2d{ 64.0f, 64.0f } - vScaledSize * 0.5, vScaledSize, olc::Colour::BLACK); + // Rotating gradient lines + fTotalTime += fElapsedTime; + olc::vf2d p1 = { 64.0f, 160.0f }; + olc::vf2d p2 = olc::vf2d{ std::cos(fTotalTime), std::sin(fTotalTime) } * 64.0f; - draw.FilledPolygon( - olc::Structure::Strip, - vecOffsetPoints, - vecColours); + draw.Line(p1, olc::Colour::CYAN, p1 + p2, olc::Colour::MAGENTA); + draw.Line(p1, olc::Colour::CYAN, p1 - p2, olc::Colour::MAGENTA); + draw.Line(p1, olc::Colour::CYAN, p1 + olc::vf2d{ -p2.y, p2.x }, olc::Colour::MAGENTA); + draw.Line(p1, olc::Colour::CYAN, p1 + olc::vf2d{ p2.y, -p2.x }, olc::Colour::MAGENTA); - draw.Polygon( - olc::Structure::Strip, - vecOffsetPoints, - olc::Colour::BLACK - ); + // Line to mouse pointer with tint + float alpha = (std::sin(fTotalTime * 4.0f) + 1.0f) / 2.0f; + draw.Line(p1, mouse.GetPosition(), olc::Colour::WHITE, olc::PixelF(1.0f, 0.0f, 0.0f, alpha)); - /* draw.TexturedTriangle( - vecTestPoints[0] - olc::vi2d{ 64, 48 }, vecTestPoints[1] - olc::vi2d{ 64, 48 }, vecTestPoints[2] - olc::vi2d{ 64, 48 }, - olc::Colour::RED, - olc::Colour::GREEN, - olc::Colour::BLUE, - { 0.0f, 0.0f }, { 0.0f, 1.0f }, { 1.0f, 0.0f }, - imLogo);*/ + // Boundary box (with fixed coordinates) + draw.Line({ 0.0f, 0.0f }, { 255.0f, 0.0f }, olc::Colour::DARK_GREY); + draw.Line({ 255.0f, 0.0f }, { 255.0f, 239.0f }, olc::Colour::DARK_GREY); + draw.Line({ 255.0f, 239.0f }, { 0.0f, 239.0f }, olc::Colour::DARK_GREY); + draw.Line({ 0.0f, 239.0f }, { 0.0f, 0.0f }, olc::Colour::DARK_GREY); + // === End stolen code === + // Draws an image for the fun of it + draw.Image(imgMini, mouse.GetPosition()); + // Copy image to screen with new shader draw.SetTarget(GetScreen()); - draw.Clear(olc::Colour::CYAN); - - /*draw.Triangle(vecTestPoints[0], vecTestPoints[1], vecTestPoints[2], - olc::Colour::RED, - olc::Colour::GREEN, - olc::Colour::BLUE); - - draw.Triangle(vecTestPoints[0], vecTestPoints[2], vecTestPoints[3], - olc::Colour::RED, - olc::Colour::BLUE, - olc::Colour::YELLOW);*/ - - draw.Polygon( - olc::Structure::Strip, - vecTestPoints, - vecColours); - - - //draw.Image(imTempBuffer, { 64, 48 }); - draw.ImageQuad(imTempBuffer, vecTestPoints); - //draw.Triangle(vecTestPoints[0], vecTestPoints[1], vecTestPoints[2], olc::Colour::BLACK); - - - - // Draw 3 triangle points - for (int i = 0; i < vecTestPoints.size(); i++) - { - //draw.Rect(vecTestPoints[i] - (vTestPointSize * 0.5f), vTestPointSize, olc::Colour::MAGENTA); - - draw.FilledCircle(vecTestPoints[i],4, olc::Colour::GREEN, olc::Pixel(0,255,0,0), olc::Colour::WHITE, 16); - draw.Circle(vecTestPoints[i], 4, olc::Colour::BLACK, olc::Colour::WHITE, 16); - } - - - - 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); - 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; - - 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); - - draw.SetProjectionMatrix(matProj); - draw.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); - 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)); - 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); - - 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); - - - // Handle mouse - if (mouse.GetButton(0).bPressed) - { - nSelectedPoint = -1; - for (int i = 0; i < vecTestPoints.size(); i++) - { - if ((mouse.GetPosition() - vecTestPoints[i]).mag2() < 9) - { - nSelectedPoint = i; - } - } - } - - if (nSelectedPoint != -1 && mouse.GetButton(0).bHeld) - { - vecTestPoints[nSelectedPoint] = mouse.GetPosition().round(); - } - - if (mouse.GetButton(0).bReleased) - { - nSelectedPoint = -1; - } - - draw.String({ 10, 10 }, mouse.GetPosition().str(), olc::Colour::BLACK); - - if (keyboard.GetKey(olc::Key::ESCAPE).bPressed) - return false; // End application - - return true; - - - //draw.SetTarget(imTemp); - //draw.Clear(olc::Colour::BLANK); - //draw.FilledRect({ 20,5 }, { 6, 54 }, olc::Colour::GREEN); - //draw.FilledRect({ 0,0 }, imTemp.Size(), olc::Colour::BLANK); - - - //draw.SetTarget(imgPrimary); - //draw.Clear(olc::Colour::BLUE); - - - //draw.WorldReset(); - //draw.FilledRect({ 0,0 }, imgPrimary.Size() * olc::vf2d(0.5f, 1.0f), olc::Pixel(0, 0, 0, 1)); - - - // draw.SetTarget(imTempBuffer); - // draw.Clear(olc::Colour::WHITE); - // draw.Image(imBlend, { 0,0 }); - - - // draw.SetTarget(GetDefaultImage()); - // draw.WorldReset(); - // draw.Clear(olc::Colour::TANGERINE); - // //draw.Image(imHighResSprite, mouse.GetPosition()); - - - // //draw.SetTarget(imLowRes); - - // //draw.Clear(olc::Colour::BLANK); - - // if (mouse.GetButton(1).bHeld) - // { - // fAngle += 0.2f * fElapsedTime; - // } - - // draw.WorldRotate(fAngle, { 0,0 });// imLowRes.Size() / 2.0f); - // if (mouse.GetButton(2).bPressed) - // { - // auto w = std::make_shared(); - // //AddChildWindow(w, { 100,100 }, { 2, 2 }); - // } - - // if (mouse.GetWheel() > 0) - // { - // fScale *= 1.1f; - // } - - // if (mouse.GetWheel() < 0) - // { - // fScale *= 0.9f; - // } - - // draw.WorldScale({ fScale, fScale }); - // //for (int x = 0; x < imgPrimary.Size().x; x++) - // // for (int y = 0; y < imgPrimary.Size().y; y++) - // // draw.Pixel(olc::vf2d( x, y ), olc::Pixel(rand() % 255, rand() % 255, rand() % 255)); - - // //// - // //return true; - - - - - // //std::cout << mouse.GetPosition() << "\n"; - // auto vMouse = draw.ScreenToWorld(mouse.GetPosition() ); - - // if (mouse.GetButton(0).bPressed) - // { - // float dist = 100000.0f; - // int idx = -1; - // for (int i = 0; i < 4; i++) - // { - // float d = (vecVerts[i] - vMouse).mag(); - // if (d < 8 && d < dist) - // { - // dist = d; - // idx = i; - // } - // } - // nSelectedVert = idx; - // } - - // - - // if (nSelectedVert != -1 && mouse.GetButton(0).bHeld) - // vecVerts[nSelectedVert] = vMouse; - - // if (mouse.GetButton(0).bReleased) - // nSelectedVert = -1; - - //// draw.ImageQuad(imLogo.region({ 0,0 }, { 10,10 }), vecVerts); - - //// draw.Line(vecVerts[0], vecVerts[1], olc::Colour::MAGENTA); - //// draw.Line(vecVerts[1], vecVerts[2], olc::Colour::MAGENTA); - //// draw.Line(vecVerts[2], vecVerts[3], olc::Colour::MAGENTA); - //// draw.Line(vecVerts[3], vecVerts[0], olc::Colour::MAGENTA); - - //// //draw.Image(imTemp, vMouse, { 4,4 }); - - //// - - //////draw.Image(olc::fontClassicPGE.imgFont, vMouse); - - //// - - //// std::string sTest = "Hello World!\nThe quick brown fox JUMPS over the LaZy dog..."; - //// olc::vf2d vSizeMono = draw.GetTextSize(sTest, false, { 1, 2 }); - //// olc::vf2d vSizeProp = draw.GetTextSize(sTest, true, { 1, 2 }); - - - //// draw.String({ 10.0f, 10.0f }, "Hello World!\nThe quick brown fox JUMPS over the LaZy dog...", olc::Colour::WHITE, { 1, 2 }); - //// draw.Rect({ 10.0f, 10.0f }, vSizeMono, olc::Colour::GREEN); - - //// draw.StringProp({ 10.0f, 100.0f }, "Hello World!\nThe quick brown fox JUMPS over the LaZy dog...", olc::Colour::BLUE, { 1, 2 }); - //// draw.Rect({ 10.0f, 100.0f }, vSizeProp, olc::Colour::GREEN); - - //// draw.StringProp({ 10.0f, 200.0f }, "Hello World!\nThe quick brown fox JUMPS over the LaZy dog...", olc::PixelF(0.5f, 0.0f, 0.0f, 0.25f), {1, 2}); - //// draw.Rect({ 10.0f, 200.0f }, vSizeProp, olc::Colour::GREEN); - - //// //draw.Pixel(vMouse, olc::Colour::GREEN); - //// //draw.Line(vMouse, vMouse + 1, olc::Colour::GREEN); - //// - //// draw.FilledRect({ 5, 100 }, { 100,100 }, olc::Colour::TANGERINE, olc::Colour::DARK_CYAN, olc::Colour::RED, olc::Colour::GREEN); - //// - //// draw.FilledRect({ 5,5 }, { 10,10 }, olc::Colour::YELLOW); - - - - ////// draw.Rect({ 8,8 }, { 20,20 }, olc::Colour::RED); - - //// if(mouse.GetButton(0).bHeld) - //// draw.Line({ 1.0f, 1.0f }, { 25.5f, 25.5f }); - - - - //// //draw.Image(imTempBuffer, vMouse); - //// draw.Image(imSampleTest, vMouse); - //// //draw.SetTarget(imgPrimary); - //// //draw.WorldReset(); - //// //draw.Image(imLowRes, { 0,0 }, { 4, 4 }); - - - //// draw.swLine({ 100,100 }, vMouse, olc::Colour::BLACK, olc::Colour::YELLOW); - - - //// - - //// draw.Rect({ 8,8, }, { 20,20 }, olc::Colour::RED, olc::Colour::GREEN, olc::Colour::BLUE, olc::Colour::WHITE); - //// draw.swRect({ 7,7, }, { 22,22 }, olc::Colour::RED, olc::Colour::GREEN, olc::Colour::BLUE, olc::Colour::WHITE); - - // for (int i = 0; i < 100; i++) - // { - // // Draw random triangle - // draw.swTexturedTriangle( - // { float(rand() % GetDefaultImage().Size().x), float(rand() % GetDefaultImage().Size().y) }, - // { float(rand() % GetDefaultImage().Size().x), float(rand() % GetDefaultImage().Size().y) }, - // { float(rand() % GetDefaultImage().Size().x), float(rand() % GetDefaultImage().Size().y) }, - // olc::Pixel(rand() % 256, rand() % 256, rand() % 256), - // olc::Pixel(rand() % 256, rand() % 256, rand() % 256), - // olc::Pixel(rand() % 256, rand() % 256, rand() % 256), { 0,0 }, { 0,1 }, { 1, 1 }, imLogo - // ); - // } - - - // //std::cout << "Mouse Fil: " << vMouse << "\n"; - // draw.swFilledTriangle({ 30,30 }, { 250,50 }, vMouse, olc::Colour::RED, olc::Colour::GREEN, olc::Colour::BLUE); - // //std::cout << "Mouse Fil: " << vMouse << "\n"; - // draw.swFilledTriangle({ 30,30 }, { 250,50 }, vMouse, olc::Colour::WHITE); - // //std::cout << "Mouse Tex: " << vMouse << "\n"; - // draw.swTexturedTriangle({ 30,30 }, { 250,50 }, vMouse, olc::Colour::RED, olc::Colour::GREEN, olc::Colour::BLUE, { 0,0 }, { 0,1 }, { 1, 1 }, imHighResSprite); - - // draw.swTriangle({ 30,30 }, { 250,50 }, vMouse, olc::Colour::RED, olc::Colour::GREEN, olc::Colour::BLUE); - // - - // //draw.Rect(vMouse, { 100,100 }); - - // /*for (auto& a : vecLogos) - // { - // a.pos += a.vel * fElapsedTime; - // a.ang += a.angvel * fElapsedTime; - - // if (a.pos.x >= imgPrimary.Size().x - 46) - // { - // a.pos.x = imgPrimary.Size().x - 46; - // a.vel.x *= -1.0f; - // } - - // if (a.pos.y >= imgPrimary.Size().y - 28) - // { - // a.pos.y = imgPrimary.Size().y - 28; - // a.vel.y *= -1.0f; - // } - - // if (a.pos.x < 0) - // { - // a.pos.x = 0; - // a.vel.x *= -1.0f; - // } - - // if (a.pos.y < 0) - // { - // a.pos.y = 0; - // a.vel.y *= -1.0f; - // } - - // draw.ImageRotated(imLogo, a.pos, a.ang, { 10,10 }, { 2.0f, 4.0f }); - // }*/ - // - - - // /*draw.SetTarget(imTest); - // draw.FilledRect({ 0,0 }, imTest.Size(), olc::Colour::BLUE); - - // draw.AffineRotate(fAngle, { 32.0f, 32.0f }); - // - // draw.Rect({ 5,5 }, { 10,10 }, olc::Colour::YELLOW); - // draw.Line({ 0,0 }, { 20, 20 }); - - - // draw.SetTarget(imgPrimary); - // draw.AffineReset(); - - // draw.FilledRect({ 0,0 }, imgPrimary.Size(), olc::Colour::VERY_DARK_MAGENTA); - - // draw.Line({ 0,0 }, imgPrimary.Size() - 1, olc::Colour::RED); - // draw.Line(olc::vf2d( imgPrimary.Size().x - 1, 0 ), olc::vf2d( 0, imgPrimary.Size().y - 1 ), olc::Colour::GREEN); - // - // if(mouse.GetButton(0).bHeld) - // draw.Line({ 0,0 }, mouse.GetPosition(), olc::Colour::TANGERINE); - - // draw.AffineRotate(fAngle * 0.2f, imgPrimary.Size() * 0.5f); - // draw.Rect({ 0,0 }, imgPrimary.Size()); - // draw.Image(imTest, { 10, 10 }, { 64, 64 }); - // draw.Image(imTest, { 100, 10 }, { 64, 64 }); - // draw.Image(imTest, { 10, 100 }, { 64, 64 }); - - // draw.Image(sprite1, { 100, 100 }, { 64, 64 }); - // draw.Image(sprite1.region({ 20.0f, 20.0f }, { 32.0f, 32.0f }), { 200, 100 }, { 32, 32 }); - // draw.Image(sprite1.region({ 20.0f, 20.0f }, { 32.0f, 32.0f }, { 100.0f, 200.0f }, { 150.0f, 180.0f }), { 100, 200 }, { 32, 32 }); - // olc::Pixel p = draw.GetPixel(imTest, { 8,5 }); - - // draw.FilledRect({ 200,200 }, { 10,10 }, p);*/ - - // ////draw.AffineRotate(fAngle, { 128,120 }); - // ////draw.AffineOffset({ 10,10 }); - // //draw.AffineScale(olc::vf2d(1,1) * (std::sin(fAngle) + 1.1)); - - // //draw.Rect({ 0,0 }, GetSize() - olc::vf2d{1, 1}, olc::Colour::CYAN); - // //draw.FillRect({ 100.0f, 30.0f }, { 20.0f, 50.0f }, olc::Colour::TANGERINE); - - // - - // //for (float i = 0; i < 20; i++) - // //{ - // // draw.AffineRotate(fAngle + (i/50.0f), {128,120}); - // // if (fmod(i,2) == 1) - // // { - // // draw.Line({ 0 + i * 4 ,0 + i * 4 }, { 255 - i * 4, 0 + i * 4 }, olc::Colour::RED, olc::Colour::GREEN); - // // draw.Line({ 255 - i * 4,0 + i * 4 }, { 255 - i * 4, 239 - i * 4 }, olc::Colour::GREEN, olc::Colour::CYAN); - // // draw.Line({ 255 - i * 4,239 - i * 4 }, { 0 + i * 4, 239 - i * 4 }, olc::Colour::CYAN, olc::Colour::WHITE); - // // draw.Line({ 0 + i * 4,239 - i * 4 }, { 0 + i * 4, 0 + i * 4 }, olc::Colour::WHITE, olc::Colour::RED); - // // } - // // else - // // { - // // draw.Line({ 0 + i * 4 ,0 + i * 4 }, { 255 - i * 4, 0 + i * 4 }, olc::Colour::RED); - // // draw.Line({ 255 - i * 4,0 + i * 4 }, { 255 - i * 4, 239 - i * 4 }, olc::Colour::GREEN); - // // draw.Line({ 255 - i * 4,239 - i * 4 }, { 0 + i * 4, 239 - i * 4 }, olc::Colour::CYAN); - // // draw.Line({ 0 + i * 4,239 - i * 4 }, { 0 + i * 4, 0 + i * 4 }, olc::Colour::WHITE); - // // } - // //} - - // ///*for (int x = 0; x < 100; x++) - // //{ - // // for (int y = 0; y < 100; y++) - // // { - // // if (x % 2 == 0) - // // { - // // draw.Pixel(olc::vf2d(x, y) + olc::vf2d(100, 100), olc::Colour::YELLOW); - // // } - // // } - // //}*/ - - // ////draw.Pixel({ -0.5f, -0.25f }, olc::Colour::YELLOW); - - // //draw.FillRect({ 100.0f, 30.0f }, { 20.0f, 50.0f }, olc::Colour::TANGERINE); - - // ////draw.Pixel({ 0.3f, 0.3f }, olc::Colour::BLACK); - // ////draw.Pixel({ 0.32f, 0.3f }, olc::Colour::BLACK); - // ////draw.Pixel({ 0.34f, 0.3f }, olc::Colour::BLACK); - - - - + // Set the custom shader + draw.SetShader(shaderExample); + + // Set custom parameters for the shader based + // on mouse position + float frequency = (mouse.GetPosition().x / 256.0f) * 0.1f; + float amplitude = (mouse.GetPosition().y / 240.0f) * 0.1f; + draw.SetShaderUniform("frequency", frequency); + draw.SetShaderUniform("amplitude", amplitude); + + // Our shader uses 2 texture units so we need to bind them here + // By default, unit 0 is bound to the primary texture (imgWithoutFX) + draw.SetShaderTexture(1, imgMini); // Bind imgMini to texture unit 1 + + // Present image with shader effect - this is required + // because here is where the shader is actually applied + draw.Image(imgWithoutFX, { 0, 0 }); + + // Successful frame return true; } }; + +// Main entry point for the application int main() { - Example demo; - //if (demo.Construct({ 256, 240 }, { 4, 4 })) - - olc::PGEConfig cfg; - cfg.vPixelSize = { 1,1 }; - cfg.vScreenSize = { 1024, 960 }; - - cfg.vPixelSize = { 4,4 }; - cfg.vScreenSize = { 256, 240 }; - //cfg.bAntiAliasMainScreen = true; - cfg.bVSync = false; - - //if (demo.Construct({ 1280, 960 }, { 1, 1 }, cfg)) - if(demo.Construct(cfg)) + // Construct demo application + Example_CustomPixelShader 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; } From 3d02440fa9174f4e40ec0779591363f49ea3049c Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Tue, 24 Feb 2026 15:27:13 -0500 Subject: [PATCH 072/101] [headless] remove window title printout --- dev/src/host_none.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/dev/src/host_none.cpp b/dev/src/host_none.cpp index d62a82bc..0e28f2f4 100644 --- a/dev/src/host_none.cpp +++ b/dev/src/host_none.cpp @@ -18,7 +18,6 @@ namespace olc::host // Make OS Update a window frame title, associated with olc::Window bool Host_None::UpdateWindowFrameTitle(olc::Window* pWindow) { - std::cout << pWindow->GetWindowTitle() << "\n"; return true; } From 8bf56a797adeb946216d275abfec7f5298e35bbd Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Tue, 24 Feb 2026 19:07:25 -0500 Subject: [PATCH 073/101] [headless][mh] separate MacOS API files from host files --- dev/tests/CMakeLists.txt | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/dev/tests/CMakeLists.txt b/dev/tests/CMakeLists.txt index 54bc2351..20a76baf 100644 --- a/dev/tests/CMakeLists.txt +++ b/dev/tests/CMakeLists.txt @@ -31,10 +31,14 @@ 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 + +set(MACOS_API_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 +) + +set(HOST_MACOS_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/../src/host_apple_macos.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../src/host_apple_macos.h ) @@ -90,6 +94,7 @@ list( ${GPU_OPENGL33_SOURCE_FILES} ${HOST_HEADLESS_SOURCE_FILES} ${HOST_ANDROID_SOURCE_FILES} + ${MACOS_API_FILES} ${HOST_MACOS_SOURCE_FILES} ${HOST_LINUX_WAYLAND_SOURCE_FILES} ${HOST_LINUX_X11_SOURCE_FILES} @@ -117,7 +122,11 @@ else() if(CMAKE_SYSTEM_NAME STREQUAL "iOS") # TODO: Building for iOS else() - set(HOST_SOURCE_FILES ${HOST_MACOS_SOURCE_FILES}) + if(NOT USE_HEADLESS) + set(HOST_SOURCE_FILES ${HOST_MACOS_SOURCE_FILES} ${MACOS_API_FILES}) + else() + set(HOST_SOURCE_FILES ${HOST_MACOS_SOURCE_FILES}) + endif() endif() # MacOS endif() # Apple @@ -150,7 +159,11 @@ else() if(CMAKE_SYSTEM_NAME STREQUAL "iOS") # TODO: Building for iOS else() - set(IMAGELOADER_SOURCE_FILES ${IMAGELOADER_MACOS_SOURCE_FILES}) + if(USE_HEADLESS) + set(IMAGELOADER_SOURCE_FILES ${IMAGELOADER_MACOS_SOURCE_FILES} ${MACOS_API_FILES}) + else() + set(IMAGELOADER_SOURCE_FILES ${IMAGELOADER_MACOS_SOURCE_FILES}) + endif() endif() endif() From b8c06f2d7bf26a087a0f61b2a5e8d1b3db57b47e Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Tue, 24 Feb 2026 19:09:38 -0500 Subject: [PATCH 074/101] [headless][mh] restore original test_mh, accidental overwrite --- dev/tests/test_mh.cpp | 890 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 737 insertions(+), 153 deletions(-) diff --git a/dev/tests/test_mh.cpp b/dev/tests/test_mh.cpp index 7a015f7b..a58b860f 100644 --- a/dev/tests/test_mh.cpp +++ b/dev/tests/test_mh.cpp @@ -1,206 +1,790 @@ #define OLC_PGE3_APPLICATION #include "olcpge3.h" -// Example application demonstrating a custom pixel shader effect. -// This class overrides the olc::PixelGameEngine base class by -// implementing the OnUserCreate() and OnUserUpdate() functions -class Example_CustomPixelShader : public olc::PixelGameEngine + +class SecondWindow : public olc::PGEWindow { public: - Example_CustomPixelShader() + + // Return true if window is to continue + bool OnUserCreate() override + { + + return false; + } + + // Return true if window is to continue + bool OnUserUpdate(float fElapsedTime) override { + fTotalTime += fElapsedTime; + draw.Clear(olc::Colour::RED); + draw.WorldRotate(fTotalTime, GetScreen().Size() / 2); + draw.FilledRect({ 10,10 }, { 20,20 }, olc::Colour::BLUE); + return true; } -protected: - // We accumulate total time for some animation - float fTotalTime = 0.0f; - olc::gpu::OLC_SHADER_CLASS shaderExample; - olc::Image imgWithoutFX; + float fTotalTime = 0; +}; - olc::Image imgMini; +class Example : public olc::PixelGameEngine +{ public: - // Called once at the start, so create things here - bool OnUserCreate() override + Example() { - // Custom pixel shader main() function and uniforms - std::string custom_ps_main = - R"( - uniform float frequency; - uniform float amplitude; - void main() + } + + olc::Image imTest; + + olc::Image imLogo; + olc::Image imTemp; + + olc::Image imSampleTest; + + olc::Image imLowRes; + olc::Image imHighResSprite; + + void CreateSampleTestImage(olc::Image& image, const olc::vi2d& s) + { + CreateImage(image, s); + draw.SetTarget(image); + + draw.Clear(olc::Colour::WHITE); + + std::vector vColours = { + olc::Colour::RED, + olc::Colour::YELLOW, + olc::Colour::GREEN, + olc::Colour::CYAN, + olc::Colour::BLUE, + olc::Colour::MAGENTA, + }; + + for (int i = 0; i < s.x/2; i++) { - // 1) Horizontal Wobble - vec2 samplePos = vec2( - oTex.x + sin(oTex.y * frequency * pgeTargetSizeInPixels.y + pgeTotalTimeElapsed * 2.0) * amplitude, - oTex.y - ); - - - - // 2) Sobel Edge Detection - - // Sample 3x3 neighborhood for Sobel - vec2 pixelSize = pgeInverseTargetSizeInPixels; - - float tl = length(texture(pgeTexture0, samplePos + vec2(-pixelSize.x, -pixelSize.y)).rgb); - float t = length(texture(pgeTexture0, samplePos + vec2(0.0, -pixelSize.y)).rgb); - float tr = length(texture(pgeTexture0, samplePos + vec2(pixelSize.x, -pixelSize.y)).rgb); - float l = length(texture(pgeTexture0, samplePos + vec2(-pixelSize.x, 0.0)).rgb); - float r = length(texture(pgeTexture0, samplePos + vec2(pixelSize.x, 0.0)).rgb); - float bl = length(texture(pgeTexture0, samplePos + vec2(-pixelSize.x, pixelSize.y)).rgb); - float b = length(texture(pgeTexture0, samplePos + vec2(0.0, pixelSize.y)).rgb); - float br = length(texture(pgeTexture0, samplePos + vec2(pixelSize.x, pixelSize.y)).rgb); - - // Apply Sobel Kernels - float sobelX = -tl + tr - 2.0 * l + 2.0 * r - bl + br; - float sobelY = -tl - 2.0 * t - tr + bl + 2.0 * b + br; - - // Calculate edge magnitude - float edge = sqrt(sobelX * sobelX + sobelY * sobelY); - - // 3) Colour bias vertically - float bias = oTex.y; - vec4 newCol = vec4( - oCol.r * bias, - oCol.g, - oCol.b * (1.0 - bias), - oCol.a - ); - - - // 4) Combine effects - - // pgeTexture0 is the primary texture unit - // pgeTexture1 is some other image - - vec4 texColour = texture(pgeTexture0, samplePos) * oCol; - vec4 otherColour = texture(pgeTexture1, samplePos); - - vec4 edgeColour = vec4(newCol * edge) + otherColour * 0.5; - pixel = vec4(edgeColour.rgb * texColour.a, texColour.a); + draw.Rect(olc::vi2d{ i,i }, s - olc::vi2d{ (i * 2)+1 ,(i * 2)+1 }, vColours[i % vColours.size()]); } - )"; + draw.Pixel({ 0.0,1 }, olc::Colour::BLACK); + + } - // Construct pixel shader from default header, but use custom main() - shaderExample.SetPixelShaderSource( - olc::gpu::Shader::PS_DefaultHeader() + - custom_ps_main); + float fAngle = 0.0f; - // Just use the PGE3 default vertex shader - shaderExample.SetVertexShaderSource( - olc::gpu::Shader::VS_DefaultHeader() + - olc::gpu::Shader::VS_DefaultMain() - ); - // Check for shader compile errors - std::string sResult = shaderExample.Compile(); - if (sResult != "OK") + struct logo + { + olc::vf2d pos; + olc::vf2d vel; + float ang = 0; + float angvel = 0; + }; + std::vector vecLogos; + + std::vector vecVerts; + + float fScale = 1.0f; + + + int nSelectedVert = -1; + + std::shared_ptr win2; + + olc::Image imBlend; + olc::Image imTempBuffer; + + std::vector vecTestPoints; + olc::vf2d vTestPointSize = { 6,6 }; + int nSelectedPoint = -1; + +public: + bool OnUserCreate() override + { + + + CreateImage(imLowRes, { 256, 240 }); + CreateImageFromFile(imHighResSprite, "e:/voxel.png"); + + //CreateImage(imTest, { 64,64 }); + CreateImageFromFile(imLogo, "../tests/olc.png"); + CreateImageFromFile(imBlend, "../tests/blend.png"); + + + olc::ImageConfig cfg; + cfg.MSAA = true; + CreateImage(imTempBuffer, { 128, 128 }, cfg); + + CreateSampleTestImage(imSampleTest, { 32, 32 }); + + CreateImage(imTemp, { 64, 64 }); + + size_t x = 1; + vecLogos.resize(x); + for (auto& a : vecLogos) { - std::cout << "Error compiling shader: " << sResult << std::endl; - return false; + 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; } - // Add custom uniforms used by custom shader. The "system" - // will have taken care of required PGE3 uniforms - shaderExample.CreateUniform("frequency"); - shaderExample.CreateUniform("amplitude"); + vecVerts = { + { 100.0f, 100.0f}, + { 200.0f, 100.0f}, + { 200.0f, 200.0f}, + { 100.0f, 200.0f} + }; + + + vecTestPoints = { + { 16, 16}, + { 64, 16}, + { 16, 32}, + { 64, 32 }, + /*{16, 48}, + {64, 48}, + { 16, 64}, + {64, 64 }*/ + }; - // Create off-screen image to draw to - CreateImage(imgWithoutFX, GetScreen().Size()); + cube = CreateSanityCube(); + + CreateImageFromFile(imSanityCube, "../../examples/assets/sanity_cube.png"); - // Load a small image to draw for fun - CreateImageFromFile(imgMini, "./assets/minsanity_texture.png"); return true; } - // Called every frame, so update things here + 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 { - // Set rendering target to off-screen image, we will draw to this - // entirely normally, then apply a custom shader when we draw the - // off-screen image to the screen - draw.SetTarget(imgWithoutFX); + //draw.Line({ 0,0 }, mouse.GetPosition(), olc::Colour::WHITE); + //return true; - // === Stolen from olcPGE3_Lines.cpp == - // Clear whole screen - draw.Clear(olc::Colour::VERY_DARK_BLUE); + if (mouse.GetButton(1).bHeld) + { + fAngle += 0.5f * fElapsedTime; + } - // Simple white line - draw.Line({ 10.0f, 10.0f }, { 246.0f, 40.0f }, olc::Colour::WHITE); + - // Gradient line - draw.Line({ 10.0f, 50.0f }, olc::Colour::RED, { 246.0f, 80.0f }, olc::Colour::YELLOW); + // SW Rastering test + draw.SetTarget(imTempBuffer); + draw.Clear(olc::Colour::TANGERINE); + draw.WorldRotate(fAngle, { 64.0f, 64.0f });// imLowRes.Size() / 2.0f); - // Rotating gradient lines - fTotalTime += fElapsedTime; - olc::vf2d p1 = { 64.0f, 160.0f }; - olc::vf2d p2 = olc::vf2d{ std::cos(fTotalTime), std::sin(fTotalTime) } * 64.0f; + draw.Rect({ 0,0 }, draw.GetTargetSize()-1, olc::Colour::BLACK); - draw.Line(p1, olc::Colour::CYAN, p1 + p2, olc::Colour::MAGENTA); - draw.Line(p1, olc::Colour::CYAN, p1 - p2, olc::Colour::MAGENTA); - draw.Line(p1, olc::Colour::CYAN, p1 + olc::vf2d{ -p2.y, p2.x }, olc::Colour::MAGENTA); - draw.Line(p1, olc::Colour::CYAN, p1 + olc::vf2d{ p2.y, -p2.x }, olc::Colour::MAGENTA); - // Line to mouse pointer with tint - float alpha = (std::sin(fTotalTime * 4.0f) + 1.0f) / 2.0f; - draw.Line(p1, mouse.GetPosition(), olc::Colour::WHITE, olc::PixelF(1.0f, 0.0f, 0.0f, alpha)); + /*draw.FilledTriangle(vecTestPoints[0] - olc::vi2d{64, 48}, vecTestPoints[1] - olc::vi2d{ 64, 48 }, vecTestPoints[2] - olc::vi2d{ 64, 48 }, + olc::Colour::RED, + olc::Colour::GREEN, + olc::Colour::BLUE); + + draw.FilledTriangle(vecTestPoints[0] - olc::vi2d{ 64, 48 }, vecTestPoints[2] - olc::vi2d{ 64, 48 }, vecTestPoints[3] - olc::vi2d{ 64, 48 }, + olc::Colour::RED, + olc::Colour::BLUE, + olc::Colour::YELLOW);*/ + + + std::vector vecOffsetPoints(vecTestPoints.size()); + std::transform( + vecTestPoints.begin(), vecTestPoints.end(), + vecOffsetPoints.begin(), + [](const olc::vf2d& v) { return v - olc::vf2d{ 64, 48 }; }); + + std::vector vecColours = { + olc::Colour::RED, + olc::Colour::GREEN, + olc::Colour::BLUE, + olc::Colour::DARK_YELLOW, + olc::Colour::DARK_MAGENTA, + olc::Colour::DARK_CYAN, + olc::Colour::BLACK, + olc::Colour::WHITE + }; + + + + + + //draw.Triangle(vecTestPoints[0] - olc::vi2d{ 64, 48 }, vecTestPoints[1] - olc::vi2d{ 64, 48 }, vecTestPoints[2] - olc::vi2d{ 64, 48 }, + // olc::Colour::BLACK); + + //draw.Triangle(vecTestPoints[0] - olc::vi2d{ 64, 48 }, vecTestPoints[2] - olc::vi2d{ 64, 48 }, vecTestPoints[3] - olc::vi2d{ 64, 48 }, + // olc::Colour::BLACK); + + draw.FilledRoundedRect(olc::vf2d{ 10.0f, 10.0f }, olc::vf2d{ 108.0f, 108.0f }, 10.0f, olc::Colour::BLUE, olc::Colour::WHITE, 8); + draw.RoundedRect(olc::vf2d{ 10.0f, 10.0f }, olc::vf2d{ 108.0f, 108.0f }, 10.0f, olc::Colour::WHITE, olc::Colour::WHITE, 8); + + + draw.FilledEllipse(olc::vf2d{ 64.0f, 64.0f }, 30, 20, olc::Colour::RED, olc::Pixel(255, 0, 0, 0), olc::Colour::WHITE, 16); + draw.Ellipse(olc::vf2d{ 64.0f, 64.0f }, 30, 20, olc::Colour::BLACK, olc::Colour::WHITE, 16); + + + draw.FilledCircle(olc::vf2d{ 64.0f, 64.0f }, 15, olc::Colour::GREEN, olc::Pixel(0, 255, 0, 0), olc::Colour::WHITE, 16); + draw.Circle(olc::vf2d{ 64.0f, 64.0f }, 17, olc::Colour::BLACK, olc::Colour::WHITE, 16); + + + olc::vf2d vScaledSize = olc::vf2d{ 8, 8 } / draw.GetWorldTransform().scale(); + draw.FilledRect(olc::vf2d{ 64.0f, 64.0f } - vScaledSize * 0.5, vScaledSize, olc::Pixel(255, 255, 0, 25)); + draw.Rect(olc::vf2d{ 64.0f, 64.0f } - vScaledSize * 0.5, vScaledSize, olc::Colour::BLACK); + + draw.FilledPolygon( + olc::Structure::Strip, + vecOffsetPoints, + vecColours); + + draw.Polygon( + olc::Structure::Strip, + vecOffsetPoints, + olc::Colour::BLACK + ); - // Boundary box (with fixed coordinates) - draw.Line({ 0.0f, 0.0f }, { 255.0f, 0.0f }, olc::Colour::DARK_GREY); - draw.Line({ 255.0f, 0.0f }, { 255.0f, 239.0f }, olc::Colour::DARK_GREY); - draw.Line({ 255.0f, 239.0f }, { 0.0f, 239.0f }, olc::Colour::DARK_GREY); - draw.Line({ 0.0f, 239.0f }, { 0.0f, 0.0f }, olc::Colour::DARK_GREY); - // === End stolen code === + /* draw.TexturedTriangle( + vecTestPoints[0] - olc::vi2d{ 64, 48 }, vecTestPoints[1] - olc::vi2d{ 64, 48 }, vecTestPoints[2] - olc::vi2d{ 64, 48 }, + olc::Colour::RED, + olc::Colour::GREEN, + olc::Colour::BLUE, + { 0.0f, 0.0f }, { 0.0f, 1.0f }, { 1.0f, 0.0f }, + imLogo);*/ - // Draws an image for the fun of it - draw.Image(imgMini, mouse.GetPosition()); - // Copy image to screen with new shader draw.SetTarget(GetScreen()); + draw.Clear(olc::Colour::CYAN); + + /*draw.Triangle(vecTestPoints[0], vecTestPoints[1], vecTestPoints[2], + olc::Colour::RED, + olc::Colour::GREEN, + olc::Colour::BLUE); + + draw.Triangle(vecTestPoints[0], vecTestPoints[2], vecTestPoints[3], + olc::Colour::RED, + olc::Colour::BLUE, + olc::Colour::YELLOW);*/ + + draw.Polygon( + olc::Structure::Strip, + vecTestPoints, + vecColours); + + + //draw.Image(imTempBuffer, { 64, 48 }); + draw.ImageQuad(imTempBuffer, vecTestPoints); + //draw.Triangle(vecTestPoints[0], vecTestPoints[1], vecTestPoints[2], olc::Colour::BLACK); + + + + // Draw 3 triangle points + for (int i = 0; i < vecTestPoints.size(); i++) + { + //draw.Rect(vecTestPoints[i] - (vTestPointSize * 0.5f), vTestPointSize, olc::Colour::MAGENTA); + + draw.FilledCircle(vecTestPoints[i],4, olc::Colour::GREEN, olc::Pixel(0,255,0,0), olc::Colour::WHITE, 16); + draw.Circle(vecTestPoints[i], 4, olc::Colour::BLACK, olc::Colour::WHITE, 16); + } + + + + 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); + 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; + + 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); + + draw.SetProjectionMatrix(matProj); + draw.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); + 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)); + 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); + + 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); + - // Set the custom shader - draw.SetShader(shaderExample); - - // Set custom parameters for the shader based - // on mouse position - float frequency = (mouse.GetPosition().x / 256.0f) * 0.1f; - float amplitude = (mouse.GetPosition().y / 240.0f) * 0.1f; - draw.SetShaderUniform("frequency", frequency); - draw.SetShaderUniform("amplitude", amplitude); - - // Our shader uses 2 texture units so we need to bind them here - // By default, unit 0 is bound to the primary texture (imgWithoutFX) - draw.SetShaderTexture(1, imgMini); // Bind imgMini to texture unit 1 - - // Present image with shader effect - this is required - // because here is where the shader is actually applied - draw.Image(imgWithoutFX, { 0, 0 }); - - // Successful frame + + // Handle mouse + if (mouse.GetButton(0).bPressed) + { + nSelectedPoint = -1; + for (int i = 0; i < vecTestPoints.size(); i++) + { + if ((mouse.GetPosition() - vecTestPoints[i]).mag2() < 9) + { + nSelectedPoint = i; + } + } + } + + if (nSelectedPoint != -1 && mouse.GetButton(0).bHeld) + { + vecTestPoints[nSelectedPoint] = mouse.GetPosition().round(); + } + + if (mouse.GetButton(0).bReleased) + { + nSelectedPoint = -1; + } + + draw.String({ 10, 10 }, mouse.GetPosition().str(), olc::Colour::BLACK); + + if (keyboard.GetKey(olc::Key::ESCAPE).bPressed) + return false; // End application + + return true; + + + //draw.SetTarget(imTemp); + //draw.Clear(olc::Colour::BLANK); + //draw.FilledRect({ 20,5 }, { 6, 54 }, olc::Colour::GREEN); + //draw.FilledRect({ 0,0 }, imTemp.Size(), olc::Colour::BLANK); + + + //draw.SetTarget(imgPrimary); + //draw.Clear(olc::Colour::BLUE); + + + //draw.WorldReset(); + //draw.FilledRect({ 0,0 }, imgPrimary.Size() * olc::vf2d(0.5f, 1.0f), olc::Pixel(0, 0, 0, 1)); + + + // draw.SetTarget(imTempBuffer); + // draw.Clear(olc::Colour::WHITE); + // draw.Image(imBlend, { 0,0 }); + + + // draw.SetTarget(GetDefaultImage()); + // draw.WorldReset(); + // draw.Clear(olc::Colour::TANGERINE); + // //draw.Image(imHighResSprite, mouse.GetPosition()); + + + // //draw.SetTarget(imLowRes); + + // //draw.Clear(olc::Colour::BLANK); + + // if (mouse.GetButton(1).bHeld) + // { + // fAngle += 0.2f * fElapsedTime; + // } + + // draw.WorldRotate(fAngle, { 0,0 });// imLowRes.Size() / 2.0f); + // if (mouse.GetButton(2).bPressed) + // { + // auto w = std::make_shared(); + // //AddChildWindow(w, { 100,100 }, { 2, 2 }); + // } + + // if (mouse.GetWheel() > 0) + // { + // fScale *= 1.1f; + // } + + // if (mouse.GetWheel() < 0) + // { + // fScale *= 0.9f; + // } + + // draw.WorldScale({ fScale, fScale }); + // //for (int x = 0; x < imgPrimary.Size().x; x++) + // // for (int y = 0; y < imgPrimary.Size().y; y++) + // // draw.Pixel(olc::vf2d( x, y ), olc::Pixel(rand() % 255, rand() % 255, rand() % 255)); + + // //// + // //return true; + + + + + // //std::cout << mouse.GetPosition() << "\n"; + // auto vMouse = draw.ScreenToWorld(mouse.GetPosition() ); + + // if (mouse.GetButton(0).bPressed) + // { + // float dist = 100000.0f; + // int idx = -1; + // for (int i = 0; i < 4; i++) + // { + // float d = (vecVerts[i] - vMouse).mag(); + // if (d < 8 && d < dist) + // { + // dist = d; + // idx = i; + // } + // } + // nSelectedVert = idx; + // } + + // + + // if (nSelectedVert != -1 && mouse.GetButton(0).bHeld) + // vecVerts[nSelectedVert] = vMouse; + + // if (mouse.GetButton(0).bReleased) + // nSelectedVert = -1; + + //// draw.ImageQuad(imLogo.region({ 0,0 }, { 10,10 }), vecVerts); + + //// draw.Line(vecVerts[0], vecVerts[1], olc::Colour::MAGENTA); + //// draw.Line(vecVerts[1], vecVerts[2], olc::Colour::MAGENTA); + //// draw.Line(vecVerts[2], vecVerts[3], olc::Colour::MAGENTA); + //// draw.Line(vecVerts[3], vecVerts[0], olc::Colour::MAGENTA); + + //// //draw.Image(imTemp, vMouse, { 4,4 }); + + //// + + //////draw.Image(olc::fontClassicPGE.imgFont, vMouse); + + //// + + //// std::string sTest = "Hello World!\nThe quick brown fox JUMPS over the LaZy dog..."; + //// olc::vf2d vSizeMono = draw.GetTextSize(sTest, false, { 1, 2 }); + //// olc::vf2d vSizeProp = draw.GetTextSize(sTest, true, { 1, 2 }); + + + //// draw.String({ 10.0f, 10.0f }, "Hello World!\nThe quick brown fox JUMPS over the LaZy dog...", olc::Colour::WHITE, { 1, 2 }); + //// draw.Rect({ 10.0f, 10.0f }, vSizeMono, olc::Colour::GREEN); + + //// draw.StringProp({ 10.0f, 100.0f }, "Hello World!\nThe quick brown fox JUMPS over the LaZy dog...", olc::Colour::BLUE, { 1, 2 }); + //// draw.Rect({ 10.0f, 100.0f }, vSizeProp, olc::Colour::GREEN); + + //// draw.StringProp({ 10.0f, 200.0f }, "Hello World!\nThe quick brown fox JUMPS over the LaZy dog...", olc::PixelF(0.5f, 0.0f, 0.0f, 0.25f), {1, 2}); + //// draw.Rect({ 10.0f, 200.0f }, vSizeProp, olc::Colour::GREEN); + + //// //draw.Pixel(vMouse, olc::Colour::GREEN); + //// //draw.Line(vMouse, vMouse + 1, olc::Colour::GREEN); + //// + //// draw.FilledRect({ 5, 100 }, { 100,100 }, olc::Colour::TANGERINE, olc::Colour::DARK_CYAN, olc::Colour::RED, olc::Colour::GREEN); + //// + //// draw.FilledRect({ 5,5 }, { 10,10 }, olc::Colour::YELLOW); + + + + ////// draw.Rect({ 8,8 }, { 20,20 }, olc::Colour::RED); + + //// if(mouse.GetButton(0).bHeld) + //// draw.Line({ 1.0f, 1.0f }, { 25.5f, 25.5f }); + + + + //// //draw.Image(imTempBuffer, vMouse); + //// draw.Image(imSampleTest, vMouse); + //// //draw.SetTarget(imgPrimary); + //// //draw.WorldReset(); + //// //draw.Image(imLowRes, { 0,0 }, { 4, 4 }); + + + //// draw.swLine({ 100,100 }, vMouse, olc::Colour::BLACK, olc::Colour::YELLOW); + + + //// + + //// draw.Rect({ 8,8, }, { 20,20 }, olc::Colour::RED, olc::Colour::GREEN, olc::Colour::BLUE, olc::Colour::WHITE); + //// draw.swRect({ 7,7, }, { 22,22 }, olc::Colour::RED, olc::Colour::GREEN, olc::Colour::BLUE, olc::Colour::WHITE); + + // for (int i = 0; i < 100; i++) + // { + // // Draw random triangle + // draw.swTexturedTriangle( + // { float(rand() % GetDefaultImage().Size().x), float(rand() % GetDefaultImage().Size().y) }, + // { float(rand() % GetDefaultImage().Size().x), float(rand() % GetDefaultImage().Size().y) }, + // { float(rand() % GetDefaultImage().Size().x), float(rand() % GetDefaultImage().Size().y) }, + // olc::Pixel(rand() % 256, rand() % 256, rand() % 256), + // olc::Pixel(rand() % 256, rand() % 256, rand() % 256), + // olc::Pixel(rand() % 256, rand() % 256, rand() % 256), { 0,0 }, { 0,1 }, { 1, 1 }, imLogo + // ); + // } + + + // //std::cout << "Mouse Fil: " << vMouse << "\n"; + // draw.swFilledTriangle({ 30,30 }, { 250,50 }, vMouse, olc::Colour::RED, olc::Colour::GREEN, olc::Colour::BLUE); + // //std::cout << "Mouse Fil: " << vMouse << "\n"; + // draw.swFilledTriangle({ 30,30 }, { 250,50 }, vMouse, olc::Colour::WHITE); + // //std::cout << "Mouse Tex: " << vMouse << "\n"; + // draw.swTexturedTriangle({ 30,30 }, { 250,50 }, vMouse, olc::Colour::RED, olc::Colour::GREEN, olc::Colour::BLUE, { 0,0 }, { 0,1 }, { 1, 1 }, imHighResSprite); + + // draw.swTriangle({ 30,30 }, { 250,50 }, vMouse, olc::Colour::RED, olc::Colour::GREEN, olc::Colour::BLUE); + // + + // //draw.Rect(vMouse, { 100,100 }); + + // /*for (auto& a : vecLogos) + // { + // a.pos += a.vel * fElapsedTime; + // a.ang += a.angvel * fElapsedTime; + + // if (a.pos.x >= imgPrimary.Size().x - 46) + // { + // a.pos.x = imgPrimary.Size().x - 46; + // a.vel.x *= -1.0f; + // } + + // if (a.pos.y >= imgPrimary.Size().y - 28) + // { + // a.pos.y = imgPrimary.Size().y - 28; + // a.vel.y *= -1.0f; + // } + + // if (a.pos.x < 0) + // { + // a.pos.x = 0; + // a.vel.x *= -1.0f; + // } + + // if (a.pos.y < 0) + // { + // a.pos.y = 0; + // a.vel.y *= -1.0f; + // } + + // draw.ImageRotated(imLogo, a.pos, a.ang, { 10,10 }, { 2.0f, 4.0f }); + // }*/ + // + + + // /*draw.SetTarget(imTest); + // draw.FilledRect({ 0,0 }, imTest.Size(), olc::Colour::BLUE); + + // draw.AffineRotate(fAngle, { 32.0f, 32.0f }); + // + // draw.Rect({ 5,5 }, { 10,10 }, olc::Colour::YELLOW); + // draw.Line({ 0,0 }, { 20, 20 }); + + + // draw.SetTarget(imgPrimary); + // draw.AffineReset(); + + // draw.FilledRect({ 0,0 }, imgPrimary.Size(), olc::Colour::VERY_DARK_MAGENTA); + + // draw.Line({ 0,0 }, imgPrimary.Size() - 1, olc::Colour::RED); + // draw.Line(olc::vf2d( imgPrimary.Size().x - 1, 0 ), olc::vf2d( 0, imgPrimary.Size().y - 1 ), olc::Colour::GREEN); + // + // if(mouse.GetButton(0).bHeld) + // draw.Line({ 0,0 }, mouse.GetPosition(), olc::Colour::TANGERINE); + + // draw.AffineRotate(fAngle * 0.2f, imgPrimary.Size() * 0.5f); + // draw.Rect({ 0,0 }, imgPrimary.Size()); + // draw.Image(imTest, { 10, 10 }, { 64, 64 }); + // draw.Image(imTest, { 100, 10 }, { 64, 64 }); + // draw.Image(imTest, { 10, 100 }, { 64, 64 }); + + // draw.Image(sprite1, { 100, 100 }, { 64, 64 }); + // draw.Image(sprite1.region({ 20.0f, 20.0f }, { 32.0f, 32.0f }), { 200, 100 }, { 32, 32 }); + // draw.Image(sprite1.region({ 20.0f, 20.0f }, { 32.0f, 32.0f }, { 100.0f, 200.0f }, { 150.0f, 180.0f }), { 100, 200 }, { 32, 32 }); + // olc::Pixel p = draw.GetPixel(imTest, { 8,5 }); + + // draw.FilledRect({ 200,200 }, { 10,10 }, p);*/ + + // ////draw.AffineRotate(fAngle, { 128,120 }); + // ////draw.AffineOffset({ 10,10 }); + // //draw.AffineScale(olc::vf2d(1,1) * (std::sin(fAngle) + 1.1)); + + // //draw.Rect({ 0,0 }, GetSize() - olc::vf2d{1, 1}, olc::Colour::CYAN); + // //draw.FillRect({ 100.0f, 30.0f }, { 20.0f, 50.0f }, olc::Colour::TANGERINE); + + // + + // //for (float i = 0; i < 20; i++) + // //{ + // // draw.AffineRotate(fAngle + (i/50.0f), {128,120}); + // // if (fmod(i,2) == 1) + // // { + // // draw.Line({ 0 + i * 4 ,0 + i * 4 }, { 255 - i * 4, 0 + i * 4 }, olc::Colour::RED, olc::Colour::GREEN); + // // draw.Line({ 255 - i * 4,0 + i * 4 }, { 255 - i * 4, 239 - i * 4 }, olc::Colour::GREEN, olc::Colour::CYAN); + // // draw.Line({ 255 - i * 4,239 - i * 4 }, { 0 + i * 4, 239 - i * 4 }, olc::Colour::CYAN, olc::Colour::WHITE); + // // draw.Line({ 0 + i * 4,239 - i * 4 }, { 0 + i * 4, 0 + i * 4 }, olc::Colour::WHITE, olc::Colour::RED); + // // } + // // else + // // { + // // draw.Line({ 0 + i * 4 ,0 + i * 4 }, { 255 - i * 4, 0 + i * 4 }, olc::Colour::RED); + // // draw.Line({ 255 - i * 4,0 + i * 4 }, { 255 - i * 4, 239 - i * 4 }, olc::Colour::GREEN); + // // draw.Line({ 255 - i * 4,239 - i * 4 }, { 0 + i * 4, 239 - i * 4 }, olc::Colour::CYAN); + // // draw.Line({ 0 + i * 4,239 - i * 4 }, { 0 + i * 4, 0 + i * 4 }, olc::Colour::WHITE); + // // } + // //} + + // ///*for (int x = 0; x < 100; x++) + // //{ + // // for (int y = 0; y < 100; y++) + // // { + // // if (x % 2 == 0) + // // { + // // draw.Pixel(olc::vf2d(x, y) + olc::vf2d(100, 100), olc::Colour::YELLOW); + // // } + // // } + // //}*/ + + // ////draw.Pixel({ -0.5f, -0.25f }, olc::Colour::YELLOW); + + // //draw.FillRect({ 100.0f, 30.0f }, { 20.0f, 50.0f }, olc::Colour::TANGERINE); + + // ////draw.Pixel({ 0.3f, 0.3f }, olc::Colour::BLACK); + // ////draw.Pixel({ 0.32f, 0.3f }, olc::Colour::BLACK); + // ////draw.Pixel({ 0.34f, 0.3f }, olc::Colour::BLACK); + + + + return true; } }; - -// Main entry point for the application int main() { - // Construct demo application - Example_CustomPixelShader 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 + Example demo; + //if (demo.Construct({ 256, 240 }, { 4, 4 })) + + olc::PGEConfig cfg; + cfg.vPixelSize = { 1,1 }; + cfg.vScreenSize = { 1024, 960 }; + + cfg.vPixelSize = { 4,4 }; + cfg.vScreenSize = { 256, 240 }; + //cfg.bAntiAliasMainScreen = true; + cfg.bVSync = false; + + //if (demo.Construct({ 1280, 960 }, { 1, 1 }, cfg)) + if(demo.Construct(cfg)) demo.Start(); - } return 0; } From d7f80f6ac219f328c6287e8901781f8012169cde Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Wed, 25 Feb 2026 16:21:03 -0500 Subject: [PATCH 075/101] [headless] javid wants no example --- examples/olcPGE3_PixelShaders-Headless.cpp | 228 --------------------- 1 file changed, 228 deletions(-) delete mode 100644 examples/olcPGE3_PixelShaders-Headless.cpp diff --git a/examples/olcPGE3_PixelShaders-Headless.cpp b/examples/olcPGE3_PixelShaders-Headless.cpp deleted file mode 100644 index a0695210..00000000 --- a/examples/olcPGE3_PixelShaders-Headless.cpp +++ /dev/null @@ -1,228 +0,0 @@ - -/* - olc::PixelGameEngine3 Example - Custom Pixel Shaders (HEADLESS MODE) - - Draws a few lines using different styles and techniques, AND THEN - renders THE ENTIRE SCENE USING A CUSTOM PIXEL SHADER! - - Licenced under the OLC-3 License -*/ - -/* - NOTE! This is an advanced example demonstrating custom pixel shaders. - - It assumes you are familiar with olcPixelGameEngine3 and GLSL shader - programming. If you are new to either of these topics, please refer - to the other examples and documentation first. -*/ - - -// Define OLC_PGE3_APPLICATION to include the implementation of -// the Pixel Game Engine as part of this translation unit -#define OLC_USE_HEADLESS -#define OLC_PGE3_APPLICATION -#include "../olcPixelGameEngine3.h" - -// Example application demonstrating a custom pixel shader effect. -// This class overrides the olc::PixelGameEngine base class by -// implementing the OnUserCreate() and OnUserUpdate() functions -class Example_CustomPixelShader : public olc::PixelGameEngine -{ -public: - Example_CustomPixelShader() - { - - } - -protected: - // We accumulate total time for some animation - float fTotalTime = 0.0f; - - olc::gpu::OLC_SHADER_CLASS shaderExample; - olc::Image imgWithoutFX; - - olc::Image imgMini; - -public: - // Called once at the start, so create things here - bool OnUserCreate() override - { - // Custom pixel shader main() function and uniforms - std::string custom_ps_main = - R"( - uniform float frequency; - uniform float amplitude; - - void main() - { - // 1) Horizontal Wobble - vec2 samplePos = vec2( - oTex.x + sin(oTex.y * frequency * pgeTargetSizeInPixels.y + pgeTotalTimeElapsed * 2.0) * amplitude, - oTex.y - ); - - - - // 2) Sobel Edge Detection - - // Sample 3x3 neighborhood for Sobel - vec2 pixelSize = pgeInverseTargetSizeInPixels; - - float tl = length(texture(pgeTexture0, samplePos + vec2(-pixelSize.x, -pixelSize.y)).rgb); - float t = length(texture(pgeTexture0, samplePos + vec2(0.0, -pixelSize.y)).rgb); - float tr = length(texture(pgeTexture0, samplePos + vec2(pixelSize.x, -pixelSize.y)).rgb); - float l = length(texture(pgeTexture0, samplePos + vec2(-pixelSize.x, 0.0)).rgb); - float r = length(texture(pgeTexture0, samplePos + vec2(pixelSize.x, 0.0)).rgb); - float bl = length(texture(pgeTexture0, samplePos + vec2(-pixelSize.x, pixelSize.y)).rgb); - float b = length(texture(pgeTexture0, samplePos + vec2(0.0, pixelSize.y)).rgb); - float br = length(texture(pgeTexture0, samplePos + vec2(pixelSize.x, pixelSize.y)).rgb); - - // Apply Sobel Kernels - float sobelX = -tl + tr - 2.0 * l + 2.0 * r - bl + br; - float sobelY = -tl - 2.0 * t - tr + bl + 2.0 * b + br; - - // Calculate edge magnitude - float edge = sqrt(sobelX * sobelX + sobelY * sobelY); - - // 3) Colour bias vertically - float bias = oTex.y; - vec4 newCol = vec4( - oCol.r * bias, - oCol.g, - oCol.b * (1.0 - bias), - oCol.a - ); - - - // 4) Combine effects - - // pgeTexture0 is the primary texture unit - // pgeTexture1 is some other image - - vec4 texColour = texture(pgeTexture0, samplePos) * oCol; - vec4 otherColour = texture(pgeTexture1, samplePos); - - vec4 edgeColour = vec4(newCol * edge) + otherColour * 0.5; - pixel = vec4(edgeColour.rgb * texColour.a, texColour.a); - } - )"; - - - // Construct pixel shader from default header, but use custom main() - shaderExample.SetPixelShaderSource( - olc::gpu::Shader::PS_DefaultHeader() + - custom_ps_main); - - // Just use the PGE3 default vertex shader - shaderExample.SetVertexShaderSource( - olc::gpu::Shader::VS_DefaultHeader() + - olc::gpu::Shader::VS_DefaultMain() - ); - - // Check for shader compile errors - std::string sResult = shaderExample.Compile(); - if (sResult != "OK") - { - std::cout << "Error compiling shader: " << sResult << std::endl; - return false; - } - - // Add custom uniforms used by custom shader. The "system" - // will have taken care of required PGE3 uniforms - shaderExample.CreateUniform("frequency"); - shaderExample.CreateUniform("amplitude"); - - // Create off-screen image to draw to - CreateImage(imgWithoutFX, GetScreen().Size()); - - // Load a small image to draw for fun - CreateImageFromFile(imgMini, "./assets/minsanity_texture.png"); - return true; - } - - // Called every frame, so update things here - bool OnUserUpdate(float fElapsedTime) override - { - // Set rendering target to off-screen image, we will draw to this - // entirely normally, then apply a custom shader when we draw the - // off-screen image to the screen - draw.SetTarget(imgWithoutFX); - - // === Stolen from olcPGE3_Lines.cpp == - - // Clear whole screen - draw.Clear(olc::Colour::VERY_DARK_BLUE); - - // Simple white line - draw.Line({ 10.0f, 10.0f }, { 246.0f, 40.0f }, olc::Colour::WHITE); - - // Gradient line - draw.Line({ 10.0f, 50.0f }, olc::Colour::RED, { 246.0f, 80.0f }, olc::Colour::YELLOW); - - // Rotating gradient lines - fTotalTime += fElapsedTime; - olc::vf2d p1 = { 64.0f, 160.0f }; - olc::vf2d p2 = olc::vf2d{ std::cos(fTotalTime), std::sin(fTotalTime) } * 64.0f; - - draw.Line(p1, olc::Colour::CYAN, p1 + p2, olc::Colour::MAGENTA); - draw.Line(p1, olc::Colour::CYAN, p1 - p2, olc::Colour::MAGENTA); - draw.Line(p1, olc::Colour::CYAN, p1 + olc::vf2d{ -p2.y, p2.x }, olc::Colour::MAGENTA); - draw.Line(p1, olc::Colour::CYAN, p1 + olc::vf2d{ p2.y, -p2.x }, olc::Colour::MAGENTA); - - // Line to mouse pointer with tint - float alpha = (std::sin(fTotalTime * 4.0f) + 1.0f) / 2.0f; - draw.Line(p1, mouse.GetPosition(), olc::Colour::WHITE, olc::PixelF(1.0f, 0.0f, 0.0f, alpha)); - - // Boundary box (with fixed coordinates) - draw.Line({ 0.0f, 0.0f }, { 255.0f, 0.0f }, olc::Colour::DARK_GREY); - draw.Line({ 255.0f, 0.0f }, { 255.0f, 239.0f }, olc::Colour::DARK_GREY); - draw.Line({ 255.0f, 239.0f }, { 0.0f, 239.0f }, olc::Colour::DARK_GREY); - draw.Line({ 0.0f, 239.0f }, { 0.0f, 0.0f }, olc::Colour::DARK_GREY); - // === End stolen code === - - // Draws an image for the fun of it - draw.Image(imgMini, mouse.GetPosition()); - - // Copy image to screen with new shader - draw.SetTarget(GetScreen()); - - // Set the custom shader - draw.SetShader(shaderExample); - - // Set custom parameters for the shader based - // on mouse position - float frequency = (mouse.GetPosition().x / 256.0f) * 0.1f; - float amplitude = (mouse.GetPosition().y / 240.0f) * 0.1f; - draw.SetShaderUniform("frequency", frequency); - draw.SetShaderUniform("amplitude", amplitude); - - // Our shader uses 2 texture units so we need to bind them here - // By default, unit 0 is bound to the primary texture (imgWithoutFX) - draw.SetShaderTexture(1, imgMini); // Bind imgMini to texture unit 1 - - // Present image with shader effect - this is required - // because here is where the shader is actually applied - draw.Image(imgWithoutFX, { 0, 0 }); - - // Successful frame - return true; - } -}; - - -// Main entry point for the application -int main() -{ - // Construct demo application - Example_CustomPixelShader 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 e4ef9be43c407d54851679aaec66046edc85b211 Mon Sep 17 00:00:00 2001 From: Ciaran Moran Date: Wed, 25 Feb 2026 23:51:02 +0000 Subject: [PATCH 076/101] We no longer accumulate total time for some animation --- examples/olcPGE3_AntiAliasing.cpp | 8 ++------ examples/olcPGE3_BatchesOfFills.cpp | 3 --- examples/olcPGE3_BatchesOfLines.cpp | 6 +----- examples/olcPGE3_CirclesEllipses.cpp | 3 --- examples/olcPGE3_Extensions.cpp | 4 +--- examples/olcPGE3_Fish.cpp | 7 ++----- examples/olcPGE3_Lines.cpp | 6 +----- examples/olcPGE3_PixelShaders.cpp | 5 +---- examples/olcPGE3_Pixels.cpp | 6 +----- examples/olcPGE3_Polygons.cpp | 3 --- examples/olcPGE3_Rectangles.cpp | 6 +----- examples/olcPGE3_Shockwave.cpp | 3 --- examples/olcPGE3_TextBasics.cpp | 4 +--- 13 files changed, 11 insertions(+), 53 deletions(-) diff --git a/examples/olcPGE3_AntiAliasing.cpp b/examples/olcPGE3_AntiAliasing.cpp index 1e361de3..eb087dce 100644 --- a/examples/olcPGE3_AntiAliasing.cpp +++ b/examples/olcPGE3_AntiAliasing.cpp @@ -44,15 +44,11 @@ class Example_AntiAliasing : public olc::PixelGameEngine return true; } - // Total time elapsed - float fTotalTime = 0; - - // Called every frame, so update things here bool OnUserUpdate(float fElapsedTime) override { - fTotalTime += fElapsedTime * 0.1f; - + float fTotalTime = TotalTimeElapsed() * 0.1f; + // Lambda to draw a rotating fan of lines and circles auto DrawFan = [&]() { diff --git a/examples/olcPGE3_BatchesOfFills.cpp b/examples/olcPGE3_BatchesOfFills.cpp index ae34e687..b62e8bda 100644 --- a/examples/olcPGE3_BatchesOfFills.cpp +++ b/examples/olcPGE3_BatchesOfFills.cpp @@ -24,9 +24,6 @@ class Example_BatchesOfFills : public olc::PixelGameEngine } protected: - // We accumulate total time for some animation - float fTotalTime = 0.0f; - std::vector vecBubblePos; std::vector vecBubbleVel; std::vector vecBubbleCol; diff --git a/examples/olcPGE3_BatchesOfLines.cpp b/examples/olcPGE3_BatchesOfLines.cpp index 7ed23c51..2c00f3e8 100644 --- a/examples/olcPGE3_BatchesOfLines.cpp +++ b/examples/olcPGE3_BatchesOfLines.cpp @@ -23,10 +23,6 @@ class Example_BatchesOfLines : public olc::PixelGameEngine } -protected: - // We accumulate total time for some animation - float fTotalTime = 0.0f; - public: // Called once at the start, so create things here bool OnUserCreate() override @@ -53,7 +49,7 @@ class Example_BatchesOfLines : public olc::PixelGameEngine draw.Line(batch, { 10.0f, 50.0f }, olc::Colour::RED, { 246.0f, 80.0f }, olc::Colour::YELLOW); // Rotating gradient lines - fTotalTime += fElapsedTime; + float fTotalTime = TotalTimeElapsed(); olc::vf2d p1 = { 64.0f, 160.0f }; olc::vf2d p2 = olc::vf2d{ std::cos(fTotalTime), std::sin(fTotalTime) } * 64.0f; diff --git a/examples/olcPGE3_CirclesEllipses.cpp b/examples/olcPGE3_CirclesEllipses.cpp index 6992475b..b7e54e64 100644 --- a/examples/olcPGE3_CirclesEllipses.cpp +++ b/examples/olcPGE3_CirclesEllipses.cpp @@ -24,9 +24,6 @@ class Example_RoundThings : public olc::PixelGameEngine } protected: - // We accumulate total time for some animation - float fTotalTime = 0.0f; - std::vector vecBubblePos; std::vector vecBubbleVel; std::vector vecBubbleCol; diff --git a/examples/olcPGE3_Extensions.cpp b/examples/olcPGE3_Extensions.cpp index c540a1c3..a2e755c1 100644 --- a/examples/olcPGE3_Extensions.cpp +++ b/examples/olcPGE3_Extensions.cpp @@ -96,8 +96,6 @@ class Example_Extensions : public olc::PixelGameEngine // 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 @@ -115,7 +113,7 @@ class Example_Extensions : public olc::PixelGameEngine 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; + float fTotalTime = TotalTimeElapsed(); draw.Line(GetScreen().Size() / 2, olc::vf2d(cos(fTotalTime), sin(fTotalTime)) * 50.0f + GetScreen().Size() / 2, olc::Colour::GREEN); // Successful frame diff --git a/examples/olcPGE3_Fish.cpp b/examples/olcPGE3_Fish.cpp index de4e04e5..5d8da581 100644 --- a/examples/olcPGE3_Fish.cpp +++ b/examples/olcPGE3_Fish.cpp @@ -366,8 +366,6 @@ class Example_Fish : public olc::PixelGameEngine } 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; @@ -389,9 +387,8 @@ class Example_Fish : public olc::PixelGameEngine // 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 + olc::Pixel background_color{73, 220, 222}; + // Clear screen to a background color draw.Clear(background_color); // Draw the targets for the other fish diff --git a/examples/olcPGE3_Lines.cpp b/examples/olcPGE3_Lines.cpp index 43218cc7..d27920d6 100644 --- a/examples/olcPGE3_Lines.cpp +++ b/examples/olcPGE3_Lines.cpp @@ -23,10 +23,6 @@ class Example_Lines : public olc::PixelGameEngine } -protected: - // We accumulate total time for some animation - float fTotalTime = 0.0f; - public: // Called once at the start, so create things here bool OnUserCreate() override @@ -48,7 +44,7 @@ class Example_Lines : public olc::PixelGameEngine draw.Line({ 10.0f, 50.0f }, olc::Colour::RED, { 246.0f, 80.0f }, olc::Colour::YELLOW); // Rotating gradient lines - fTotalTime += fElapsedTime; + float fTotalTime = TotalTimeElapsed(); olc::vf2d p1 = { 64.0f, 160.0f }; olc::vf2d p2 = olc::vf2d{ std::cos(fTotalTime), std::sin(fTotalTime) } * 64.0f; diff --git a/examples/olcPGE3_PixelShaders.cpp b/examples/olcPGE3_PixelShaders.cpp index 1ceff755..f12bc0ef 100644 --- a/examples/olcPGE3_PixelShaders.cpp +++ b/examples/olcPGE3_PixelShaders.cpp @@ -34,9 +34,6 @@ class Example_CustomPixelShader : public olc::PixelGameEngine } protected: - // We accumulate total time for some animation - float fTotalTime = 0.0f; - olc::gpu::Shader_GLSL33 shaderExample; olc::Image imgWithoutFX; @@ -160,7 +157,7 @@ class Example_CustomPixelShader : public olc::PixelGameEngine draw.Line({ 10.0f, 50.0f }, olc::Colour::RED, { 246.0f, 80.0f }, olc::Colour::YELLOW); // Rotating gradient lines - fTotalTime += fElapsedTime; + float fTotalTime = TotalTimeElapsed(); olc::vf2d p1 = { 64.0f, 160.0f }; olc::vf2d p2 = olc::vf2d{ std::cos(fTotalTime), std::sin(fTotalTime) } * 64.0f; diff --git a/examples/olcPGE3_Pixels.cpp b/examples/olcPGE3_Pixels.cpp index 5eab4bd7..65c45a8d 100644 --- a/examples/olcPGE3_Pixels.cpp +++ b/examples/olcPGE3_Pixels.cpp @@ -23,10 +23,6 @@ class Example_Pixels : public olc::PixelGameEngine } -protected: - // We accumulate total time for some animation - float fTotalTime = 0.0f; - public: // Called once at the start, so create things here bool OnUserCreate() override @@ -55,7 +51,7 @@ class Example_Pixels : public olc::PixelGameEngine } // Draw random pixels in a box, tinted over time - fTotalTime += fElapsedTime; + float fTotalTime = TotalTimeElapsed(); olc::Pixel tint = olc::Pixel( (uint8_t)((std::sin(fTotalTime) + 1.0f) * 127.5f), (uint8_t)((std::sin(fTotalTime + 2.0f) + 1.0f) * 127.5f), diff --git a/examples/olcPGE3_Polygons.cpp b/examples/olcPGE3_Polygons.cpp index 93032264..529d31f2 100644 --- a/examples/olcPGE3_Polygons.cpp +++ b/examples/olcPGE3_Polygons.cpp @@ -24,9 +24,6 @@ class Example_Pixels : public olc::PixelGameEngine } protected: - // We accumulate total time for some animation - float fTotalTime = 0.0f; - std::vector vecPolygonPoints; int nSelectedPoint = -1; diff --git a/examples/olcPGE3_Rectangles.cpp b/examples/olcPGE3_Rectangles.cpp index 010c7251..578b5b53 100644 --- a/examples/olcPGE3_Rectangles.cpp +++ b/examples/olcPGE3_Rectangles.cpp @@ -23,10 +23,6 @@ class Example_Rectangles : public olc::PixelGameEngine } -protected: - // We accumulate total time for some animation - float fTotalTime = 0.0f; - public: // Called once at the start, so create things here bool OnUserCreate() override @@ -58,7 +54,7 @@ class Example_Rectangles : public olc::PixelGameEngine olc::Colour::BLUE, olc::Colour::YELLOW); // Draw a filled rectangle with a tint that pulses over time - fTotalTime += fElapsedTime; + float fTotalTime = TotalTimeElapsed(); olc::Pixel tint = olc::Pixel( (uint8_t)((std::sin(fTotalTime) + 1.0f) * 127.5f), (uint8_t)((std::sin(fTotalTime + 2.0f) + 1.0f) * 127.5f), diff --git a/examples/olcPGE3_Shockwave.cpp b/examples/olcPGE3_Shockwave.cpp index 45e66304..6d358088 100644 --- a/examples/olcPGE3_Shockwave.cpp +++ b/examples/olcPGE3_Shockwave.cpp @@ -33,9 +33,6 @@ class Example_Shockwave : public olc::PixelGameEngine } protected: - // We accumulate total time for some animation - float fTotalTime = 0.0f; - olc::gpu::Shader_GLSL33 shaderExample; olc::Image imgWithoutFX; diff --git a/examples/olcPGE3_TextBasics.cpp b/examples/olcPGE3_TextBasics.cpp index 4602047e..2023aac0 100644 --- a/examples/olcPGE3_TextBasics.cpp +++ b/examples/olcPGE3_TextBasics.cpp @@ -24,8 +24,6 @@ class Example_Text : public olc::PixelGameEngine } protected: - // We accumulate total time for some animation - float fTotalTime = 0.0f; float fTickerTime = 0.0f; // We'll draw random text in the background with @@ -47,7 +45,7 @@ class Example_Text : public olc::PixelGameEngine draw.Clear(olc::Colour::VERY_DARK_BLUE); // Rainbow text in time - fTotalTime += fElapsedTime; + float fTotalTime = TotalTimeElapsed(); olc::Pixel colRainbow = olc::Pixel( (uint8_t)((std::sin(fTotalTime * 2.0f + 0.0f) + 1.0f) * 127.5f), (uint8_t)((std::sin(fTotalTime * 2.0f + 2.0f) + 1.0f) * 127.5f), From 13bfd93570601ebf69cb8707eb49565a53ba41b8 Mon Sep 17 00:00:00 2001 From: Ciaran Moran Date: Thu, 26 Feb 2026 01:11:02 +0000 Subject: [PATCH 077/101] emscripten - handle tab background timing --- dev/src/draw_batch.cpp | 3 ++ dev/src/host_web_emscripten.cpp | 20 ++++++++++++ dev/src/host_web_emscripten.h | 4 ++- examples/olcPGE3_FrameGraph.cpp | 57 +++++++++++++++++++++++++++++++++ olcPixelGameEngine3.h | 28 ++++++++++++++-- 5 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 examples/olcPGE3_FrameGraph.cpp diff --git a/dev/src/draw_batch.cpp b/dev/src/draw_batch.cpp index c632ca61..a78148d1 100644 --- a/dev/src/draw_batch.cpp +++ b/dev/src/draw_batch.cpp @@ -327,6 +327,9 @@ const FilledBatch& olc::Draw::FilledPolygon(FilledBatch& batch, const olc::Struc buffPoints.data[i], buffPoints.data[i + 1], buffPoints.data[i + 2], buffColours.data[i], buffColours.data[i + 1], buffColours.data[i + 2]); } + + default: + break; } return batch; diff --git a/dev/src/host_web_emscripten.cpp b/dev/src/host_web_emscripten.cpp index 9f345829..d5411c22 100644 --- a/dev/src/host_web_emscripten.cpp +++ b/dev/src/host_web_emscripten.cpp @@ -220,6 +220,9 @@ namespace olc::host emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, reinterpret_cast(cbData), 1, resize_callback); emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, reinterpret_cast(cbData), 1, fullscreen_change_callback); + // Visibility Change Callback + emscripten_set_visibilitychange_callback(reinterpret_cast(cbData), 1, visibility_callback); + // trigger resize after a short pause emscripten_sleep(50); resize_callback(EMSCRIPTEN_EVENT_RESIZE, nullptr, reinterpret_cast(cbData)); @@ -575,6 +578,23 @@ namespace olc::host return 0; } + EM_BOOL Host_Web_Emscripten::visibility_callback(int eventType, const EmscriptenVisibilityChangeEvent *visibilityChangeEvent, void *userData) + { + CallbackData *pCallbackData = reinterpret_cast(userData); + + if (visibilityChangeEvent->hidden) + { + pCallbackData->pHost->timeHidden = std::chrono::steady_clock::now(); + } + else + { + auto durationHidden = std::chrono::steady_clock::now() - pCallbackData->pHost->timeHidden; + pCallbackData->pHost->pPrimaryPGE->timeFrame2 += durationHidden; + } + + return 0; + } + bool Host_Web_Emscripten::olc_OnMouseButton(olc::Window* pWindow, const uint8_t nButton, const bool bPressed) { return pWindow->olc_OnMouseButton(nButton, bPressed); diff --git a/dev/src/host_web_emscripten.h b/dev/src/host_web_emscripten.h index 595479cd..2f79f591 100644 --- a/dev/src/host_web_emscripten.h +++ b/dev/src/host_web_emscripten.h @@ -71,7 +71,8 @@ namespace olc::host static EM_BOOL fullscreen_change_callback(int eventType, const EmscriptenFullscreenChangeEvent *event, void *userData); static EM_BOOL resize_callback(int eventType, const EmscriptenUiEvent *event, void *userData); static EM_BOOL focus_callback(int eventType, const EmscriptenFocusEvent* focusEvent, void* userData); - + static EM_BOOL visibility_callback(int eventType, const EmscriptenVisibilityChangeEvent *visibilityChangeEvent, void *userData); + private: // Window Wrappers // Set Mouse Device State static bool olc_OnMouseButton(olc::Window* pWindow, const uint8_t nButton, const bool bPressed); @@ -103,6 +104,7 @@ namespace olc::host std::unordered_map mapKeys; // Map of system mouse buttons to olc mouse buttons std::unordered_map mapMouseButtons; + std::chrono::steady_clock::time_point timeHidden; }; diff --git a/examples/olcPGE3_FrameGraph.cpp b/examples/olcPGE3_FrameGraph.cpp new file mode 100644 index 00000000..38b6dd91 --- /dev/null +++ b/examples/olcPGE3_FrameGraph.cpp @@ -0,0 +1,57 @@ +#define OLC_PGE3_APPLICATION +#include "../olcPixelGameEngine3.h" + +constexpr int GRAPH_WIDTH = 512; +constexpr int GRAPH_HEIGHT = 480; + +class Frame_Graph : public olc::PixelGameEngine +{ +public: + Frame_Graph() {} + +private: + std::vector vSamples; + size_t nSampleIndex = 0; + +public: + bool OnUserCreate() override + { + vSamples.resize(GRAPH_WIDTH, 0.0f); + return true; + } + + bool OnUserUpdate(float fElapsedTime) override + { + vSamples[nSampleIndex] = fElapsedTime; + nSampleIndex = (nSampleIndex + 1) % GRAPH_WIDTH; + + draw.Clear(olc::Colour::VERY_DARK_BLUE); + + for (size_t i = 0; i < vSamples.size(); i++) + { + const float fHeight = std::log10(vSamples[i] * 1000.0f + 1e-6f) * 100.0f; + draw.Line({(float)i, GRAPH_HEIGHT}, {(float)i, GRAPH_HEIGHT - fHeight}, (i == nSampleIndex - 1) ? olc::Colour::BLACK : olc::Colour::WHITE); + } + + for(auto i: {10.0f, 100.0f, 1000.0f, 10000.0f}) + { + const float fHeight = std::log10(float(i) + 1e-6f) * 100.0f; + draw.Line({0.0f, GRAPH_HEIGHT - fHeight}, {20.0f, GRAPH_HEIGHT - fHeight}, olc::Colour::RED); + draw.String({22.0f, GRAPH_HEIGHT - fHeight - 5.0f}, std::to_string(int(i)) + "ms", olc::Colour::RED); + } + + return true; + } +}; + +int main() +{ + Frame_Graph demo; + + if (demo.Construct({GRAPH_WIDTH, GRAPH_HEIGHT}, {2, 2})) + { + demo.Start(); + } + + return 0; +} \ No newline at end of file diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index c089ff12..c9ef25af 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -5795,7 +5795,8 @@ namespace olc::host static EM_BOOL fullscreen_change_callback(int eventType, const EmscriptenFullscreenChangeEvent *event, void *userData); static EM_BOOL resize_callback(int eventType, const EmscriptenUiEvent *event, void *userData); static EM_BOOL focus_callback(int eventType, const EmscriptenFocusEvent* focusEvent, void* userData); - + static EM_BOOL visibility_callback(int eventType, const EmscriptenVisibilityChangeEvent *visibilityChangeEvent, void *userData); + private: // Window Wrappers // Set Mouse Device State static bool olc_OnMouseButton(olc::Window* pWindow, const uint8_t nButton, const bool bPressed); @@ -5827,6 +5828,7 @@ namespace olc::host std::unordered_map mapKeys; // Map of system mouse buttons to olc mouse buttons std::unordered_map mapMouseButtons; + std::chrono::steady_clock::time_point timeHidden; }; @@ -6814,7 +6816,6 @@ namespace olc::host // Make OS Update a window frame title, associated with olc::Window bool Host_None::UpdateWindowFrameTitle(olc::Window* pWindow) { - std::cout << pWindow->GetWindowTitle() << "\n"; return true; } @@ -12061,6 +12062,9 @@ namespace olc::host emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, reinterpret_cast(cbData), 1, resize_callback); emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, reinterpret_cast(cbData), 1, fullscreen_change_callback); + // Visibility Change Callback + emscripten_set_visibilitychange_callback(reinterpret_cast(cbData), 1, visibility_callback); + // trigger resize after a short pause emscripten_sleep(50); resize_callback(EMSCRIPTEN_EVENT_RESIZE, nullptr, reinterpret_cast(cbData)); @@ -12416,6 +12420,23 @@ namespace olc::host return 0; } + EM_BOOL Host_Web_Emscripten::visibility_callback(int eventType, const EmscriptenVisibilityChangeEvent *visibilityChangeEvent, void *userData) + { + CallbackData *pCallbackData = reinterpret_cast(userData); + + if (visibilityChangeEvent->hidden) + { + pCallbackData->pHost->timeHidden = std::chrono::steady_clock::now(); + } + else + { + auto durationHidden = std::chrono::steady_clock::now() - pCallbackData->pHost->timeHidden; + pCallbackData->pHost->pPrimaryPGE->timeFrame2 += durationHidden; + } + + return 0; + } + bool Host_Web_Emscripten::olc_OnMouseButton(olc::Window* pWindow, const uint8_t nButton, const bool bPressed) { return pWindow->olc_OnMouseButton(nButton, bPressed); @@ -16616,6 +16637,9 @@ const FilledBatch& olc::Draw::FilledPolygon(FilledBatch& batch, const olc::Struc buffPoints.data[i], buffPoints.data[i + 1], buffPoints.data[i + 2], buffColours.data[i], buffColours.data[i + 1], buffColours.data[i + 2]); } + + default: + break; } return batch; From 254657f9dcb79a511687fe23b65f425337c0d24b Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Thu, 26 Feb 2026 02:31:25 -0500 Subject: [PATCH 078/101] [examples] make friction responsive to time, uniform behavior on all platforms and with vsync turned off --- examples/olcPGE3_Mouse.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/examples/olcPGE3_Mouse.cpp b/examples/olcPGE3_Mouse.cpp index 2de5f707..dba962e9 100644 --- a/examples/olcPGE3_Mouse.cpp +++ b/examples/olcPGE3_Mouse.cpp @@ -51,12 +51,13 @@ class Example_Mouse : public olc::PixelGameEngine olc::vf2d mousePos = mouse.GetPosition().round(); // Update scroll accumulator - scrollAcculator += mouse.GetWheel() * fElapsedTime; - scrollAcculator *= 0.95f; // Friction/Decay + + scrollTracker = (mouse.GetWheel() < 0 || mouse.GetWheel() > 0) ? (float)mouse.GetWheel() : scrollTracker; + scrollTracker -= scrollTracker * 4.0f * fElapsedTime; // 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); + DrawScrollIndicator(barPosition, barSize, scrollTracker); // Create particles on mouse buttons if(mouse.GetButton(0).bPressed) // Left click @@ -79,7 +80,7 @@ class Example_Mouse : public olc::PixelGameEngine { p.life -= fElapsedTime; p.pos += p.vel * fElapsedTime; - p.vel *= 0.98f; // Friction + p.vel -= (p.vel * 0.98f * fElapsedTime); // Friction // Little cosmetic fix to stop popping at the end of life if (p.life < 0.0f) p.life = 0.0f; @@ -178,7 +179,7 @@ class Example_Mouse : public olc::PixelGameEngine } private: std::vector vecParticles; - float scrollAcculator = 0.0f; + float scrollTracker = 0.0f; }; // Main entry point for the application @@ -190,7 +191,7 @@ int main() // Create "screen" of 256x240 "pixels" // with a pixel size of 4x4 actual screen pixels PGEConfig config; - config.bVSync = true; + config.bVSync = false; config.vPixelSize = { 4,4 }; config.vScreenSize = { 256,240 }; From 54bcab50b9d92a0178bf7cbb73b61498527ccf8d Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Thu, 26 Feb 2026 03:36:28 -0500 Subject: [PATCH 079/101] [window] add IsFocused() api, implement olc_OnFocus --- dev/src/window.cpp | 9 +++++++-- dev/src/window.h | 6 +++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/dev/src/window.cpp b/dev/src/window.cpp index 6c5a9311..d936688c 100644 --- a/dev/src/window.cpp +++ b/dev/src/window.cpp @@ -44,9 +44,9 @@ namespace olc return true; } - bool Window::olc_OnMouseFocus(const bool bHasFocus) + bool Window::olc_OnFocus(const bool bHasFocus) { - olc_IgnoreUnused(bHasFocus); + bWindowIsFocused = bHasFocus; return false; } @@ -132,5 +132,10 @@ namespace olc pHost->SetMouseVisible(this, bShow); } + bool Window::IsFocused() const + { + return bWindowIsFocused; + } + }; //! END IMPLEMENTATION \ No newline at end of file diff --git a/dev/src/window.h b/dev/src/window.h index 3e890929..203fc801 100644 --- a/dev/src/window.h +++ b/dev/src/window.h @@ -62,7 +62,7 @@ namespace olc virtual bool olc_OnMouseButton(const uint8_t nButton, const bool bPressed); virtual bool olc_OnMouseMove(const olc::vi2d& vMousePos); virtual bool olc_OnMouseWheel(const int32_t nScroll); - virtual bool olc_OnMouseFocus(const bool bHasFocus); + virtual bool olc_OnFocus(const bool bHasFocus); // Set Window State virtual bool olc_OnWindowPosition(const olc::vi2d& vPos); @@ -96,9 +96,13 @@ namespace olc // Show or hide mouse cursor void ShowMouseCursor(const bool bShow); + // Focus + bool IsFocused() const; + protected: bool bRequestToClose = false; bool bShouldRemove = false; + bool bWindowIsFocused = false; protected: size_t nUniqueID = size_t(-1); From 16d03983032b50393345908e2da857d9d840e462 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Thu, 26 Feb 2026 03:37:05 -0500 Subject: [PATCH 080/101] [WinAPI] use olc_OnFocus --- dev/src/host_win_winapi.cpp | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/dev/src/host_win_winapi.cpp b/dev/src/host_win_winapi.cpp index 5a525afd..2a82dde8 100644 --- a/dev/src/host_win_winapi.cpp +++ b/dev/src/host_win_winapi.cpp @@ -322,6 +322,8 @@ namespace olc::host lp = GetWindowLongPtr(hWnd, GWL_EXSTYLE); SetWindowLongPtr(hWnd, GWL_EXSTYLE, lp | (WS_EX_WINDOWEDGE)); + pWindow->olc_OnFocus(true); + //SetWindowPos(hWnd, NULL, vWinPos.x, vWinPos.y, width, height, SWP_SHOWWINDOW); //ShowWindow(hWnd, 1); //UpdateWindow(hWnd); @@ -333,7 +335,6 @@ namespace olc::host // source window with a long_ptr to this class, and therefore we can't // call the appropriate event handler. - // Store the link bewteen host resource and window mapUID2HWND.insert_or_assign(pWindow->GetUID(), hWnd); mapHWND2PTR.insert_or_assign(hWnd, pWindow); @@ -434,12 +435,34 @@ namespace olc::host window->olc_OnMouseWheel(GET_WHEEL_DELTA_WPARAM(wParam)); break; } - // case WM_MOUSELEAVE: ptrPGE->olc_UpdateMouseFocus(false); return 0; - // case WM_SETFOCUS: ptrPGE->olc_UpdateKeyFocus(true); return 0; - // case WM_KILLFOCUS: ptrPGE->olc_UpdateKeyFocus(false); return 0; + + case WM_ACTIVATE: + { + window->olc_OnFocus((LOWORD(wParam) != WA_INACTIVE)); + return 0; + } + + case WM_MOUSEACTIVATE: + { + window->olc_OnFocus(true); + return MA_ACTIVATE; + } + + case WM_SETFOCUS: + { + window->olc_OnFocus(true); + return 0; + } + + case WM_KILLFOCUS: + { + window->olc_OnFocus(false); + return 0; + } case WM_KEYDOWN: { + window->olc_OnFocus(true); if (mapKeys.contains(int32_t(wParam))) { window->olc_OnKeyPress(mapKeys[int32_t(wParam)], true); From 7aa0b307aa7300826cd24f0075eef0c7942448c7 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Thu, 26 Feb 2026 03:37:16 -0500 Subject: [PATCH 081/101] [X11] use olc_OnFocus --- dev/src/host_lin_x11.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dev/src/host_lin_x11.cpp b/dev/src/host_lin_x11.cpp index dce60163..2777da6b 100644 --- a/dev/src/host_lin_x11.cpp +++ b/dev/src/host_lin_x11.cpp @@ -228,13 +228,13 @@ namespace olc::host { if(auto* pge_window = get_pge_window(xev.xfocus.window); pge_window) { - pge_window->olc_OnMouseFocus(true); + pge_window->olc_OnFocus(true); } } else if (xev.type == FocusOut) { if(auto* pge_window = get_pge_window(xev.xfocus.window); pge_window) { - pge_window->olc_OnMouseFocus(false); + pge_window->olc_OnFocus(false); } } else if (xev.type == ClientMessage) From 765857f2561ae0cda80c7348d7e045baf5c66065 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Thu, 26 Feb 2026 03:37:29 -0500 Subject: [PATCH 082/101] [Wayland] use olc_OnFocus --- dev/src/host_lin_wayland.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dev/src/host_lin_wayland.cpp b/dev/src/host_lin_wayland.cpp index 8c61b54c..feefcabe 100644 --- a/dev/src/host_lin_wayland.cpp +++ b/dev/src/host_lin_wayland.cpp @@ -696,7 +696,7 @@ namespace olc::host } auto* pge_window = mapUID2OlcWindow[active_window_id]; - pge_window->olc_OnMouseFocus(true); + pge_window->olc_OnFocus(true); } void Host_Linux_Wayland::keyboard_leave_callback(void* data, wl_keyboard* keyboard, uint32_t serial, wl_surface* surface) @@ -708,7 +708,7 @@ namespace olc::host void Host_Linux_Wayland::keyboard_leave(wl_keyboard* keyboard, uint32_t serial, wl_surface* surface) { auto* pge_window = mapUID2OlcWindow[active_window_id]; - pge_window->olc_OnMouseFocus(false); + pge_window->olc_OnFocus(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 f9695f0dc5fb8ee52187829590b7b3d56a15ef62 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Thu, 26 Feb 2026 03:41:52 -0500 Subject: [PATCH 083/101] [Emscripten] use olc_OnFocus --- dev/src/host_web_emscripten.cpp | 8 ++++---- dev/src/host_web_emscripten.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/dev/src/host_web_emscripten.cpp b/dev/src/host_web_emscripten.cpp index 9f345829..b598edfa 100644 --- a/dev/src/host_web_emscripten.cpp +++ b/dev/src/host_web_emscripten.cpp @@ -565,11 +565,11 @@ namespace olc::host if (eventType == EMSCRIPTEN_EVENT_BLUR) { - olc_OnMouseFocus(pCallbackData->pWindow, false); + olc_OnFocus(pCallbackData->pWindow, false); } else if (eventType == EMSCRIPTEN_EVENT_FOCUS) { - olc_OnMouseFocus(pCallbackData->pWindow, true); + olc_OnFocus(pCallbackData->pWindow, true); } return 0; @@ -590,9 +590,9 @@ namespace olc::host return pWindow->olc_OnMouseWheel(nScroll); } - bool Host_Web_Emscripten::olc_OnMouseFocus(olc::Window* pWindow, const bool bHasFocus) + bool Host_Web_Emscripten::olc_OnFocus(olc::Window* pWindow, const bool bHasFocus) { - return pWindow->olc_OnMouseFocus(bHasFocus); + return pWindow->olc_OnFocus(bHasFocus); } bool Host_Web_Emscripten::olc_OnKeyPress(olc::Window* pWindow, const olc::Key key, const bool bPressed) diff --git a/dev/src/host_web_emscripten.h b/dev/src/host_web_emscripten.h index 595479cd..a2af4843 100644 --- a/dev/src/host_web_emscripten.h +++ b/dev/src/host_web_emscripten.h @@ -77,7 +77,7 @@ namespace olc::host static bool olc_OnMouseButton(olc::Window* pWindow, const uint8_t nButton, const bool bPressed); static bool olc_OnMouseMove(olc::Window* pWindow, const olc::vi2d& vMousePos); static bool olc_OnMouseWheel(olc::Window* pWindow, const int32_t nScroll); - static bool olc_OnMouseFocus(olc::Window* pWindow, const bool bHasFocus); + static bool olc_OnFocus(olc::Window* pWindow, const bool bHasFocus); // Set Keyboard Device State static bool olc_OnKeyPress(olc::Window* pWindow, const olc::Key key, const bool bPressed); From 7ccef05b07ad3769e368ede016af356fda386de6 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Thu, 26 Feb 2026 03:43:18 -0500 Subject: [PATCH 084/101] [sh] update header --- olcPixelGameEngine3.h | 65 ++++++++++++++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 17 deletions(-) diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index c089ff12..136b1b86 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -3678,7 +3678,7 @@ namespace olc virtual bool olc_OnMouseButton(const uint8_t nButton, const bool bPressed); virtual bool olc_OnMouseMove(const olc::vi2d& vMousePos); virtual bool olc_OnMouseWheel(const int32_t nScroll); - virtual bool olc_OnMouseFocus(const bool bHasFocus); + virtual bool olc_OnFocus(const bool bHasFocus); // Set Window State virtual bool olc_OnWindowPosition(const olc::vi2d& vPos); @@ -3712,9 +3712,13 @@ namespace olc // Show or hide mouse cursor void ShowMouseCursor(const bool bShow); + // Focus + bool IsFocused() const; + protected: bool bRequestToClose = false; bool bShouldRemove = false; + bool bWindowIsFocused = false; protected: size_t nUniqueID = size_t(-1); @@ -5801,7 +5805,7 @@ namespace olc::host static bool olc_OnMouseButton(olc::Window* pWindow, const uint8_t nButton, const bool bPressed); static bool olc_OnMouseMove(olc::Window* pWindow, const olc::vi2d& vMousePos); static bool olc_OnMouseWheel(olc::Window* pWindow, const int32_t nScroll); - static bool olc_OnMouseFocus(olc::Window* pWindow, const bool bHasFocus); + static bool olc_OnFocus(olc::Window* pWindow, const bool bHasFocus); // Set Keyboard Device State static bool olc_OnKeyPress(olc::Window* pWindow, const olc::Key key, const bool bPressed); @@ -6814,7 +6818,6 @@ namespace olc::host // Make OS Update a window frame title, associated with olc::Window bool Host_None::UpdateWindowFrameTitle(olc::Window* pWindow) { - std::cout << pWindow->GetWindowTitle() << "\n"; return true; } @@ -7232,6 +7235,8 @@ namespace olc::host lp = GetWindowLongPtr(hWnd, GWL_EXSTYLE); SetWindowLongPtr(hWnd, GWL_EXSTYLE, lp | (WS_EX_WINDOWEDGE)); + pWindow->olc_OnFocus(true); + //SetWindowPos(hWnd, NULL, vWinPos.x, vWinPos.y, width, height, SWP_SHOWWINDOW); //ShowWindow(hWnd, 1); //UpdateWindow(hWnd); @@ -7243,7 +7248,6 @@ namespace olc::host // source window with a long_ptr to this class, and therefore we can't // call the appropriate event handler. - // Store the link bewteen host resource and window mapUID2HWND.insert_or_assign(pWindow->GetUID(), hWnd); mapHWND2PTR.insert_or_assign(hWnd, pWindow); @@ -7344,12 +7348,34 @@ namespace olc::host window->olc_OnMouseWheel(GET_WHEEL_DELTA_WPARAM(wParam)); break; } - // case WM_MOUSELEAVE: ptrPGE->olc_UpdateMouseFocus(false); return 0; - // case WM_SETFOCUS: ptrPGE->olc_UpdateKeyFocus(true); return 0; - // case WM_KILLFOCUS: ptrPGE->olc_UpdateKeyFocus(false); return 0; + + case WM_ACTIVATE: + { + window->olc_OnFocus((LOWORD(wParam) != WA_INACTIVE)); + return 0; + } + + case WM_MOUSEACTIVATE: + { + window->olc_OnFocus(true); + return MA_ACTIVATE; + } + + case WM_SETFOCUS: + { + window->olc_OnFocus(true); + return 0; + } + + case WM_KILLFOCUS: + { + window->olc_OnFocus(false); + return 0; + } case WM_KEYDOWN: { + window->olc_OnFocus(true); if (mapKeys.contains(int32_t(wParam))) { window->olc_OnKeyPress(mapKeys[int32_t(wParam)], true); @@ -10763,13 +10789,13 @@ namespace olc::host { if(auto* pge_window = get_pge_window(xev.xfocus.window); pge_window) { - pge_window->olc_OnMouseFocus(true); + pge_window->olc_OnFocus(true); } } else if (xev.type == FocusOut) { if(auto* pge_window = get_pge_window(xev.xfocus.window); pge_window) { - pge_window->olc_OnMouseFocus(false); + pge_window->olc_OnFocus(false); } } else if (xev.type == ClientMessage) @@ -11699,7 +11725,7 @@ namespace olc::host } auto* pge_window = mapUID2OlcWindow[active_window_id]; - pge_window->olc_OnMouseFocus(true); + pge_window->olc_OnFocus(true); } void Host_Linux_Wayland::keyboard_leave_callback(void* data, wl_keyboard* keyboard, uint32_t serial, wl_surface* surface) @@ -11711,7 +11737,7 @@ namespace olc::host void Host_Linux_Wayland::keyboard_leave(wl_keyboard* keyboard, uint32_t serial, wl_surface* surface) { auto* pge_window = mapUID2OlcWindow[active_window_id]; - pge_window->olc_OnMouseFocus(false); + pge_window->olc_OnFocus(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) @@ -12406,11 +12432,11 @@ namespace olc::host if (eventType == EMSCRIPTEN_EVENT_BLUR) { - olc_OnMouseFocus(pCallbackData->pWindow, false); + olc_OnFocus(pCallbackData->pWindow, false); } else if (eventType == EMSCRIPTEN_EVENT_FOCUS) { - olc_OnMouseFocus(pCallbackData->pWindow, true); + olc_OnFocus(pCallbackData->pWindow, true); } return 0; @@ -12431,9 +12457,9 @@ namespace olc::host return pWindow->olc_OnMouseWheel(nScroll); } - bool Host_Web_Emscripten::olc_OnMouseFocus(olc::Window* pWindow, const bool bHasFocus) + bool Host_Web_Emscripten::olc_OnFocus(olc::Window* pWindow, const bool bHasFocus) { - return pWindow->olc_OnMouseFocus(bHasFocus); + return pWindow->olc_OnFocus(bHasFocus); } bool Host_Web_Emscripten::olc_OnKeyPress(olc::Window* pWindow, const olc::Key key, const bool bPressed) @@ -17891,9 +17917,9 @@ namespace olc return true; } - bool Window::olc_OnMouseFocus(const bool bHasFocus) + bool Window::olc_OnFocus(const bool bHasFocus) { - olc_IgnoreUnused(bHasFocus); + bWindowIsFocused = bHasFocus; return false; } @@ -17979,6 +18005,11 @@ namespace olc pHost->SetMouseVisible(this, bShow); } + bool Window::IsFocused() const + { + return bWindowIsFocused; + } + }; #define PGE_WINDOW_IMPLEMENTED 1 #endif From 8b718a5706d885af4147841b344523a8b9ce3e4b Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Thu, 26 Feb 2026 03:58:23 -0500 Subject: [PATCH 085/101] [MacOS] use olc_OnFocus --- dev/src/host_apple_macos.cpp | 4 ++-- olcPixelGameEngine3.h | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dev/src/host_apple_macos.cpp b/dev/src/host_apple_macos.cpp index a24e5c75..d9c0e813 100644 --- a/dev/src/host_apple_macos.cpp +++ b/dev/src/host_apple_macos.cpp @@ -487,13 +487,13 @@ namespace olc::host { case DEMINIMIZE_WINDOW: case BECOME_ACTIVE: { - pPGEwindow->olc_OnMouseFocus(true); + pPGEwindow->olc_OnFocus(true); break; } case MINIMIZE_WINDOW: case RESIGN_ACTIVE: { - pPGEwindow->olc_OnMouseFocus(false); + pPGEwindow->olc_OnFocus(false); break; } case NONE: diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 136b1b86..e34a15c5 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -8036,13 +8036,13 @@ namespace olc::host { case DEMINIMIZE_WINDOW: case BECOME_ACTIVE: { - pPGEwindow->olc_OnMouseFocus(true); + pPGEwindow->olc_OnFocus(true); break; } case MINIMIZE_WINDOW: case RESIGN_ACTIVE: { - pPGEwindow->olc_OnMouseFocus(false); + pPGEwindow->olc_OnFocus(false); break; } case NONE: From 6bc7dfd903678ca9e9d73ce49223c4b774162d83 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Thu, 26 Feb 2026 04:17:10 -0500 Subject: [PATCH 086/101] [examples] add IsFocused example usage to olcPGE3_Keyboard --- examples/olcPGE3_Keyboard.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/examples/olcPGE3_Keyboard.cpp b/examples/olcPGE3_Keyboard.cpp index 565ce6af..8f475216 100644 --- a/examples/olcPGE3_Keyboard.cpp +++ b/examples/olcPGE3_Keyboard.cpp @@ -39,6 +39,14 @@ class Example_Keyboard : public olc::PixelGameEngine // Called every frame, so update things here bool OnUserUpdate(float fElapsedTime) override { + if(!IsFocused()) + { + // Clear whole screen + draw.Clear(olc::Colour::VERY_DARK_RED); + draw.String((ScreenSize() / 2) - (draw.GetTextSize("Not Focused") / 2), "Not Focused"); + return true; + } + auto metrics = draw.GetDrawMetrics(); draw.ResetDrawMetrics(); From 3fad36ed9e071aec8572fc8445999754ac6feec7 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Thu, 26 Feb 2026 07:25:09 -0500 Subject: [PATCH 087/101] [emscripten] pause/resume main loop in visibilityChangeEvent handler --- dev/src/host_web_emscripten.cpp | 2 ++ olcPixelGameEngine3.h | 2 ++ 2 files changed, 4 insertions(+) diff --git a/dev/src/host_web_emscripten.cpp b/dev/src/host_web_emscripten.cpp index d5411c22..2c594d34 100644 --- a/dev/src/host_web_emscripten.cpp +++ b/dev/src/host_web_emscripten.cpp @@ -585,9 +585,11 @@ namespace olc::host if (visibilityChangeEvent->hidden) { pCallbackData->pHost->timeHidden = std::chrono::steady_clock::now(); + emscripten_pause_main_loop(); } else { + emscripten_resume_main_loop(); auto durationHidden = std::chrono::steady_clock::now() - pCallbackData->pHost->timeHidden; pCallbackData->pHost->pPrimaryPGE->timeFrame2 += durationHidden; } diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index c9ef25af..47cf6fe6 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -12427,9 +12427,11 @@ namespace olc::host if (visibilityChangeEvent->hidden) { pCallbackData->pHost->timeHidden = std::chrono::steady_clock::now(); + emscripten_pause_main_loop(); } else { + emscripten_resume_main_loop(); auto durationHidden = std::chrono::steady_clock::now() - pCallbackData->pHost->timeHidden; pCallbackData->pHost->pPrimaryPGE->timeFrame2 += durationHidden; } From f6c8ecce838ff64e39e0ea7654013e03e0c22278 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Thu, 26 Feb 2026 18:27:05 -0500 Subject: [PATCH 088/101] [emscripten] revamp keyboard layout detection --- dev/src/host_web_emscripten.cpp | 88 +++++++++++++++++-------------- dev/src/host_web_emscripten.h | 6 +++ olcPixelGameEngine3.h | 93 +++++++++++++++++++-------------- 3 files changed, 110 insertions(+), 77 deletions(-) diff --git a/dev/src/host_web_emscripten.cpp b/dev/src/host_web_emscripten.cpp index 9f345829..60e360ac 100644 --- a/dev/src/host_web_emscripten.cpp +++ b/dev/src/host_web_emscripten.cpp @@ -10,44 +10,42 @@ namespace olc::host Host_Web_Emscripten::Host_Web_Emscripten() { std::cout << "Emscripten: host constructed.\n"; + std::string locale = getNavigatorLocale(); + std::transform(locale.begin(), locale.end(), locale.begin(), [](unsigned char c) { return std::tolower(c); }); - // Detect and Store Keyboard Layout - EM_ASM({ - if (!navigator.keyboard || !navigator.keyboard.getLayoutMap) - return; - - navigator.keyboard.getLayoutMap().then(function(map) - { - const keys = [map.get("KeyQ"), map.get("KeyW"), map.get("KeyE"), map.get("KeyR"), map.get("KeyT"), map.get("KeyY"), map.get("Backslash")]; - - // QWERTY - UK/US - if(keys[0] == 'q' && keys[1] == 'w' && keys[2] == 'e' && keys[3] == 'r' && keys[4] == 't' && keys[5] == 'y') { - if(keys[6] == '#' || keys[6] == '~') - { - Module.keyboardLayout = 0; - return; - } - else - { - Module.keyboardLayout = 1; - return; - } - } - - // QWERTZ - DE - if(keys[0] == 'q' && keys[1] == 'w' && keys[2] == 'e' && keys[3] == 'r' && keys[4] == 't' && keys[5] == 'z') { - Module.keyboardLayout = 2; - return; - } - - // AZERTY - FR - if(keys[0] == 'q' && keys[1] == 'w' && keys[2] == 'e' && keys[3] == 'r' && keys[4] == 't' && keys[5] == 'z') { - Module.keyboardLayout = 3; - return; - } - - }); - }); + size_t sep = locale.find('-'); + std::string lang = locale.substr(0, sep); + std::string region = (sep != std::string::npos) ? locale.substr(sep + 1) : ""; + + if(region == "ch" || region == "li") + { + keyboardLayout = KeyboardLayout::QWERTZ; + } + else if(locale == "fr-ca") + { + keyboardLayout = KeyboardLayout::QWERTY_US; + } + else if(lang == "fr") + { + keyboardLayout = KeyboardLayout::AZERTY; + } + else if ( + lang == "de" || lang == "cs" || lang == "sk" || + lang == "hu" || lang == "hr" || lang == "bs" || + lang == "sl") + { + keyboardLayout = KeyboardLayout::QWERTZ; + } + else if( + region == "gb" || region == "ie" || region == "za" || + region == "au" || region == "nz" || region == "in") + { + keyboardLayout = KeyboardLayout::QWERTY_UK; + } + else + { + keyboardLayout = KeyboardLayout::QWERTY_US; + } // Map Emscripten Defined DOM_PK_ Codes to olc::KeyCodes mapKeys[DOM_PK_UNKNOWN] = Key::NONE; @@ -289,7 +287,7 @@ namespace olc::host olc::KeyboardLayout Host_Web_Emscripten::GetKeyboardLayout() const { - return static_cast(EM_ASM_INT({ return Module.keyboardLayout || 0; })); + return keyboardLayout; } // Called at very start of application @@ -614,5 +612,19 @@ namespace olc::host { return pWindow->olc_OnWindowClose(); } + + std::string Host_Web_Emscripten::getNavigatorLocale() + { + char* raw = (char*)EM_ASM_PTR({ + var lang = window.navigator.language || "en-US"; + var len = lengthBytesUTF8(lang) + 1; + var buf = _malloc(len); + stringToUTF8(lang, buf, len); + return buf; + }); + std::string result(raw); + free(raw); + return result; + } } //! END IMPLEMENTATION diff --git a/dev/src/host_web_emscripten.h b/dev/src/host_web_emscripten.h index 595479cd..337345de 100644 --- a/dev/src/host_web_emscripten.h +++ b/dev/src/host_web_emscripten.h @@ -3,7 +3,9 @@ //! START STDHEADER GLOBAL #include +#include #include +#include #include #include //! END STDHEADER @@ -87,6 +89,9 @@ namespace olc::host static bool olc_OnWindowSize(olc::Window* pWindow, const olc::vi2d& vWindowSize); static bool olc_OnWindowClose(olc::Window* pWindow); + private: // Emscripten internal funcs + std::string getNavigatorLocale(); + public: // Callback data type struct CallbackData { Host_Web_Emscripten* pHost; @@ -103,6 +108,7 @@ namespace olc::host std::unordered_map mapKeys; // Map of system mouse buttons to olc mouse buttons std::unordered_map mapMouseButtons; + olc::KeyboardLayout keyboardLayout{OLC_DEFAULT_KEYBOARD_LAYOUT}; }; diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index c089ff12..11148d6e 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -5811,6 +5811,9 @@ namespace olc::host static bool olc_OnWindowSize(olc::Window* pWindow, const olc::vi2d& vWindowSize); static bool olc_OnWindowClose(olc::Window* pWindow); + private: // Emscripten internal funcs + std::string getNavigatorLocale(); + public: // Callback data type struct CallbackData { Host_Web_Emscripten* pHost; @@ -5827,6 +5830,7 @@ namespace olc::host std::unordered_map mapKeys; // Map of system mouse buttons to olc mouse buttons std::unordered_map mapMouseButtons; + olc::KeyboardLayout keyboardLayout{OLC_DEFAULT_KEYBOARD_LAYOUT}; }; @@ -6814,7 +6818,6 @@ namespace olc::host // Make OS Update a window frame title, associated with olc::Window bool Host_None::UpdateWindowFrameTitle(olc::Window* pWindow) { - std::cout << pWindow->GetWindowTitle() << "\n"; return true; } @@ -11851,44 +11854,42 @@ namespace olc::host Host_Web_Emscripten::Host_Web_Emscripten() { std::cout << "Emscripten: host constructed.\n"; + std::string locale = getNavigatorLocale(); + std::transform(locale.begin(), locale.end(), locale.begin(), [](unsigned char c) { return std::tolower(c); }); - // Detect and Store Keyboard Layout - EM_ASM({ - if (!navigator.keyboard || !navigator.keyboard.getLayoutMap) - return; - - navigator.keyboard.getLayoutMap().then(function(map) - { - const keys = [map.get("KeyQ"), map.get("KeyW"), map.get("KeyE"), map.get("KeyR"), map.get("KeyT"), map.get("KeyY"), map.get("Backslash")]; - - // QWERTY - UK/US - if(keys[0] == 'q' && keys[1] == 'w' && keys[2] == 'e' && keys[3] == 'r' && keys[4] == 't' && keys[5] == 'y') { - if(keys[6] == '#' || keys[6] == '~') - { - Module.keyboardLayout = 0; - return; - } - else - { - Module.keyboardLayout = 1; - return; - } - } - - // QWERTZ - DE - if(keys[0] == 'q' && keys[1] == 'w' && keys[2] == 'e' && keys[3] == 'r' && keys[4] == 't' && keys[5] == 'z') { - Module.keyboardLayout = 2; - return; - } - - // AZERTY - FR - if(keys[0] == 'q' && keys[1] == 'w' && keys[2] == 'e' && keys[3] == 'r' && keys[4] == 't' && keys[5] == 'z') { - Module.keyboardLayout = 3; - return; - } - - }); - }); + size_t sep = locale.find('-'); + std::string lang = locale.substr(0, sep); + std::string region = (sep != std::string::npos) ? locale.substr(sep + 1) : ""; + + if(region == "ch" || region == "li") + { + keyboardLayout = KeyboardLayout::QWERTZ; + } + else if(locale == "fr-ca") + { + keyboardLayout = KeyboardLayout::QWERTY_US; + } + else if(lang == "fr") + { + keyboardLayout = KeyboardLayout::AZERTY; + } + else if ( + lang == "de" || lang == "cs" || lang == "sk" || + lang == "hu" || lang == "hr" || lang == "bs" || + lang == "sl") + { + keyboardLayout = KeyboardLayout::QWERTZ; + } + else if( + region == "gb" || region == "ie" || region == "za" || + region == "au" || region == "nz" || region == "in") + { + keyboardLayout = KeyboardLayout::QWERTY_UK; + } + else + { + keyboardLayout = KeyboardLayout::QWERTY_US; + } // Map Emscripten Defined DOM_PK_ Codes to olc::KeyCodes mapKeys[DOM_PK_UNKNOWN] = Key::NONE; @@ -12130,7 +12131,7 @@ namespace olc::host olc::KeyboardLayout Host_Web_Emscripten::GetKeyboardLayout() const { - return static_cast(EM_ASM_INT({ return Module.keyboardLayout || 0; })); + return keyboardLayout; } // Called at very start of application @@ -12455,6 +12456,20 @@ namespace olc::host { return pWindow->olc_OnWindowClose(); } + + std::string Host_Web_Emscripten::getNavigatorLocale() + { + char* raw = (char*)EM_ASM_PTR({ + var lang = window.navigator.language || "en-US"; + var len = lengthBytesUTF8(lang) + 1; + var buf = _malloc(len); + stringToUTF8(lang, buf, len); + return buf; + }); + std::string result(raw); + free(raw); + return result; + } } #endif From b055025b90d63c23f5a84b7298bfc4eb40f092ed Mon Sep 17 00:00:00 2001 From: Ciaran Moran Date: Sat, 28 Feb 2026 01:05:10 +0000 Subject: [PATCH 089/101] Use wglCreateContextAttribsARB for context creation on windows --- dev/src/gpu_opengl33.cpp | 28 +++++++++++++++++++++++++++- olcPixelGameEngine3.h | 29 +++++++++++++++++++++++++++-- 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/dev/src/gpu_opengl33.cpp b/dev/src/gpu_opengl33.cpp index 873ab79c..51f8e306 100644 --- a/dev/src/gpu_opengl33.cpp +++ b/dev/src/gpu_opengl33.cpp @@ -238,9 +238,34 @@ void main() } auto glDeviceContext = GetDC((HWND)(os_win_id[0])); + HGLRC tempContext = wglCreateContext(glDeviceContext); + if (!tempContext) + { + lastError = RendererError::FailedToCreateRenderContext; + return false; + } + wglMakeCurrent(glDeviceContext, tempContext); + + typedef HGLRC(WINAPI* PFN_wglCreateContextAttribsARB)(HDC, HGLRC, const int*); + auto pfnCreateContextAttribs = (PFN_wglCreateContextAttribsARB)wglGetProcAddress("wglCreateContextAttribsARB"); + + wglMakeCurrent(nullptr, nullptr); + wglDeleteContext(tempContext); + + if (pfnCreateContextAttribs) + { + int gl33_attribs[] = { + 0x2091, 3, // WGL_CONTEXT_MAJOR_VERSION_ARB + 0x2092, 3, // WGL_CONTEXT_MINOR_VERSION_ARB + 0x9126, 0x00000001, // WGL_CONTEXT_PROFILE_MASK_ARB = CORE + 0}; + glRenderContext = pfnCreateContextAttribs(glDeviceContext, nullptr, gl33_attribs); + } + else + glRenderContext = wglCreateContext(glDeviceContext); // Create OpenGL Render Context - if (!(glRenderContext = wglCreateContext(glDeviceContext))) + if (!glRenderContext) { lastError = RendererError::FailedToCreateRenderContext; return false; @@ -251,6 +276,7 @@ void main() lastError = RendererError::FailedToSwitchRenderContext; return false; } + #endif #if OLC_HOST == OLC_HOST_LINUX_X11 diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index c089ff12..13525172 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -6814,7 +6814,6 @@ namespace olc::host // Make OS Update a window frame title, associated with olc::Window bool Host_None::UpdateWindowFrameTitle(olc::Window* pWindow) { - std::cout << pWindow->GetWindowTitle() << "\n"; return true; } @@ -14085,9 +14084,34 @@ void main() } auto glDeviceContext = GetDC((HWND)(os_win_id[0])); + HGLRC tempContext = wglCreateContext(glDeviceContext); + if (!tempContext) + { + lastError = RendererError::FailedToCreateRenderContext; + return false; + } + wglMakeCurrent(glDeviceContext, tempContext); + + typedef HGLRC(WINAPI* PFN_wglCreateContextAttribsARB)(HDC, HGLRC, const int*); + auto pfnCreateContextAttribs = (PFN_wglCreateContextAttribsARB)wglGetProcAddress("wglCreateContextAttribsARB"); + + wglMakeCurrent(nullptr, nullptr); + wglDeleteContext(tempContext); + + if (pfnCreateContextAttribs) + { + int gl33_attribs[] = { + 0x2091, 3, // WGL_CONTEXT_MAJOR_VERSION_ARB + 0x2092, 3, // WGL_CONTEXT_MINOR_VERSION_ARB + 0x9126, 0x00000001, // WGL_CONTEXT_PROFILE_MASK_ARB = CORE + 0}; + glRenderContext = pfnCreateContextAttribs(glDeviceContext, nullptr, gl33_attribs); + } + else + glRenderContext = wglCreateContext(glDeviceContext); // Create OpenGL Render Context - if (!(glRenderContext = wglCreateContext(glDeviceContext))) + if (!glRenderContext) { lastError = RendererError::FailedToCreateRenderContext; return false; @@ -14098,6 +14122,7 @@ void main() lastError = RendererError::FailedToSwitchRenderContext; return false; } + #endif #if OLC_HOST == OLC_HOST_LINUX_X11 From fed87964303da8c5acb225cbe0439408288dde38 Mon Sep 17 00:00:00 2001 From: tgd2 <59289427+tgd2@users.noreply.github.com> Date: Sat, 28 Feb 2026 16:33:25 +0000 Subject: [PATCH 090/101] Adding simple pixel shader examples Adding example file with three simple custom pixel shaders, of slightly increasing complexity Added new pixel art asset to support examples (with confirmation that this is original art created by TGD) --- examples/assets/pixelfish.png | Bin 0 -> 1590 bytes examples/olcPGE3_SimplePixelShaders.cpp | 178 ++++++++++++++++++++++++ 2 files changed, 178 insertions(+) create mode 100644 examples/assets/pixelfish.png create mode 100644 examples/olcPGE3_SimplePixelShaders.cpp diff --git a/examples/assets/pixelfish.png b/examples/assets/pixelfish.png new file mode 100644 index 0000000000000000000000000000000000000000..4bb811acff8cd7c08d4122b69958c54b7ad1603c GIT binary patch literal 1590 zcmV-62Fdw}P)004Lh0{{R3LVO8b00001b5ch_0Itp) z=>Px#Fi=cXMF0Q*9V=6QB~t*0QL9omUsq4U!^i*e=>Gss|Dyo^>?Z)^HYu(DgLa(% zcc}l&s_vsYd;kCd5Oh*bQvm<}|NsC0|NsC0|NsC0|G-d-LjV8;m`OxIRCt{2nt^hI zAPj~FinZ$c{*Su@1Og;Lgst{&*}7V!^z%;$!Szer+`h!|c3TSPZUI=bf52Y*M8HzW zw%eFdK2yP8Hm0=y3ijGRV6S~H;HW&-Yrmo(`{Ew-sXuh_HJ@OBu2ejal2(zv2*(0F zjEAfrRsn^07}Fo4pW;^g+P@I+ggc54lObz|4S?~-h^#g5Qw8L8?*bJ56NLzUPQQXf zW1zc;y1PS>57Gt*-v<`I5Nl<-7`;X~ zNebRh#1wh=ytNnb0bM8w=P^==sD`+!4^37mx#prt{z7^Msrj#23Vr(ER4saB(T2o+b0a zn8i(igk5`YISpQNF=ag$N7~S8rv2iGJ6t4bH(rK*NlKB!eC2CUR(Dd4rLhFCs`<_K`!wvB z%@A;&HR0Aui1W40Y(j?6?0pFU4V~CTDd zS7I>oU|!Yq?^n`{7%5mSEvg)_JvgM;jxr(_u%aLh z$f*=zMr1B^IY5&4C_?T{5kDEY3TAw%qU3&s#TTp-a7^A)N|EO}T|6Z3sbyJ6LThxi--i)aHhM}~J`b3I=77^KcCilvEN+!Ood&e?L}gN9 zfE)#D0@8qh&NHgq*8$K)-n8$G7$ozaBP972+(bM{m%fa}Tt^pS@XYBam;8^Wk3#JWDBd0d(#5fnmbcLUqGYZ-#Lu8+WX zuEunliJ7u#eB#Y^+sD{gl}rKiZPDpu7MCXPY;D_^JHP98mJlEh76}KsxUDjDB?MSW zN`%41%t?ZM)1;X%0n8&063+J42va<9KA3c%gkaW#B|P*TpD-yQ#@(YP)MXN8CBz6o z=YBLk#la!WSHJ;)aDL}AT%12N6STa9$o|uGh0q}4Nn9k)IUi9qjCC=Y#Qpc$LQ9%= zf_mOy3BdwX`Ie#P;uNPaXB=i+Leu~!^Lvi4U0hG%5g#1eTUtU?0H=ML-1eGdW%qzZ$a8K6X1=ZiAVb-;uWD%b;< zWt=cJAJC(KL9l|HN9ev#+Y6``<4$#CLDnArsI{nnL0MV<#q$ za5sQ*vGN`UfIPm~T)p-51F8}}l07*qoM6N<$g1ZLcI{*Lx literal 0 HcmV?d00001 diff --git a/examples/olcPGE3_SimplePixelShaders.cpp b/examples/olcPGE3_SimplePixelShaders.cpp new file mode 100644 index 00000000..869e4f4e --- /dev/null +++ b/examples/olcPGE3_SimplePixelShaders.cpp @@ -0,0 +1,178 @@ +/* + olc::PixelGameEngine3 Example - Simple Custom Pixel Shaders + + Example of three simple custom shaders: + 1. Greyscale (super simple) + 2. Negative (simple) + 3. Rainbow (still simple, but with a uniform defined) + + Licenced under the OLC-3 License +*/ + +#define OLC_PGE3_APPLICATION +#include "../olcPixelGameEngine3.h" + +class Example_SimpleCustomShaders : public olc::PixelGameEngine +{ +public: + Example_SimpleCustomShaders() {} + + float fTotalTime{ 0.0f }; + olc::Image imgPixelFish; + + olc::gpu::Shader_GLSL33 customShader_GreyScale; + olc::gpu::Shader_GLSL33 customShader_Negative; + olc::gpu::Shader_GLSL33 customShader_Rainbow; + + const olc::vf2d TextPadding{ 4.0f, 4.0f }; + + bool OnUserCreate() override + { + CreateImageFromFile(imgPixelFish, "./assets/pixelfish.png"); + + // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + // Set up greyscale shader: + + std::string PS_GreyscaleMain = + R"( + void main() + { + vec4 texColor = texture(pgeTexture0, oTex) * oCol; + float luminance = 0.299 * texColor.r + 0.587 * texColor.g + 0.114 * texColor.b; + vec4 texGreyscale = vec4(luminance, luminance, luminance, texColor.a); + pixel = vec4(texGreyscale.rgb * texGreyscale.a, texGreyscale.a); + } + )"; + + // Coefficients for luminance based on: https://en.wikipedia.org/wiki/Luma_(video) + + customShader_GreyScale.SetVertexShaderSource(olc::gpu::Shader::VS_DefaultHeader() + olc::gpu::Shader::VS_DefaultMain()); + customShader_GreyScale.SetPixelShaderSource(olc::gpu::Shader::PS_DefaultHeader() + PS_GreyscaleMain); + + // Check for shader compile errors + std::string sResult_GreyScale = customShader_GreyScale.Compile(); + if (sResult_GreyScale != "OK") + { + std::cout << "Error compiling shader: " << sResult_GreyScale << std::endl; + return false; + } + + // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + // Set up negative shader: + + std::string PS_NegativeMain = + R"( + void main() + { + vec4 texColor = texture(pgeTexture0, oTex) * oCol; + vec3 rgbNegative = 1.0 - texColor.rgb; + rgbNegative = rgbNegative.bgr; + rgbNegative += 0.15; + vec4 texNegative = vec4(rgbNegative.r, rgbNegative.g, rgbNegative.b, texColor.a); + pixel = vec4(texNegative.rgb * texNegative.a, texNegative.a); + } + )"; + + // Shader based on a simple approximation of Balatro negative shader + + customShader_Negative.SetVertexShaderSource(olc::gpu::Shader::VS_DefaultHeader() + olc::gpu::Shader::VS_DefaultMain()); + customShader_Negative.SetPixelShaderSource(olc::gpu::Shader::PS_DefaultHeader() + PS_NegativeMain); + + // Check for shader compile errors + std::string sResult_Negative = customShader_Negative.Compile(); + if (sResult_Negative != "OK") + { + std::cout << "Error compiling shader: " << sResult_Negative << std::endl; + return false; + } + + // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + // Set up rainbow shader: + + std::string PS_RainbowMain = + R"( + + uniform float time; + uniform float speed = 0.25; + uniform float intensity = 0.3; + + void main() + { + vec4 texColor = texture(pgeTexture0, oTex) * oCol; + + float y = (oTex.x * 0.4 + oTex.y * 0.6) + speed * time; + + texColor.r *= (1.0 - intensity) + intensity * sin(y * 6.0 + 0.0); + texColor.g *= (1.0 - intensity) + intensity * sin(y * 6.0 + 2.0 * 3.1415 / 3.0); + texColor.b *= (1.0 - intensity) + intensity * sin(y * 6.0 + 2.0 * 3.1415 * 2.0 / 3.0); + + pixel = vec4(texColor.rgb * texColor.a, texColor.a); + } + )"; + + // Simple rainbow shader - maths written out long hand for readability + + customShader_Rainbow.SetVertexShaderSource(olc::gpu::Shader::VS_DefaultHeader() + olc::gpu::Shader::VS_DefaultMain()); + customShader_Rainbow.SetPixelShaderSource(olc::gpu::Shader::PS_DefaultHeader() + PS_RainbowMain); + + // Check for shader compile errors + std::string sResult_Rainbow = customShader_Rainbow.Compile(); + if (sResult_Rainbow != "OK") + { + std::cout << "Error compiling shader: " << sResult_Rainbow << std::endl; + return false; + } + + customShader_Rainbow.CreateUniform("time"); + + // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + + return true; + } + + bool OnUserUpdate(float fElapsedTime) override + { + fTotalTime += fElapsedTime; + + // Top left image - default shader: + draw.ResetShader(); + olc::vf2d positionTL{ 0.0f, 0.0f }; + draw.Image(imgPixelFish, positionTL); + draw.String(positionTL + TextPadding, "DEFAULT SHADER", olc::Colour::BLACK); + + // Top right image - greyscale shader: + draw.SetShader(customShader_GreyScale); + olc::vf2d positionTR{ 0.5f * GetScreen().Size().x, 0.0f }; + draw.Image(imgPixelFish, positionTR); + draw.ResetShader(); + draw.String(positionTR + TextPadding, "GREYSCALE SHADER", olc::Colour::BLACK); + + // Bottom left image - negative shader: + draw.SetShader(customShader_Negative); + olc::vf2d positionBL{ 0.0f, 0.5f * GetScreen().Size().y}; + draw.Image(imgPixelFish, positionBL); + draw.ResetShader(); + draw.String(positionBL + TextPadding, "NEGATIVE SHADER", olc::Colour::BLACK); + + // Bottom right image - rainbow shader: + draw.SetShader(customShader_Rainbow); + draw.SetShaderUniform("time", fTotalTime); + olc::vf2d positionBR{ 0.5f * GetScreen().Size().x, 0.5f * GetScreen().Size().y }; + draw.Image(imgPixelFish, positionBR); + draw.ResetShader(); + draw.String(positionBR + TextPadding, "RAINBOW SHADER", olc::Colour::BLACK); + + return true; + } +}; + +int main() +{ + Example_SimpleCustomShaders simpleCustomShadersExample; + if (simpleCustomShadersExample.Construct({ 320, 240 }, { 4, 4 })) + simpleCustomShadersExample.Start(); + return 0; +} From 80b9edaa63f226bd8ae5fb3626fd3c3cb07bb426 Mon Sep 17 00:00:00 2001 From: dandistine <34634876+dandistine@users.noreply.github.com> Date: Sun, 1 Mar 2026 08:55:08 -0600 Subject: [PATCH 091/101] Simplify wayland protocol logic --- examples/CMakeLists.txt | 148 +++++++++------------------------------- 1 file changed, 34 insertions(+), 114 deletions(-) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 34c7227d..4f010482 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -33,130 +33,50 @@ if(UNIX AND BUILD_WAYLAND) # 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") - set(POINTER_WARP_PROTOCOL "/usr/share/wayland-protocols/staging/pointer-warp/pointer-warp-v1.xml") - set(CURSOR_SHAPE_PROTOCOL "/usr/share/wayland-protocols/staging/cursor-shape/cursor-shape-v1.xml") - set(TABLET_PROTOCOL "/usr/share/wayland-protocols/stable/tablet/tablet-v2.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() - - if(NOT EXISTS ${POINTER_WARP_PROTOCOL}) - message(FATAL_ERROR "pointer-warp protocol not found. You may need to install wayland-protocols via your package manager") - endif() - - if(NOT EXISTS ${CURSOR_SHAPE_PROTOCOL}) - message(FATAL_ERROR "cursor-shape protocol not found. You may need to install wayland-protocols via your package manager") - endif() - - if(NOT EXISTS ${TABLET_PROTOCOL}) - message(FATAL_ERROR "tablet 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) - set(POINTER_WARP_C ${CMAKE_CURRENT_BINARY_DIR}/pointer-warp.c) - set(POINTER_WARP_H ${CMAKE_CURRENT_BINARY_DIR}/pointer-warp.h) - set(CURSOR_SHAPE_C ${CMAKE_CURRENT_BINARY_DIR}/cursor-shape.c) - set(CURSOR_SHAPE_H ${CMAKE_CURRENT_BINARY_DIR}/cursor-shape.h) - set(TABLET_C ${CMAKE_CURRENT_BINARY_DIR}/tablet.c) - set(TABLET_H ${CMAKE_CURRENT_BINARY_DIR}/tablet.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 - ) + function(generate_wayland_protocol PROTOCOL_TARGET PROTOCOL_FILE) + if(NOT EXISTS ${PROTOCOL_FILE}) + message(FATAL_ERROR ${PROTOCOL_FILE} " not found. You may need to install wayland-protocols via your package manager") + endif() - add_custom_command( - OUTPUT ${POINTER_WARP_C} - COMMAND ${WAYLAND_SCANNER} private-code ${POINTER_WARP_PROTOCOL} ${POINTER_WARP_C} - DEPENDS ${POINTER_WARP_PROTOCOL} - VERBATIM - ) + set(C_FILENAME "${CMAKE_CURRENT_BINARY_DIR}/${PROTOCOL_TARGET}.c") + set(H_FILENAME "${CMAKE_CURRENT_BINARY_DIR}/${PROTOCOL_TARGET}.h") - add_custom_command( - OUTPUT ${POINTER_WARP_H} - COMMAND ${WAYLAND_SCANNER} client-header ${POINTER_WARP_PROTOCOL} ${POINTER_WARP_H} - DEPENDS ${POINTER_WARP_PROTOCOL} - VERBATIM - ) + add_custom_command( + OUTPUT ${C_FILENAME} + COMMAND ${WAYLAND_SCANNER} private-code ${PROTOCOL_FILE} ${C_FILENAME} + DEPENDS ${PROTOCOL_FILE} + VERBATIM + ) - add_custom_command( - OUTPUT ${CURSOR_SHAPE_C} - COMMAND ${WAYLAND_SCANNER} private-code ${CURSOR_SHAPE_PROTOCOL} ${CURSOR_SHAPE_C} - DEPENDS ${CURSOR_SHAPE_PROTOCOL} - VERBATIM - ) + add_custom_command( + OUTPUT ${H_FILENAME} + COMMAND ${WAYLAND_SCANNER} client-header ${PROTOCOL_FILE} ${H_FILENAME} + DEPENDS ${PROTOCOL_FILE} + VERBATIM + ) - add_custom_command( - OUTPUT ${CURSOR_SHAPE_H} - COMMAND ${WAYLAND_SCANNER} client-header ${CURSOR_SHAPE_PROTOCOL} ${CURSOR_SHAPE_H} - DEPENDS ${CURSOR_SHAPE_PROTOCOL} - VERBATIM - ) + set_source_files_properties(${C_FILENAME} ${H_FILENAME} PROPERTIES GENERATED TRUE) - add_custom_command( - OUTPUT ${TABLET_C} - COMMAND ${WAYLAND_SCANNER} private-code ${TABLET_PROTOCOL} ${TABLET_C} - DEPENDS ${TABLET_PROTOCOL} - VERBATIM - ) + add_library(wayland_${PROTOCOL_TARGET} STATIC ${C_FILENAME} ${H_FILENAME}) + endfunction() - add_custom_command( - OUTPUT ${TABLET_H} - COMMAND ${WAYLAND_SCANNER} client-header ${TABLET_PROTOCOL} ${TABLET_H} - DEPENDS ${TABLET_PROTOCOL} - VERBATIM - ) + generate_wayland_protocol("xdg-shell" "/usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml") + generate_wayland_protocol("xdg-decoration" "/usr/share/wayland-protocols/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml") + generate_wayland_protocol("pointer-warp" "/usr/share/wayland-protocols/staging/pointer-warp/pointer-warp-v1.xml") + generate_wayland_protocol("cursor-shape" "/usr/share/wayland-protocols/staging/cursor-shape/cursor-shape-v1.xml") + generate_wayland_protocol("tablet" "/usr/share/wayland-protocols/stable/tablet/tablet-v2.xml") # 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} - ${POINTER_WARP_C} - ${POINTER_WARP_H} - ${CURSOR_SHAPE_C} - ${CURSOR_SHAPE_H} - ${TABLET_C} - ${TABLET_H} + add_library(wayland_protocols INTERFACE) + target_link_libraries(wayland_protocols INTERFACE + wayland_xdg-shell + wayland_xdg-decoration + wayland_pointer-warp + wayland_cursor-shape + wayland_tablet ) - target_include_directories(wayland_protocols PUBLIC + target_include_directories(wayland_protocols INTERFACE ${CMAKE_CURRENT_BINARY_DIR} ) endif() From 6892cb0ac82157e2b25945ec18f15705bcd818d5 Mon Sep 17 00:00:00 2001 From: dandistine <34634876+dandistine@users.noreply.github.com> Date: Sun, 1 Mar 2026 11:05:40 -0600 Subject: [PATCH 092/101] Implement Alt key and fullscreen handlers for linux --- dev/src/host_iface.h | 2 + dev/src/host_lin_wayland.cpp | 28 +++++++++++--- dev/src/host_lin_wayland.h | 3 ++ dev/src/host_lin_x11.cpp | 29 ++++++++++++++ dev/src/host_lin_x11.h | 1 + dev/src/hw_keyboard.h | 2 +- dev/src/window.cpp | 5 +++ dev/src/window.h | 3 ++ examples/olcPGE3_Fish.cpp | 6 +++ olcPixelGameEngine3.h | 73 ++++++++++++++++++++++++++++++++---- 10 files changed, 138 insertions(+), 14 deletions(-) diff --git a/dev/src/host_iface.h b/dev/src/host_iface.h index aa0a6cde..9eb00a52 100644 --- a/dev/src/host_iface.h +++ b/dev/src/host_iface.h @@ -72,6 +72,8 @@ namespace olc 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; + // Set a window to fullscreen or not fullscreen + virtual bool SetFullScreen(olc::Window* pWindow, const bool bFullScreen) = 0; public: // OS Specific Environment Information virtual olc::KeyboardLayout GetKeyboardLayout() const = 0; diff --git a/dev/src/host_lin_wayland.cpp b/dev/src/host_lin_wayland.cpp index feefcabe..e6d29d79 100644 --- a/dev/src/host_lin_wayland.cpp +++ b/dev/src/host_lin_wayland.cpp @@ -103,6 +103,7 @@ namespace olc::host mapKeys[XKB_KEY_Scroll_Lock] = Key::SCROLL; mapKeys[XKB_KEY_Tab] = Key::TAB; mapKeys[XKB_KEY_Delete] = Key::DEL; mapKeys[XKB_KEY_Home] = Key::HOME; mapKeys[XKB_KEY_End] = Key::END; mapKeys[XKB_KEY_Page_Up] = Key::PGUP; mapKeys[XKB_KEY_Page_Down] = Key::PGDN; mapKeys[XKB_KEY_Insert] = Key::INS; mapKeys[XKB_KEY_Shift_L] = Key::SHIFT; mapKeys[XKB_KEY_Shift_R] = Key::SHIFT; mapKeys[XKB_KEY_Control_L] = Key::CTRL; mapKeys[XKB_KEY_Control_R] = Key::CTRL; + mapKeys[XKB_KEY_Alt_L] = Key::ALT; mapKeys[XKB_KEY_Alt_R] = Key::ALT; mapKeys[XKB_KEY_space] = Key::SPACE; mapKeys[XKB_KEY_period] = Key::PERIOD; mapKeys[XKB_KEY_0] = Key::K0; mapKeys[XKB_KEY_1] = Key::K1; mapKeys[XKB_KEY_2] = Key::K2; mapKeys[XKB_KEY_3] = Key::K3; mapKeys[XKB_KEY_4] = Key::K4; @@ -351,6 +352,19 @@ namespace olc::host return true; } + bool Host_Linux_Wayland::SetFullScreen(olc::Window* pWindow, const bool bFullScreen) + { + auto itr = mapUID2Window.find(pWindow->GetUID()); + if(itr != mapUID2Window.end()) { + itr->second.fullscreen = bFullScreen; + if(bFullScreen) { + xdg_toplevel_set_fullscreen(itr->second.toplevel, nullptr); + } else { + xdg_toplevel_unset_fullscreen(itr->second.toplevel); + } + } + return true; + } void Host_Linux_Wayland::registry_handle_global(wl_registry* registry, uint32_t name, const char* interface, uint32_t version) { @@ -404,12 +418,14 @@ namespace olc::host if(w.toplevel == toplevel) { // Attempt to constrain the window size to what the compositor may have told us earlier // in a bounds_configure message - if(w.bounds_x != 0) { - width = std::min(width, w.bounds_x); - } - - if(w.bounds_y != 0) { - height = std::min(height, w.bounds_y); + if(!w.fullscreen) { + if(w.bounds_x != 0) { + width = std::min(width, w.bounds_x); + } + + if(w.bounds_y != 0) { + height = std::min(height, w.bounds_y); + } } mapUID2OlcWindow[i.first]->olc_OnWindowSize({width, height}); diff --git a/dev/src/host_lin_wayland.h b/dev/src/host_lin_wayland.h index c916a953..62c2814d 100644 --- a/dev/src/host_lin_wayland.h +++ b/dev/src/host_lin_wayland.h @@ -43,6 +43,8 @@ namespace olc::host int32_t bounds_x{0}; int32_t bounds_y{0}; bool cursor_visible{true}; + // Ignore window size bounds for fullscreen events + bool fullscreen{false}; }; namespace wayland { @@ -120,6 +122,7 @@ namespace olc::host bool SyncWithDesktopComposite() override; bool SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) override; bool SetMouseVisible(olc::Window* pWindow, const bool bVisible) override; + bool SetFullScreen(olc::Window* pWindow, const bool bFullScreen) override; public: bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; diff --git a/dev/src/host_lin_x11.cpp b/dev/src/host_lin_x11.cpp index 2777da6b..c28d7a24 100644 --- a/dev/src/host_lin_x11.cpp +++ b/dev/src/host_lin_x11.cpp @@ -41,6 +41,7 @@ namespace olc::host mapKeys[XK_Scroll_Lock] = Key::SCROLL; mapKeys[XK_Tab] = Key::TAB; mapKeys[XK_Delete] = Key::DEL; mapKeys[XK_Home] = Key::HOME; mapKeys[XK_End] = Key::END; mapKeys[XK_Page_Up] = Key::PGUP; mapKeys[XK_Page_Down] = Key::PGDN; mapKeys[XK_Insert] = Key::INS; mapKeys[XK_Shift_L] = Key::SHIFT; mapKeys[XK_Shift_R] = Key::SHIFT; mapKeys[XK_Control_L] = Key::CTRL; mapKeys[XK_Control_R] = Key::CTRL; + mapKeys[XK_Alt_L] = Key::ALT; mapKeys[XK_Alt_R] = Key::ALT; mapKeys[XK_space] = Key::SPACE; mapKeys[XK_period] = Key::PERIOD; mapKeys[XK_0] = Key::K0; mapKeys[XK_1] = Key::K1; mapKeys[XK_2] = Key::K2; mapKeys[XK_3] = Key::K3; mapKeys[XK_4] = Key::K4; @@ -465,6 +466,34 @@ namespace olc::host return true; } + bool Host_Linux_X11::SetFullScreen(olc::Window* pWindow, const bool bFullScreen) + { + using namespace X11; + auto win = mapUID2X11Window.at(pWindow->GetUID()); + // If the bFullScreen flag is set, make the window fullscreen, otherwise restore it + Atom wm_state; + Atom fullscreen; + wm_state = XInternAtom(olc_Display, "_NET_WM_STATE", False); + fullscreen = XInternAtom(olc_Display, "_NET_WM_STATE_FULLSCREEN", False); + XEvent xev{ 0 }; + xev.type = ClientMessage; + xev.xclient.window = win; + xev.xclient.message_type = wm_state; + xev.xclient.format = 32; + xev.xclient.data.l[0] = (bFullScreen ? 1 : 0); // the action (0: off, 1: on, 2: toggle) + xev.xclient.data.l[1] = fullscreen; // first property to alter + xev.xclient.data.l[2] = 0; // second property to alter + xev.xclient.data.l[3] = 0; // source indication + XMapWindow(olc_Display, win); + XSendEvent(olc_Display, DefaultRootWindow(olc_Display), False, + SubstructureRedirectMask | SubstructureNotifyMask, &xev); + XFlush(olc_Display); + XWindowAttributes gwa; + XGetWindowAttributes(olc_Display, win, &gwa); + pWindow->olc_OnWindowSize({gwa.width, gwa.height}); + + return true; + } } //! END IMPLEMENTATION diff --git a/dev/src/host_lin_x11.h b/dev/src/host_lin_x11.h index 0d39373f..318d0cd4 100644 --- a/dev/src/host_lin_x11.h +++ b/dev/src/host_lin_x11.h @@ -55,6 +55,7 @@ namespace olc::host bool SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) override; // Show or hide mouse cursor for given window bool SetMouseVisible(olc::Window* pWindow, const bool bVisible) override; + bool SetFullScreen(olc::Window* pWindow, const bool bFullScreen) override; public: bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; diff --git a/dev/src/hw_keyboard.h b/dev/src/hw_keyboard.h index a44343d0..653e71db 100644 --- a/dev/src/hw_keyboard.h +++ b/dev/src/hw_keyboard.h @@ -52,7 +52,7 @@ namespace olc UP, DOWN, LEFT, RIGHT, // Other keys - SPACE, TAB, SHIFT, CTRL, INS, DEL, HOME, END, PGUP, PGDN, CAPS_LOCK, + SPACE, TAB, SHIFT, CTRL, ALT, INS, DEL, HOME, END, PGUP, PGDN, CAPS_LOCK, BACK, ESCAPE, RETURN, ENTER, PAUSE, SCROLL, EQUALS, COMMA, MINUS, // OEM specific keys diff --git a/dev/src/window.cpp b/dev/src/window.cpp index d936688c..b8e7c728 100644 --- a/dev/src/window.cpp +++ b/dev/src/window.cpp @@ -132,6 +132,11 @@ namespace olc pHost->SetMouseVisible(this, bShow); } + void Window::ShowFullScreen(const bool bFullScreen) + { + pHost->SetFullScreen(this, bFullScreen); + } + bool Window::IsFocused() const { return bWindowIsFocused; diff --git a/dev/src/window.h b/dev/src/window.h index 203fc801..bb35f5ac 100644 --- a/dev/src/window.h +++ b/dev/src/window.h @@ -96,6 +96,9 @@ namespace olc // Show or hide mouse cursor void ShowMouseCursor(const bool bShow); + // Set the Window to be FullScreen or Not Fullscreen + void ShowFullScreen(const bool bFullScreen); + // Focus bool IsFocused() const; diff --git a/examples/olcPGE3_Fish.cpp b/examples/olcPGE3_Fish.cpp index 5d8da581..31d9d849 100644 --- a/examples/olcPGE3_Fish.cpp +++ b/examples/olcPGE3_Fish.cpp @@ -370,6 +370,7 @@ class Example_Fish : public olc::PixelGameEngine std::vector others; std::vector targets; + bool fullscreen = false; public: // Called once at the start, so create things here bool OnUserCreate() override @@ -388,6 +389,11 @@ class Example_Fish : public olc::PixelGameEngine bool OnUserUpdate(float fElapsedTime) override { olc::Pixel background_color{73, 220, 222}; + if(GetKeyboard().GetKey(olc::Key::ALT).bHeld && GetKeyboard().GetKey(olc::Key::ENTER).bPressed) { + fullscreen = !fullscreen; + ShowFullScreen(fullscreen); + } + // Clear screen to a background color draw.Clear(background_color); diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 00c8a762..5c93df24 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -3566,7 +3566,7 @@ namespace olc UP, DOWN, LEFT, RIGHT, // Other keys - SPACE, TAB, SHIFT, CTRL, INS, DEL, HOME, END, PGUP, PGDN, CAPS_LOCK, + SPACE, TAB, SHIFT, CTRL, ALT, INS, DEL, HOME, END, PGUP, PGDN, CAPS_LOCK, BACK, ESCAPE, RETURN, ENTER, PAUSE, SCROLL, EQUALS, COMMA, MINUS, // OEM specific keys @@ -3712,6 +3712,9 @@ namespace olc // Show or hide mouse cursor void ShowMouseCursor(const bool bShow); + // Set the Window to be FullScreen or Not Fullscreen + void ShowFullScreen(const bool bFullScreen); + // Focus bool IsFocused() const; @@ -3796,6 +3799,8 @@ namespace olc 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; + // Set a window to fullscreen or not fullscreen + virtual bool SetFullScreen(olc::Window* pWindow, const bool bFullScreen) = 0; public: // OS Specific Environment Information virtual olc::KeyboardLayout GetKeyboardLayout() const = 0; @@ -5512,6 +5517,7 @@ namespace olc::host bool SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) override; // Show or hide mouse cursor for given window bool SetMouseVisible(olc::Window* pWindow, const bool bVisible) override; + bool SetFullScreen(olc::Window* pWindow, const bool bFullScreen) override; public: bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; @@ -5573,6 +5579,8 @@ namespace olc::host int32_t bounds_x{0}; int32_t bounds_y{0}; bool cursor_visible{true}; + // Ignore window size bounds for fullscreen events + bool fullscreen{false}; }; namespace wayland { @@ -5650,6 +5658,7 @@ namespace olc::host bool SyncWithDesktopComposite() override; bool SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) override; bool SetMouseVisible(olc::Window* pWindow, const bool bVisible) override; + bool SetFullScreen(olc::Window* pWindow, const bool bFullScreen) override; public: bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; @@ -10608,6 +10617,7 @@ namespace olc::host mapKeys[XK_Scroll_Lock] = Key::SCROLL; mapKeys[XK_Tab] = Key::TAB; mapKeys[XK_Delete] = Key::DEL; mapKeys[XK_Home] = Key::HOME; mapKeys[XK_End] = Key::END; mapKeys[XK_Page_Up] = Key::PGUP; mapKeys[XK_Page_Down] = Key::PGDN; mapKeys[XK_Insert] = Key::INS; mapKeys[XK_Shift_L] = Key::SHIFT; mapKeys[XK_Shift_R] = Key::SHIFT; mapKeys[XK_Control_L] = Key::CTRL; mapKeys[XK_Control_R] = Key::CTRL; + mapKeys[XK_Alt_L] = Key::ALT; mapKeys[XK_Alt_R] = Key::ALT; mapKeys[XK_space] = Key::SPACE; mapKeys[XK_period] = Key::PERIOD; mapKeys[XK_0] = Key::K0; mapKeys[XK_1] = Key::K1; mapKeys[XK_2] = Key::K2; mapKeys[XK_3] = Key::K3; mapKeys[XK_4] = Key::K4; @@ -11032,7 +11042,35 @@ namespace olc::host return true; } + bool Host_Linux_X11::SetFullScreen(olc::Window* pWindow, const bool bFullScreen) + { + using namespace X11; + auto win = mapUID2X11Window.at(pWindow->GetUID()); + + // If the bFullScreen flag is set, make the window fullscreen, otherwise restore it + Atom wm_state; + Atom fullscreen; + wm_state = XInternAtom(olc_Display, "_NET_WM_STATE", False); + fullscreen = XInternAtom(olc_Display, "_NET_WM_STATE_FULLSCREEN", False); + XEvent xev{ 0 }; + xev.type = ClientMessage; + xev.xclient.window = win; + xev.xclient.message_type = wm_state; + xev.xclient.format = 32; + xev.xclient.data.l[0] = (bFullScreen ? 1 : 0); // the action (0: off, 1: on, 2: toggle) + xev.xclient.data.l[1] = fullscreen; // first property to alter + xev.xclient.data.l[2] = 0; // second property to alter + xev.xclient.data.l[3] = 0; // source indication + XMapWindow(olc_Display, win); + XSendEvent(olc_Display, DefaultRootWindow(olc_Display), False, + SubstructureRedirectMask | SubstructureNotifyMask, &xev); + XFlush(olc_Display); + XWindowAttributes gwa; + XGetWindowAttributes(olc_Display, win, &gwa); + pWindow->olc_OnWindowSize({gwa.width, gwa.height}); + return true; + } } #endif @@ -11138,6 +11176,7 @@ namespace olc::host mapKeys[XKB_KEY_Scroll_Lock] = Key::SCROLL; mapKeys[XKB_KEY_Tab] = Key::TAB; mapKeys[XKB_KEY_Delete] = Key::DEL; mapKeys[XKB_KEY_Home] = Key::HOME; mapKeys[XKB_KEY_End] = Key::END; mapKeys[XKB_KEY_Page_Up] = Key::PGUP; mapKeys[XKB_KEY_Page_Down] = Key::PGDN; mapKeys[XKB_KEY_Insert] = Key::INS; mapKeys[XKB_KEY_Shift_L] = Key::SHIFT; mapKeys[XKB_KEY_Shift_R] = Key::SHIFT; mapKeys[XKB_KEY_Control_L] = Key::CTRL; mapKeys[XKB_KEY_Control_R] = Key::CTRL; + mapKeys[XKB_KEY_Alt_L] = Key::ALT; mapKeys[XKB_KEY_Alt_R] = Key::ALT; mapKeys[XKB_KEY_space] = Key::SPACE; mapKeys[XKB_KEY_period] = Key::PERIOD; mapKeys[XKB_KEY_0] = Key::K0; mapKeys[XKB_KEY_1] = Key::K1; mapKeys[XKB_KEY_2] = Key::K2; mapKeys[XKB_KEY_3] = Key::K3; mapKeys[XKB_KEY_4] = Key::K4; @@ -11386,6 +11425,19 @@ namespace olc::host return true; } + bool Host_Linux_Wayland::SetFullScreen(olc::Window* pWindow, const bool bFullScreen) + { + auto itr = mapUID2Window.find(pWindow->GetUID()); + if(itr != mapUID2Window.end()) { + itr->second.fullscreen = bFullScreen; + if(bFullScreen) { + xdg_toplevel_set_fullscreen(itr->second.toplevel, nullptr); + } else { + xdg_toplevel_unset_fullscreen(itr->second.toplevel); + } + } + return true; + } void Host_Linux_Wayland::registry_handle_global(wl_registry* registry, uint32_t name, const char* interface, uint32_t version) { @@ -11439,12 +11491,14 @@ namespace olc::host if(w.toplevel == toplevel) { // Attempt to constrain the window size to what the compositor may have told us earlier // in a bounds_configure message - if(w.bounds_x != 0) { - width = std::min(width, w.bounds_x); - } - - if(w.bounds_y != 0) { - height = std::min(height, w.bounds_y); + if(!w.fullscreen) { + if(w.bounds_x != 0) { + width = std::min(width, w.bounds_x); + } + + if(w.bounds_y != 0) { + height = std::min(height, w.bounds_y); + } } mapUID2OlcWindow[i.first]->olc_OnWindowSize({width, height}); @@ -18048,6 +18102,11 @@ namespace olc pHost->SetMouseVisible(this, bShow); } + void Window::ShowFullScreen(const bool bFullScreen) + { + pHost->SetFullScreen(this, bFullScreen); + } + bool Window::IsFocused() const { return bWindowIsFocused; From be37a4e04ad14c15f6e9c68fed3d9d83a14c71b1 Mon Sep 17 00:00:00 2001 From: John Galvin Date: Sun, 22 Feb 2026 13:37:00 +0000 Subject: [PATCH 093/101] 81-need-app-name-in-titlebar-22-02-2026 --- dev/src/core.cpp | 2 +- dev/src/core.h | 3 +++ dev/tests/test_mh.cpp | 2 +- examples/olcPGE3_3DCube.cpp | 2 +- examples/olcPGE3_AntiAliasing.cpp | 1 + examples/olcPGE3_BatchesOfFills.cpp | 2 +- examples/olcPGE3_BatchesOfLines.cpp | 2 +- examples/olcPGE3_Camera2D_Tiles.cpp | 2 +- examples/olcPGE3_CirclesEllipses.cpp | 2 +- examples/olcPGE3_Extensions.cpp | 2 ++ examples/olcPGE3_Fish.cpp | 2 +- examples/olcPGE3_ImageQuads.cpp | 2 +- examples/olcPGE3_ImageRect.cpp | 2 +- examples/olcPGE3_ImageRegion.cpp | 2 +- examples/olcPGE3_ImageScaleRotate.cpp | 2 +- examples/olcPGE3_ImageTiling.cpp | 2 +- examples/olcPGE3_Keyboard.cpp | 2 +- examples/olcPGE3_Lines.cpp | 2 +- examples/olcPGE3_Mouse.cpp | 2 +- examples/olcPGE3_PixelShaders.cpp | 2 +- examples/olcPGE3_Pixels.cpp | 2 +- examples/olcPGE3_Polygons.cpp | 2 +- examples/olcPGE3_Rectangles.cpp | 2 +- examples/olcPGE3_Shockwave.cpp | 2 +- examples/olcPGE3_TextBasics.cpp | 2 +- examples/olcPGE3_TextFontMap.cpp | 2 +- examples/olcPGE3_WorldTransform.cpp | 2 +- 27 files changed, 30 insertions(+), 24 deletions(-) diff --git a/dev/src/core.cpp b/dev/src/core.cpp index e3af3d40..0a7aff23 100644 --- a/dev/src/core.cpp +++ b/dev/src/core.cpp @@ -542,7 +542,7 @@ namespace olc if (durationFrameCount >= 1s) { durationFrameCount -= 1s; - std::string sTitle = "OneLoneCoder.com - Pixel Game Engine 3 - Test - FPS: " + std::to_string(frameCount); + std::string sTitle = "OneLoneCoder.com - Pixel Game Engine 3 - " + sAppName + " - FPS: " + std::to_string(frameCount); SetWindowTitle(sTitle); fps = frameCount; frameCount = 0; diff --git a/dev/src/core.h b/dev/src/core.h index 9de6114b..1cd8fe29 100644 --- a/dev/src/core.h +++ b/dev/src/core.h @@ -134,6 +134,9 @@ namespace olc protected: // PGE Configuration PGEConfig config; + + // Application Name + std::string sAppName = ""; }; // The olc::PixelGameEngine3 core, manages the main window, child windows, engine loop, timing and devices diff --git a/dev/tests/test_mh.cpp b/dev/tests/test_mh.cpp index a58b860f..b66a48f7 100644 --- a/dev/tests/test_mh.cpp +++ b/dev/tests/test_mh.cpp @@ -34,7 +34,7 @@ class Example : public olc::PixelGameEngine public: Example() { - + sAppName = "Test mh"; } olc::Image imTest; diff --git a/examples/olcPGE3_3DCube.cpp b/examples/olcPGE3_3DCube.cpp index 0a6a2628..95e158a9 100644 --- a/examples/olcPGE3_3DCube.cpp +++ b/examples/olcPGE3_3DCube.cpp @@ -20,7 +20,7 @@ class Example_3DCube : public olc::PixelGameEngine public: Example_3DCube() { - + sAppName = "Example - 3D Stanity Cube"; } protected: diff --git a/examples/olcPGE3_AntiAliasing.cpp b/examples/olcPGE3_AntiAliasing.cpp index eb087dce..ed51e6ce 100644 --- a/examples/olcPGE3_AntiAliasing.cpp +++ b/examples/olcPGE3_AntiAliasing.cpp @@ -22,6 +22,7 @@ class Example_AntiAliasing : public olc::PixelGameEngine public: Example_AntiAliasing() { + sAppName = "Example - Anti-Aliasing"; } protected: diff --git a/examples/olcPGE3_BatchesOfFills.cpp b/examples/olcPGE3_BatchesOfFills.cpp index b62e8bda..8efe6ea5 100644 --- a/examples/olcPGE3_BatchesOfFills.cpp +++ b/examples/olcPGE3_BatchesOfFills.cpp @@ -20,7 +20,7 @@ class Example_BatchesOfFills : public olc::PixelGameEngine public: Example_BatchesOfFills() { - + sAppName = "Example - Batches of Fills"; } protected: diff --git a/examples/olcPGE3_BatchesOfLines.cpp b/examples/olcPGE3_BatchesOfLines.cpp index 2c00f3e8..9a169e98 100644 --- a/examples/olcPGE3_BatchesOfLines.cpp +++ b/examples/olcPGE3_BatchesOfLines.cpp @@ -20,7 +20,7 @@ class Example_BatchesOfLines : public olc::PixelGameEngine public: Example_BatchesOfLines() { - + sAppName = "Example - Batches of Lines"; } public: diff --git a/examples/olcPGE3_Camera2D_Tiles.cpp b/examples/olcPGE3_Camera2D_Tiles.cpp index 8f53da8d..cb1467fd 100644 --- a/examples/olcPGE3_Camera2D_Tiles.cpp +++ b/examples/olcPGE3_Camera2D_Tiles.cpp @@ -33,7 +33,7 @@ class Example_Camera2D_Tiles : public olc::PixelGameEngine public: Example_Camera2D_Tiles() { - + sAppName = "Example Camera2D - Tiles, Panning and Zooming"; } protected: diff --git a/examples/olcPGE3_CirclesEllipses.cpp b/examples/olcPGE3_CirclesEllipses.cpp index b7e54e64..51a6fabf 100644 --- a/examples/olcPGE3_CirclesEllipses.cpp +++ b/examples/olcPGE3_CirclesEllipses.cpp @@ -20,7 +20,7 @@ class Example_RoundThings : public olc::PixelGameEngine public: Example_RoundThings() { - + sAppName = "Example - Circles and Ellipses"; } protected: diff --git a/examples/olcPGE3_Extensions.cpp b/examples/olcPGE3_Extensions.cpp index a2e755c1..582e5633 100644 --- a/examples/olcPGE3_Extensions.cpp +++ b/examples/olcPGE3_Extensions.cpp @@ -89,6 +89,8 @@ class Example_Extensions : public olc::PixelGameEngine // You can install as many extensions as you like, and they will be called in // the order they were installed InstallWindowExtension(&pgex); + + sAppName = "Example - Extensions"; } protected: diff --git a/examples/olcPGE3_Fish.cpp b/examples/olcPGE3_Fish.cpp index 5d8da581..a72e1223 100644 --- a/examples/olcPGE3_Fish.cpp +++ b/examples/olcPGE3_Fish.cpp @@ -362,7 +362,7 @@ class Example_Fish : public olc::PixelGameEngine public: Example_Fish() { - + sAppName = "Example - Fish... lots of really cool fish"; } protected: diff --git a/examples/olcPGE3_ImageQuads.cpp b/examples/olcPGE3_ImageQuads.cpp index 9a7a201f..e753da16 100644 --- a/examples/olcPGE3_ImageQuads.cpp +++ b/examples/olcPGE3_ImageQuads.cpp @@ -20,7 +20,7 @@ class Example_ImageQuads : public olc::PixelGameEngine public: Example_ImageQuads() { - + sAppName = "Example - Image Quads"; } protected: diff --git a/examples/olcPGE3_ImageRect.cpp b/examples/olcPGE3_ImageRect.cpp index 5542b9ad..0a710ac3 100644 --- a/examples/olcPGE3_ImageRect.cpp +++ b/examples/olcPGE3_ImageRect.cpp @@ -23,7 +23,7 @@ class Example_Image : public olc::PixelGameEngine public: Example_Image() { - + sAppName = "Example - ImageRect"; } protected: diff --git a/examples/olcPGE3_ImageRegion.cpp b/examples/olcPGE3_ImageRegion.cpp index eaaf34cf..e477986a 100644 --- a/examples/olcPGE3_ImageRegion.cpp +++ b/examples/olcPGE3_ImageRegion.cpp @@ -24,7 +24,7 @@ class Example_Image : public olc::PixelGameEngine public: Example_Image() { - + sAppName = "Example - ImageRect"; } protected: diff --git a/examples/olcPGE3_ImageScaleRotate.cpp b/examples/olcPGE3_ImageScaleRotate.cpp index 27337ab5..7cf23046 100644 --- a/examples/olcPGE3_ImageScaleRotate.cpp +++ b/examples/olcPGE3_ImageScaleRotate.cpp @@ -22,7 +22,7 @@ class Example_ImageRotated : public olc::PixelGameEngine public: Example_ImageRotated() { - + sAppName = "Example - ImageRotated (and scaled)"; } protected: diff --git a/examples/olcPGE3_ImageTiling.cpp b/examples/olcPGE3_ImageTiling.cpp index f42840df..5266d576 100644 --- a/examples/olcPGE3_ImageTiling.cpp +++ b/examples/olcPGE3_ImageTiling.cpp @@ -36,7 +36,7 @@ class Example_ImageTiling : public olc::PixelGameEngine public: Example_ImageTiling() { - + sAppName = "Example Image Tiling"; } protected: diff --git a/examples/olcPGE3_Keyboard.cpp b/examples/olcPGE3_Keyboard.cpp index 8f475216..b9ad4b36 100644 --- a/examples/olcPGE3_Keyboard.cpp +++ b/examples/olcPGE3_Keyboard.cpp @@ -20,7 +20,7 @@ class Example_Keyboard : public olc::PixelGameEngine public: Example_Keyboard() { - + sAppName = "Example - Keyboard"; } protected: diff --git a/examples/olcPGE3_Lines.cpp b/examples/olcPGE3_Lines.cpp index d27920d6..0408ea23 100644 --- a/examples/olcPGE3_Lines.cpp +++ b/examples/olcPGE3_Lines.cpp @@ -20,7 +20,7 @@ class Example_Lines : public olc::PixelGameEngine public: Example_Lines() { - + sAppName = "Example - Lines"; } public: diff --git a/examples/olcPGE3_Mouse.cpp b/examples/olcPGE3_Mouse.cpp index dba962e9..b47be4b4 100644 --- a/examples/olcPGE3_Mouse.cpp +++ b/examples/olcPGE3_Mouse.cpp @@ -31,7 +31,7 @@ class Example_Mouse : public olc::PixelGameEngine public: Example_Mouse() { - + sAppName = "Example Mouse"; } public: diff --git a/examples/olcPGE3_PixelShaders.cpp b/examples/olcPGE3_PixelShaders.cpp index f12bc0ef..cbc8dbf0 100644 --- a/examples/olcPGE3_PixelShaders.cpp +++ b/examples/olcPGE3_PixelShaders.cpp @@ -30,7 +30,7 @@ class Example_CustomPixelShader : public olc::PixelGameEngine public: Example_CustomPixelShader() { - + sAppName = "Example - Custom Pixel Shader"; } protected: diff --git a/examples/olcPGE3_Pixels.cpp b/examples/olcPGE3_Pixels.cpp index 65c45a8d..25ae3d7e 100644 --- a/examples/olcPGE3_Pixels.cpp +++ b/examples/olcPGE3_Pixels.cpp @@ -20,7 +20,7 @@ class Example_Pixels : public olc::PixelGameEngine public: Example_Pixels() { - + sAppName = "Example - Pixels ...lots of pixels"; } public: diff --git a/examples/olcPGE3_Polygons.cpp b/examples/olcPGE3_Polygons.cpp index 529d31f2..7f291a08 100644 --- a/examples/olcPGE3_Polygons.cpp +++ b/examples/olcPGE3_Polygons.cpp @@ -20,7 +20,7 @@ class Example_Pixels : public olc::PixelGameEngine public: Example_Pixels() { - + sAppName = "Example - Polygons"; } protected: diff --git a/examples/olcPGE3_Rectangles.cpp b/examples/olcPGE3_Rectangles.cpp index 578b5b53..34ac72cc 100644 --- a/examples/olcPGE3_Rectangles.cpp +++ b/examples/olcPGE3_Rectangles.cpp @@ -20,7 +20,7 @@ class Example_Rectangles : public olc::PixelGameEngine public: Example_Rectangles() { - + sAppName = "Example - Rectangles"; } public: diff --git a/examples/olcPGE3_Shockwave.cpp b/examples/olcPGE3_Shockwave.cpp index 6d358088..d2819e1c 100644 --- a/examples/olcPGE3_Shockwave.cpp +++ b/examples/olcPGE3_Shockwave.cpp @@ -29,7 +29,7 @@ class Example_Shockwave : public olc::PixelGameEngine public: Example_Shockwave() { - + sAppName = "Example - Shockwave Pixel Shader"; } protected: diff --git a/examples/olcPGE3_TextBasics.cpp b/examples/olcPGE3_TextBasics.cpp index 2023aac0..05ced303 100644 --- a/examples/olcPGE3_TextBasics.cpp +++ b/examples/olcPGE3_TextBasics.cpp @@ -20,7 +20,7 @@ class Example_Text : public olc::PixelGameEngine public: Example_Text() { - + sAppName = "Example - Text Basics"; } protected: diff --git a/examples/olcPGE3_TextFontMap.cpp b/examples/olcPGE3_TextFontMap.cpp index 595578a7..e8464e10 100644 --- a/examples/olcPGE3_TextFontMap.cpp +++ b/examples/olcPGE3_TextFontMap.cpp @@ -20,7 +20,7 @@ class Example_FontMap: public olc::PixelGameEngine public: Example_FontMap() { - + sAppName = "Example - Custom Fonts"; } protected: diff --git a/examples/olcPGE3_WorldTransform.cpp b/examples/olcPGE3_WorldTransform.cpp index 375ff547..2e97c651 100644 --- a/examples/olcPGE3_WorldTransform.cpp +++ b/examples/olcPGE3_WorldTransform.cpp @@ -29,7 +29,7 @@ class Example_WorldTransform: public olc::PixelGameEngine public: Example_WorldTransform() { - + sAppName = "Example - World Transformations"; } protected: From 2fee7ab45e0e426f51791d5842bdb234caa0f299 Mon Sep 17 00:00:00 2001 From: John Galvin Date: Thu, 26 Feb 2026 20:10:14 +0000 Subject: [PATCH 094/101] Added PGEConfig.sAppName with discriminator, PGEConfig.sAppname takes precedence --- dev/src/core.cpp | 19 +++++++++++++++++++ dev/src/core.h | 2 ++ dev/tests/test_mh.cpp | 5 ++++- examples/olcPGE3_3DCube.cpp | 3 ++- olcPixelGameEngine3.h | 21 +++++++++++++++++++++ 5 files changed, 48 insertions(+), 2 deletions(-) diff --git a/dev/src/core.cpp b/dev/src/core.cpp index 0a7aff23..7b3a0449 100644 --- a/dev/src/core.cpp +++ b/dev/src/core.cpp @@ -362,6 +362,25 @@ namespace olc { config = cfg; + // Check for constructor sAppName, if not set use Config sAppName + if (sAppName.empty()) + { + sAppName = config.sAppName; + } + else + { + if (config.sAppName == "PGE3") + { + // User has not set Config sAppName to something specific, so set the Config sAppName to constructor sAppName + config.sAppName = sAppName; + } + else + { + // User has set config.sAppName therefore we override constructor sAppname , + sAppName = config.sAppName; + } + } + // This is the earliest point within familiar PGE ecosystem // where we can instatiate the host interface. diff --git a/dev/src/core.h b/dev/src/core.h index 1cd8fe29..81f9b757 100644 --- a/dev/src/core.h +++ b/dev/src/core.h @@ -59,6 +59,8 @@ namespace olc bool bAntiAliasMainScreen = false; // Default clear colour for the primary drawing surface olc::Pixel colClear = olc::Colour::BLACK; + // Default Application Name (shown in window title bar) + std::string sAppName = "PGE3"; }; // A PGE Window is a window with drawing and input capabilities a la olc::PixelGameEngine diff --git a/dev/tests/test_mh.cpp b/dev/tests/test_mh.cpp index b66a48f7..aa2b90f2 100644 --- a/dev/tests/test_mh.cpp +++ b/dev/tests/test_mh.cpp @@ -34,7 +34,9 @@ class Example : public olc::PixelGameEngine public: Example() { - sAppName = "Test mh"; + // sAppName can be set here, or in the config passed to Construct() + // NOTE: If you set it here, it will be overridden by the config version of sAppName, if specified + // sAppName = "Test mh"; } olc::Image imTest; @@ -781,6 +783,7 @@ int main() cfg.vScreenSize = { 256, 240 }; //cfg.bAntiAliasMainScreen = true; cfg.bVSync = false; + cfg.sAppName = "Test mh"; //if (demo.Construct({ 1280, 960 }, { 1, 1 }, cfg)) if(demo.Construct(cfg)) diff --git a/examples/olcPGE3_3DCube.cpp b/examples/olcPGE3_3DCube.cpp index 95e158a9..87183612 100644 --- a/examples/olcPGE3_3DCube.cpp +++ b/examples/olcPGE3_3DCube.cpp @@ -20,7 +20,7 @@ class Example_3DCube : public olc::PixelGameEngine public: Example_3DCube() { - sAppName = "Example - 3D Stanity Cube"; + } protected: @@ -220,6 +220,7 @@ int main() config.vScreenSize = { 256, 240 }; config.vPixelSize = { 4, 4 }; config.bAntiAliasMainScreen = false; + config.sAppName = "Example - 3D Stanity Cube"; if (demo.Construct(config)) { diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 00c8a762..5bc0f1ad 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -3925,6 +3925,8 @@ namespace olc bool bAntiAliasMainScreen = false; // Default clear colour for the primary drawing surface olc::Pixel colClear = olc::Colour::BLACK; + // Default Application Name (shown in window title bar) + std::string sAppName = "PGE3"; }; // A PGE Window is a window with drawing and input capabilities a la olc::PixelGameEngine @@ -17112,6 +17114,25 @@ namespace olc { config = cfg; + // Check for constructor sAppName, if not set use Config sAppName + if (sAppName.empty()) + { + sAppName = config.sAppName; + } + else + { + if (config.sAppName == "PGE3") + { + // User has not set Config sAppName to something specific, so set the Config sAppName to constructor sAppName + config.sAppName = sAppName; + } + else + { + // User has set config.sAppName therefore we override constructor sAppname , + sAppName = config.sAppName; + } + } + // This is the earliest point within familiar PGE ecosystem // where we can instatiate the host interface. From 9e24173e76f740f46573b94f6324b15b4befc2cb Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Sun, 1 Mar 2026 15:11:48 -0500 Subject: [PATCH 095/101] fix typo on 3DCube example app name --- examples/olcPGE3_3DCube.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/olcPGE3_3DCube.cpp b/examples/olcPGE3_3DCube.cpp index 87183612..a51cdbc3 100644 --- a/examples/olcPGE3_3DCube.cpp +++ b/examples/olcPGE3_3DCube.cpp @@ -220,7 +220,7 @@ int main() config.vScreenSize = { 256, 240 }; config.vPixelSize = { 4, 4 }; config.bAntiAliasMainScreen = false; - config.sAppName = "Example - 3D Stanity Cube"; + config.sAppName = "Example - 3D Sanity Cube"; if (demo.Construct(config)) { From a625d0b54252b0c8931638b00a2a181fa19465c4 Mon Sep 17 00:00:00 2001 From: Moros Smith Date: Sun, 1 Mar 2026 15:12:42 -0500 Subject: [PATCH 096/101] update single header --- olcPixelGameEngine3.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 5bc0f1ad..c122f35b 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -4002,6 +4002,9 @@ namespace olc protected: // PGE Configuration PGEConfig config; + + // Application Name + std::string sAppName = ""; }; // The olc::PixelGameEngine3 core, manages the main window, child windows, engine loop, timing and devices @@ -17313,7 +17316,7 @@ namespace olc if (durationFrameCount >= 1s) { durationFrameCount -= 1s; - std::string sTitle = "OneLoneCoder.com - Pixel Game Engine 3 - Test - FPS: " + std::to_string(frameCount); + std::string sTitle = "OneLoneCoder.com - Pixel Game Engine 3 - " + sAppName + " - FPS: " + std::to_string(frameCount); SetWindowTitle(sTitle); fps = frameCount; frameCount = 0; From 24f37985b3ad6d019bdf1c092b88f6af13502f66 Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Mon, 2 Mar 2026 21:48:44 +0000 Subject: [PATCH 097/101] i hate these things - hopefully this is done forever --- .../olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj | 14 +- .../olcPGE3_BuildSH.vcxproj.filters | 3 + dev/src/matrix3d.h | 75 ++++- dev/src/transform2d.h | 169 +++++------ examples/olcPGE3_ImageTiling.cpp | 67 +++-- examples/olcPGE3_LocalTransforms.cpp | 138 +++++++++ examples/olcPGE3_WorldTransform.cpp | 2 +- olcPixelGameEngine3.h | 274 ++++++++++++------ utilities/olcUTIL3_Camera2D.h | 51 ++-- 9 files changed, 565 insertions(+), 228 deletions(-) create mode 100644 examples/olcPGE3_LocalTransforms.cpp diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj index fb620449..47b250e2 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj @@ -50,10 +50,10 @@ true - false - false - false - false + true + true + true + true true @@ -79,6 +79,12 @@ true true + + false + false + false + false + true true diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters index c6f04276..6ebcfc8f 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters @@ -90,6 +90,9 @@ Source Files + + Source Files + diff --git a/dev/src/matrix3d.h b/dev/src/matrix3d.h index e6a123a9..aac67835 100644 --- a/dev/src/matrix3d.h +++ b/dev/src/matrix3d.h @@ -82,6 +82,14 @@ namespace olc me(2, 2) = 1; } + // Create instant identity matrix + static inline constexpr auto identity_matrix() + { + m_3d out; + out.identity(); + return out; + } + inline constexpr std::array m4x4() { auto& me = (*this); @@ -110,6 +118,15 @@ namespace olc translate(v.x, v.y); } + // Create instant translation matrix + template + static inline constexpr auto translation(const olc::v_2d& v) + { + m_3d out; + out.translate(v.x, v.y); + return out; + } + // Create scaling matrix via components template inline constexpr void scale(const Q x, const Q y) @@ -127,6 +144,15 @@ namespace olc scale(v.x, v.y); } + // Create instant scaling matrix + template + static inline constexpr auto scaling(const olc::v_2d& v) + { + m_3d out; + out.scale(v.x, v.y); + return out; + } + // Create rotation matrix with radians template inline constexpr void rotate(const Q rads) @@ -139,6 +165,15 @@ namespace olc me(1, 1) = me(0, 0); } + // Create instant rotation matrix with radians + template + static inline constexpr auto rotation(const Q rads) + { + m_3d out; + out.rotate(rads); + return out; + } + // Create shearing matrix via components template inline constexpr void shear(const Q x, const Q y) @@ -156,6 +191,40 @@ namespace olc shear(v.x, v.y); } + // Create instant shearing matrix + template + static inline constexpr auto shearing(const olc::v_2d& v) + { + m_3d out; + out.shear(v.x, v.y); + return out; + } + + + // Return inverted matrix + //inline constexpr auto invert() const + //{ + // // https://stackoverflow.com/a/18504573 + // olc::m_3d out; + // auto& me = (*this); + + // T det = me(0, 0) * (me(1, 1) * me(2, 2) - me(2, 1) * me(1, 2)) - + // me(0, 1) * (me(1, 0) * me(2, 2) - me(1, 2) * me(2, 0)) + + // me(0, 2) * (me(1, 0) * me(2, 1) - me(1, 1) * me(2, 0)); + + // T invdet = T(1) / det; + + // out(0, 0) = (me(1, 1) * me(2, 2) - me(2, 1) * me(1, 2)) * invdet; + // out(0, 1) = (me(0, 2) * me(2, 1) - me(0, 1) * me(2, 2)) * invdet; + // out(0, 2) = (me(0, 1) * me(1, 2) - me(0, 2) * me(1, 1)) * invdet; + // out(1, 0) = (me(1, 2) * me(2, 0) - me(1, 0) * me(2, 2)) * invdet; + // out(1, 1) = (me(0, 0) * me(2, 2) - me(0, 2) * me(2, 0)) * invdet; + // out(1, 2) = (me(1, 0) * me(0, 2) - me(0, 0) * me(1, 2)) * invdet; + // out(2, 0) = (me(1, 0) * me(2, 1) - me(2, 0) * me(1, 1)) * invdet; + // out(2, 1) = (me(2, 0) * me(0, 1) - me(0, 0) * me(2, 1)) * invdet; + // out(2, 2) = (me(0, 0) * me(1, 1) - me(1, 0) * me(0, 1)) * invdet; + // return out; + //} // Return inverted matrix inline constexpr auto invert() const @@ -165,8 +234,8 @@ namespace olc auto& me = (*this); T det = me(0, 0) * (me(1, 1) * me(2, 2) - me(2, 1) * me(1, 2)) - - me(0, 1) * (me(1, 0) * me(2, 2) - me(1, 2) * me(2, 0)) + - me(0, 2) * (me(1, 0) * me(2, 1) - me(1, 1) * me(2, 0)); + me(0, 1) * (me(1, 0) * me(2, 2) - me(1, 2) * me(2, 0)) + + me(0, 2) * (me(1, 0) * me(2, 1) - me(1, 1) * me(2, 0)); T invdet = T(1) / det; @@ -202,7 +271,7 @@ namespace olc olc::m_3d out; for (size_t c = 0; c < 3; c++) for (size_t r = 0; r < 3; r++) - out(r, c) = me(r, 0) * rhs(0, c) + me(r, 1) * rhs(1, c) + me(r, 2) * rhs(2, c); + out(c, r) = me(0, r) * rhs(c, 0) + me(1, r) * rhs(c, 1) + me(2, r) * rhs(c, 2); return out; } diff --git a/dev/src/transform2d.h b/dev/src/transform2d.h index 3b350f81..c4cec130 100644 --- a/dev/src/transform2d.h +++ b/dev/src/transform2d.h @@ -7,6 +7,7 @@ #include #include #include +#include //! END STDHEADER //! START CUSTOMHEADER @@ -40,7 +41,10 @@ namespace olc public: // Constructor - inline constexpr t_2d() = default; + inline constexpr t_2d() + { + identity(); + } // Copy constructor inline constexpr t_2d(const t_2d& t) = default; @@ -48,17 +52,27 @@ namespace olc // Assignment operator inline constexpr t_2d& operator=(const t_2d& t) = default; + inline constexpr void identity() + { + // Clear stacks and reset to identity + m_stackForward = std::stack>(); + m_stackInverse = std::stack>(); + + m_stackForward.push(olc::m_3d::identity_matrix()); + m_stackInverse.push(olc::m_3d::identity_matrix()); + } + // Transform a vector by this transform template inline constexpr auto forward(const olc::v_2d& v) const { - return m_mForward * v; + return m_stackForward.top() * v; } template inline constexpr auto forwardRound(const olc::v_2d& v) const { - return (m_mForward * v).round(); + return (m_stackForward.top() * v).round(); } // Transform a vector of v_2d by this transform @@ -66,7 +80,7 @@ namespace olc inline constexpr auto forward(const std::vector>& v) const { std::vector> o(v.size()); - std::transform(v.begin(), v.end(), o.begin(), [this](const olc::v_2d& i) {return m_mForward * i; }); + std::transform(v.begin(), v.end(), o.begin(), [this](const olc::v_2d& i) {return m_stackForward.top() * i; }); return o; } @@ -74,7 +88,7 @@ namespace olc inline constexpr auto forwardRound(const std::vector>& v) const { std::vector> o(v.size()); - std::transform(v.begin(), v.end(), o.begin(), [this](const olc::v_2d& i) {return (m_mForward * i).round(); }); + std::transform(v.begin(), v.end(), o.begin(), [this](const olc::v_2d& i) {return (m_stackForward.top() * i).round(); }); return o; } @@ -82,14 +96,14 @@ namespace olc template inline constexpr auto forwardX(std::vector>&& v) const { - std::transform(v.begin(), v.end(), v.begin(), [this](const olc::v_2d& i) {return (m_mForward * i); }); + std::transform(v.begin(), v.end(), v.begin(), [this](const olc::v_2d& i) {return (m_stackForward.top() * i); }); return v; } template inline constexpr auto forwardRoundX(std::vector>&& v) const { - std::transform(v.begin(), v.end(), v.begin(), [this](const olc::v_2d& i) {return (m_mForward * i).round(); }); + std::transform(v.begin(), v.end(), v.begin(), [this](const olc::v_2d& i) {return (m_stackForward.top() * i).round(); }); return v; } @@ -97,7 +111,7 @@ namespace olc template inline constexpr auto inverse(const olc::v_2d& v) const { - return m_mInverse * v; + return m_stackInverse.top() * v; } // Transform a vector by this transform @@ -105,7 +119,7 @@ namespace olc inline constexpr auto inverse(const std::vector>& v) const { std::vector> o(v.size()); - std::transform(v.begin(), v.end(), o.begin(), [this](const olc::v_2d& i) {return m_mInverse * i; }); + std::transform(v.begin(), v.end(), o.begin(), [this](const olc::v_2d& i) {return m_stackInverse.top() * i; }); return o; } @@ -113,7 +127,7 @@ namespace olc template inline constexpr auto inverseX(std::vector>&& v) const { - std::transform(v.begin(), v.end(), v.begin(), [this](const olc::v_2d& i) {return m_mInverse * i; }); + std::transform(v.begin(), v.end(), v.begin(), [this](const olc::v_2d& i) {return m_stackInverse.top() * i; }); return v; } @@ -122,135 +136,126 @@ namespace olc template inline constexpr void scale(const olc::v_2d& v) { - m_vScale = v; - m_mScale.scale(m_vScale); - update(); + m_stackForward.push(m_stackForward.top() * olc::m_3d::scaling(v)); + m_stackInverse.push(m_stackForward.top().invert()); } // Get scaling component of this transformation - inline constexpr const auto& scale() const + inline constexpr const auto scale() const { - return m_vScale; + const auto& mat = m_stackForward.top(); + return olc::v_2d + { + T(olc::internal::sgn(mat(0, 0)) * std::hypot(mat(0, 0), mat(1, 0))), + T(olc::internal::sgn(mat(1, 1)) * std::hypot(mat(0, 1), mat(1, 1))) + }; } // Set translation component of this transformation template inline constexpr void translate(const olc::v_2d& v) { - m_vTranslate = v; - m_mTranslate.translate(m_vTranslate); - update(); + m_stackForward.push(m_stackForward.top() * olc::m_3d::translation(v)); + m_stackInverse.push(m_stackForward.top().invert()); } // Get translation component of this transformation - inline constexpr const auto& translate() const + inline constexpr const auto translation() const { - return m_vTranslate; + const auto& mat = m_stackForward.top(); + return olc::v_2d + { + T(mat(2, 0)), + T(mat(2, 1)) + }; } // Set translation component of this transformation template inline constexpr void rotate(const Q& v, const olc::v_2d& p = { 0,0 }) { - m_dTheta = T(v); - m_vRotatePoint = p; - m_3d matTrans1; - matTrans1.translate(-m_vRotatePoint); - m_3d matTrans2; - matTrans2.translate(m_vRotatePoint); - m_3d matRotate; - matRotate.rotate(m_dTheta); - m_mRotate = matTrans1 * matRotate * matTrans2; - update(); + m_stackForward.push(m_stackForward.top() * (olc::m_3d::translation(-p) * olc::m_3d::rotation(v) * olc::m_3d::translation(p))); + m_stackInverse.push(m_stackForward.top().invert()); } // Get translation component of this transformation - inline constexpr const auto& rotate() const + inline constexpr const auto rotation() const { - return m_dTheta; + const auto& mat = m_stackForward.top(); + return T(std::atan2(mat(0, 1), mat(1, 1))); } // Set shear component of this transformation template inline constexpr void shear(const olc::v_2d& v) { - m_vShear = v; - m_mShear.shear(m_vShear); - update(); - } - - // Get shear component of this transformation - inline constexpr const auto& shear() const - { - return m_vShear; + m_stackForward.push(m_stackForward.top() * olc::m_3d::shearing(v)); + m_stackInverse.push(m_stackForward.top().invert()); } // Get forward transformation matrix inline constexpr const auto& forward_matrix() const { - return m_mForward; + return m_stackForward.top(); } // Get inverse transformation matrix inline constexpr const auto& inverse_matrix() const { - return m_mInverse; + return m_stackInverse.top(); } - // Construct transform from an existing transformation matrix template - inline constexpr void from_matrix(const olc::m_3d& mat) + inline constexpr auto operator * (const olc::t_2d& rhs) const { - // https://math.stackexchange.com/a/13165 - m_vTranslate = - { - T(mat(2, 0)), - T(mat(2, 1)) - }; - - m_vScale = - { - T(olc::internal::sgn(mat(0, 0)) * std::hypot(mat(0, 0), mat(1, 0))), - T(olc::internal::sgn(mat(1, 1)) * std::hypot(mat(0, 1), mat(1, 1))) - }; + auto& me = *this; + return me.forward_matrix() * rhs.forward_matrix(); + } - m_dTheta = T(std::atan2(mat(0, 1), mat(1, 1))); + // Push a new transform onto the stack, multiplying it with the + // current transform (arbitrary affine matrix) + template + inline constexpr auto push(const olc::m_3d& m) + { + m_stackForward.push(m_stackForward.top() * m); + m_stackInverse.push(m_stackForward.top().invert()); - m_mTranslate.translate(m_vTranslate); - m_mScale.scale(m_vScale); - m_mRotate.rotate(m_dTheta); - update(); } - template - inline constexpr auto operator * (const olc::t_2d& rhs) const + // Pop the last transform off the stack, reverting to the + // previous transform + inline constexpr auto pop() { - auto& me = *this; - return me.forward_matrix() * rhs.forward_matrix(); + if (!m_stackForward.empty()) + { + m_stackForward.pop(); + m_stackInverse.pop(); + + if (m_stackForward.empty()) + identity(); + } } - protected: - // Constructs resultant matrices when transformation changes - inline constexpr void update() + // Condense the transform stack into a single matrix, leaving + // the current transformation intact but removing all history + inline constexpr void squash() { - m_mForward = m_mScale * m_mRotate * m_mShear * m_mTranslate; - m_mInverse = m_mForward.invert(); + if (m_stackForward.size() > 1) + { + auto top = m_stackForward.top(); + m_stackForward = std::stack>(); + m_stackInverse = std::stack>(); + m_stackForward.push(top); + m_stackInverse.push(top.invert()); + } } protected: - T m_dTheta = 0; - olc::v_2d m_vScale = { 1,1 }; - olc::v_2d m_vTranslate = { 0,0 }; - olc::v_2d m_vRotatePoint = { 0,0 }; - olc::v_2d m_vShear = { 0,0 }; - olc::m_3d m_mForward; - olc::m_3d m_mInverse; - olc::m_3d m_mScale; - olc::m_3d m_mTranslate; - olc::m_3d m_mRotate; - olc::m_3d m_mShear; + std::stack> m_stackForward; + std::stack> m_stackInverse; }; + typedef t_2d tf2d; typedef t_2d td2d; } diff --git a/examples/olcPGE3_ImageTiling.cpp b/examples/olcPGE3_ImageTiling.cpp index f42840df..3985d9e4 100644 --- a/examples/olcPGE3_ImageTiling.cpp +++ b/examples/olcPGE3_ImageTiling.cpp @@ -49,6 +49,18 @@ class Example_ImageTiling : public olc::PixelGameEngine bool OnUserCreate() override { + olc::tf2d t1; + t1.translate(olc::vf2d{ 10,5 }); + t1.scale(olc::vf2d{ 2,2 }); + t1.translate(olc::vf2d{ 10,5 }); + t1.scale(olc::vf2d{ 4.0f, 4.0f }); + t1.translate(olc::vf2d{ 10,5 }); + olc::vf2d v = t1.translation(); + olc::vf2d s = t1.scale(); + + auto i1 = t1.forward_matrix().invert(); + auto i2 = i1.invert(); + CreateImageFromFile(imTiles, "./assets/tile_atlas_4x4_16x16.png"); return true; @@ -191,10 +203,10 @@ class Example_ImageTiling : public olc::PixelGameEngine bool bPanning = false; olc::vf2d vLastMouseScreenPos = { 0.0f, 0.0f }; + // The "Affine Transform" representing the world olc::tf2d transform; - // Function to handle panning and zooming void HandlePanAndZoom() { @@ -219,54 +231,57 @@ class Example_ImageTiling : public olc::PixelGameEngine 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()); - } + // Squash current transform to a single matrix. This is required + // so we dont contnuously add to the transform stack + transform.squash(); + // Get current mouse position + olc::vf2d vMousePos = mouse.GetPosition(); + olc::vf2d vMouseWorldPos = transform.inverse(vMousePos); + olc::vf2d vLastMouseWorldPos = transform.inverse(vLastMouseScreenPos); + // NOTE!!! Scale & Rotate BEFORE translation + // 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); + auto posWorldBefore = vMouseWorldPos; // 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); + float fRotateDelta = (mouse.GetWheel() > 0) ? 0.1f : -0.1f; + transform.rotate(fRotateDelta, posWorldBefore); } else { - // Adjust scale depending on wheel direction - if (mouse.GetWheel() > 0) - transform.scale(transform.scale() * 1.1f); - else - transform.scale(transform.scale() * 0.9f); + olc::vf2d vScaleDelta = olc::vf2d{ 1.0f, 1.0f } * ((mouse.GetWheel() > 0) ? 1.1f : 0.9f); + transform.scale(vScaleDelta); } // Get the new screen position of the point under the mouse - auto posScreenAfterRotate = transform.forward(posWorldBeforeRotate); + auto posWorldAfter = transform.inverse(mouse.GetPosition()); // Adjust translation to keep mouse position stable - auto posScreenDisplacement = vLastMouseScreenPos - posScreenAfterRotate; + auto posScreenDisplacement = posWorldBefore - posWorldAfter; // Apply adjustment - transform.translate(transform.translate() + posScreenDisplacement); + transform.translate(-posScreenDisplacement); + } + + + // If we are panning, update translation component of transform + if (bPanning) + { + // Update translation by the mouse delta + transform.translate(vMouseWorldPos - vLastMouseWorldPos); } + + // Finally, apply the updated transform to the PGE draw system draw.SetWorldTransform(transform); diff --git a/examples/olcPGE3_LocalTransforms.cpp b/examples/olcPGE3_LocalTransforms.cpp new file mode 100644 index 00000000..c186eef3 --- /dev/null +++ b/examples/olcPGE3_LocalTransforms.cpp @@ -0,0 +1,138 @@ +/* + olc::PixelGameEngine3 Example - Local Transforms + + Draws a tree with local transforms, demonstrating how the + transform can be used to create hierarchical relationships + between objects + + 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 transformed drawing. This class +// overrides the olc::PixelGameEngine base class by implementing +// the OnUserCreate() and OnUserUpdate() functions +class Example_LocalTransforms : public olc::PixelGameEngine +{ +public: + Example_LocalTransforms() + { + } + +protected: + bool bFirstFrame = true; + +public: + // Called once at the start, so create things here + bool OnUserCreate() override + { + // Nothing to do here, so return true + return true; + } + + // Called every frame, so update things here + bool OnUserUpdate(float fElapsedTime) override + { + if (!bFirstFrame && !keyboard.GetKey(olc::Key::SPACE).bPressed) + return true; + + // Only generate the tree on the first frame, or when space is pressed + bFirstFrame = false; + + // Clear screen to sky + draw.FilledRect({ 0,0 }, ScreenSize(), + olc::Colour::CYAN, olc::Colour::CYAN, + olc::Colour::DARK_CYAN, olc::Colour::DARK_CYAN); + + auto RandomFloat = []() -> float + { + return float(rand()) / float(RAND_MAX); + }; + + auto RandomFloatRange = [&](float fMin, float fMax) -> float + { + return fMin + RandomFloat() * (fMax - fMin); + }; + + auto RandomIntRange = [&](int iMin, int iMax) -> int + { + return iMin + rand() % (iMax - iMin + 1); + }; + + auto RandomColour = [&]() ->olc::Pixel + { + return olc::PixelF(RandomFloat(), RandomFloat(), RandomFloat()); + }; + + // Root Transform + olc::tf2d transform; + transform.translate(ScreenSize() / 2.0f + olc::vf2d(0.0f, 40.0f)); + + std::function DrawBranch = [&](int depth) -> void + { + if (depth <= 0) + { + // Draw a leaf - note its always the same circle! + draw.FilledCircle({ 0, -40 }, 40, olc::Pixel(0, 180, 0), olc::Pixel(0, 150, 0), olc::Colour::WHITE); + return; + } + + // Set the lopcal transform + draw.SetWorldTransform(transform); + + // Draw the branch - note its always teh same rectangle! + draw.FilledRect({ -2, -40 }, { 4, 40 }, olc::Pixel(150,75,0)); + + // Randomly generate some child branches + int numBranches = RandomIntRange(1, 5); + for (int b = 0; b < numBranches; b++) + { + // Move the transform to the end of the branch, so that child branches will be positioned correctly + transform.translate(olc::vf2d{ 0.0f, -40.0f }); + transform.scale(olc::vf2d{ 0.75, RandomFloatRange(0.5f, 0.75f)}); + transform.rotate(RandomFloatRange(-0.5f, 0.5f)); + + // Recursively draw child branches + DrawBranch(depth - 1); + + // Pop the transform to return to the previous state for the next branch + transform.pop(); + transform.pop(); + transform.pop(); + } + + }; + + // Go! + DrawBranch(5); + + draw.WorldReset(); + draw.StringProp({ 10, 10 }, "Press SPACE to regenerate tree", olc::Colour::BLACK); + + // Successful frame + return true; + } +}; + + +// Main entry point for the application +int main() +{ + // Construct demo application + Example_LocalTransforms 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/examples/olcPGE3_WorldTransform.cpp b/examples/olcPGE3_WorldTransform.cpp index 375ff547..0d78c74e 100644 --- a/examples/olcPGE3_WorldTransform.cpp +++ b/examples/olcPGE3_WorldTransform.cpp @@ -124,9 +124,9 @@ class Example_WorldTransform: public olc::PixelGameEngine DrawScene(olc::Colour::WHITE); // Draw in yellow the transformed world - draw.WorldOffset(vWorldOffset); draw.WorldScale(vWorldScale); draw.WorldRotate(fWorldRotation); + draw.WorldOffset(vWorldOffset); DrawScene(olc::Colour::YELLOW); // Get Mouse in "transformed world" space diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index fc34ff50..4faf7a45 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -118,6 +118,7 @@ #include #include #include +#include #include #include #include @@ -1349,6 +1350,14 @@ namespace olc me(2, 2) = 1; } + // Create instant identity matrix + static inline constexpr auto identity_matrix() + { + m_3d out; + out.identity(); + return out; + } + inline constexpr std::array m4x4() { auto& me = (*this); @@ -1377,6 +1386,15 @@ namespace olc translate(v.x, v.y); } + // Create instant translation matrix + template + static inline constexpr auto translation(const olc::v_2d& v) + { + m_3d out; + out.translate(v.x, v.y); + return out; + } + // Create scaling matrix via components template inline constexpr void scale(const Q x, const Q y) @@ -1394,6 +1412,15 @@ namespace olc scale(v.x, v.y); } + // Create instant scaling matrix + template + static inline constexpr auto scaling(const olc::v_2d& v) + { + m_3d out; + out.scale(v.x, v.y); + return out; + } + // Create rotation matrix with radians template inline constexpr void rotate(const Q rads) @@ -1406,6 +1433,15 @@ namespace olc me(1, 1) = me(0, 0); } + // Create instant rotation matrix with radians + template + static inline constexpr auto rotation(const Q rads) + { + m_3d out; + out.rotate(rads); + return out; + } + // Create shearing matrix via components template inline constexpr void shear(const Q x, const Q y) @@ -1423,6 +1459,40 @@ namespace olc shear(v.x, v.y); } + // Create instant shearing matrix + template + static inline constexpr auto shearing(const olc::v_2d& v) + { + m_3d out; + out.shear(v.x, v.y); + return out; + } + + + // Return inverted matrix + //inline constexpr auto invert() const + //{ + // // https://stackoverflow.com/a/18504573 + // olc::m_3d out; + // auto& me = (*this); + + // T det = me(0, 0) * (me(1, 1) * me(2, 2) - me(2, 1) * me(1, 2)) - + // me(0, 1) * (me(1, 0) * me(2, 2) - me(1, 2) * me(2, 0)) + + // me(0, 2) * (me(1, 0) * me(2, 1) - me(1, 1) * me(2, 0)); + + // T invdet = T(1) / det; + + // out(0, 0) = (me(1, 1) * me(2, 2) - me(2, 1) * me(1, 2)) * invdet; + // out(0, 1) = (me(0, 2) * me(2, 1) - me(0, 1) * me(2, 2)) * invdet; + // out(0, 2) = (me(0, 1) * me(1, 2) - me(0, 2) * me(1, 1)) * invdet; + // out(1, 0) = (me(1, 2) * me(2, 0) - me(1, 0) * me(2, 2)) * invdet; + // out(1, 1) = (me(0, 0) * me(2, 2) - me(0, 2) * me(2, 0)) * invdet; + // out(1, 2) = (me(1, 0) * me(0, 2) - me(0, 0) * me(1, 2)) * invdet; + // out(2, 0) = (me(1, 0) * me(2, 1) - me(2, 0) * me(1, 1)) * invdet; + // out(2, 1) = (me(2, 0) * me(0, 1) - me(0, 0) * me(2, 1)) * invdet; + // out(2, 2) = (me(0, 0) * me(1, 1) - me(1, 0) * me(0, 1)) * invdet; + // return out; + //} // Return inverted matrix inline constexpr auto invert() const @@ -1432,8 +1502,8 @@ namespace olc auto& me = (*this); T det = me(0, 0) * (me(1, 1) * me(2, 2) - me(2, 1) * me(1, 2)) - - me(0, 1) * (me(1, 0) * me(2, 2) - me(1, 2) * me(2, 0)) + - me(0, 2) * (me(1, 0) * me(2, 1) - me(1, 1) * me(2, 0)); + me(0, 1) * (me(1, 0) * me(2, 2) - me(1, 2) * me(2, 0)) + + me(0, 2) * (me(1, 0) * me(2, 1) - me(1, 1) * me(2, 0)); T invdet = T(1) / det; @@ -1469,7 +1539,7 @@ namespace olc olc::m_3d out; for (size_t c = 0; c < 3; c++) for (size_t r = 0; r < 3; r++) - out(r, c) = me(r, 0) * rhs(0, c) + me(r, 1) * rhs(1, c) + me(r, 2) * rhs(2, c); + out(c, r) = me(0, r) * rhs(c, 0) + me(1, r) * rhs(c, 1) + me(2, r) * rhs(c, 2); return out; } @@ -1886,7 +1956,10 @@ namespace olc public: // Constructor - inline constexpr t_2d() = default; + inline constexpr t_2d() + { + identity(); + } // Copy constructor inline constexpr t_2d(const t_2d& t) = default; @@ -1894,17 +1967,27 @@ namespace olc // Assignment operator inline constexpr t_2d& operator=(const t_2d& t) = default; + inline constexpr void identity() + { + // Clear stacks and reset to identity + m_stackForward = std::stack>(); + m_stackInverse = std::stack>(); + + m_stackForward.push(olc::m_3d::identity_matrix()); + m_stackInverse.push(olc::m_3d::identity_matrix()); + } + // Transform a vector by this transform template inline constexpr auto forward(const olc::v_2d& v) const { - return m_mForward * v; + return m_stackForward.top() * v; } template inline constexpr auto forwardRound(const olc::v_2d& v) const { - return (m_mForward * v).round(); + return (m_stackForward.top() * v).round(); } // Transform a vector of v_2d by this transform @@ -1912,7 +1995,7 @@ namespace olc inline constexpr auto forward(const std::vector>& v) const { std::vector> o(v.size()); - std::transform(v.begin(), v.end(), o.begin(), [this](const olc::v_2d& i) {return m_mForward * i; }); + std::transform(v.begin(), v.end(), o.begin(), [this](const olc::v_2d& i) {return m_stackForward.top() * i; }); return o; } @@ -1920,7 +2003,7 @@ namespace olc inline constexpr auto forwardRound(const std::vector>& v) const { std::vector> o(v.size()); - std::transform(v.begin(), v.end(), o.begin(), [this](const olc::v_2d& i) {return (m_mForward * i).round(); }); + std::transform(v.begin(), v.end(), o.begin(), [this](const olc::v_2d& i) {return (m_stackForward.top() * i).round(); }); return o; } @@ -1928,14 +2011,14 @@ namespace olc template inline constexpr auto forwardX(std::vector>&& v) const { - std::transform(v.begin(), v.end(), v.begin(), [this](const olc::v_2d& i) {return (m_mForward * i); }); + std::transform(v.begin(), v.end(), v.begin(), [this](const olc::v_2d& i) {return (m_stackForward.top() * i); }); return v; } template inline constexpr auto forwardRoundX(std::vector>&& v) const { - std::transform(v.begin(), v.end(), v.begin(), [this](const olc::v_2d& i) {return (m_mForward * i).round(); }); + std::transform(v.begin(), v.end(), v.begin(), [this](const olc::v_2d& i) {return (m_stackForward.top() * i).round(); }); return v; } @@ -1943,7 +2026,7 @@ namespace olc template inline constexpr auto inverse(const olc::v_2d& v) const { - return m_mInverse * v; + return m_stackInverse.top() * v; } // Transform a vector by this transform @@ -1951,7 +2034,7 @@ namespace olc inline constexpr auto inverse(const std::vector>& v) const { std::vector> o(v.size()); - std::transform(v.begin(), v.end(), o.begin(), [this](const olc::v_2d& i) {return m_mInverse * i; }); + std::transform(v.begin(), v.end(), o.begin(), [this](const olc::v_2d& i) {return m_stackInverse.top() * i; }); return o; } @@ -1959,7 +2042,7 @@ namespace olc template inline constexpr auto inverseX(std::vector>&& v) const { - std::transform(v.begin(), v.end(), v.begin(), [this](const olc::v_2d& i) {return m_mInverse * i; }); + std::transform(v.begin(), v.end(), v.begin(), [this](const olc::v_2d& i) {return m_stackInverse.top() * i; }); return v; } @@ -1968,135 +2051,126 @@ namespace olc template inline constexpr void scale(const olc::v_2d& v) { - m_vScale = v; - m_mScale.scale(m_vScale); - update(); + m_stackForward.push(m_stackForward.top() * olc::m_3d::scaling(v)); + m_stackInverse.push(m_stackForward.top().invert()); } // Get scaling component of this transformation - inline constexpr const auto& scale() const + inline constexpr const auto scale() const { - return m_vScale; + const auto& mat = m_stackForward.top(); + return olc::v_2d + { + T(olc::internal::sgn(mat(0, 0)) * std::hypot(mat(0, 0), mat(1, 0))), + T(olc::internal::sgn(mat(1, 1)) * std::hypot(mat(0, 1), mat(1, 1))) + }; } // Set translation component of this transformation template inline constexpr void translate(const olc::v_2d& v) { - m_vTranslate = v; - m_mTranslate.translate(m_vTranslate); - update(); + m_stackForward.push(m_stackForward.top() * olc::m_3d::translation(v)); + m_stackInverse.push(m_stackForward.top().invert()); } // Get translation component of this transformation - inline constexpr const auto& translate() const + inline constexpr const auto translation() const { - return m_vTranslate; + const auto& mat = m_stackForward.top(); + return olc::v_2d + { + T(mat(2, 0)), + T(mat(2, 1)) + }; } // Set translation component of this transformation template inline constexpr void rotate(const Q& v, const olc::v_2d& p = { 0,0 }) { - m_dTheta = T(v); - m_vRotatePoint = p; - m_3d matTrans1; - matTrans1.translate(-m_vRotatePoint); - m_3d matTrans2; - matTrans2.translate(m_vRotatePoint); - m_3d matRotate; - matRotate.rotate(m_dTheta); - m_mRotate = matTrans1 * matRotate * matTrans2; - update(); + m_stackForward.push(m_stackForward.top() * (olc::m_3d::translation(-p) * olc::m_3d::rotation(v) * olc::m_3d::translation(p))); + m_stackInverse.push(m_stackForward.top().invert()); } // Get translation component of this transformation - inline constexpr const auto& rotate() const + inline constexpr const auto rotation() const { - return m_dTheta; + const auto& mat = m_stackForward.top(); + return T(std::atan2(mat(0, 1), mat(1, 1))); } // Set shear component of this transformation template inline constexpr void shear(const olc::v_2d& v) { - m_vShear = v; - m_mShear.shear(m_vShear); - update(); - } - - // Get shear component of this transformation - inline constexpr const auto& shear() const - { - return m_vShear; + m_stackForward.push(m_stackForward.top() * olc::m_3d::shearing(v)); + m_stackInverse.push(m_stackForward.top().invert()); } // Get forward transformation matrix inline constexpr const auto& forward_matrix() const { - return m_mForward; + return m_stackForward.top(); } // Get inverse transformation matrix inline constexpr const auto& inverse_matrix() const { - return m_mInverse; + return m_stackInverse.top(); } - // Construct transform from an existing transformation matrix template - inline constexpr void from_matrix(const olc::m_3d& mat) + inline constexpr auto operator * (const olc::t_2d& rhs) const { - // https://math.stackexchange.com/a/13165 - m_vTranslate = - { - T(mat(2, 0)), - T(mat(2, 1)) - }; - - m_vScale = - { - T(olc::internal::sgn(mat(0, 0)) * std::hypot(mat(0, 0), mat(1, 0))), - T(olc::internal::sgn(mat(1, 1)) * std::hypot(mat(0, 1), mat(1, 1))) - }; + auto& me = *this; + return me.forward_matrix() * rhs.forward_matrix(); + } - m_dTheta = T(std::atan2(mat(0, 1), mat(1, 1))); + // Push a new transform onto the stack, multiplying it with the + // current transform (arbitrary affine matrix) + template + inline constexpr auto push(const olc::m_3d& m) + { + m_stackForward.push(m_stackForward.top() * m); + m_stackInverse.push(m_stackForward.top().invert()); - m_mTranslate.translate(m_vTranslate); - m_mScale.scale(m_vScale); - m_mRotate.rotate(m_dTheta); - update(); } - template - inline constexpr auto operator * (const olc::t_2d& rhs) const + // Pop the last transform off the stack, reverting to the + // previous transform + inline constexpr auto pop() { - auto& me = *this; - return me.forward_matrix() * rhs.forward_matrix(); + if (!m_stackForward.empty()) + { + m_stackForward.pop(); + m_stackInverse.pop(); + + if (m_stackForward.empty()) + identity(); + } } - protected: - // Constructs resultant matrices when transformation changes - inline constexpr void update() + // Condense the transform stack into a single matrix, leaving + // the current transformation intact but removing all history + inline constexpr void squash() { - m_mForward = m_mScale * m_mRotate * m_mShear * m_mTranslate; - m_mInverse = m_mForward.invert(); + if (m_stackForward.size() > 1) + { + auto top = m_stackForward.top(); + m_stackForward = std::stack>(); + m_stackInverse = std::stack>(); + m_stackForward.push(top); + m_stackInverse.push(top.invert()); + } } protected: - T m_dTheta = 0; - olc::v_2d m_vScale = { 1,1 }; - olc::v_2d m_vTranslate = { 0,0 }; - olc::v_2d m_vRotatePoint = { 0,0 }; - olc::v_2d m_vShear = { 0,0 }; - olc::m_3d m_mForward; - olc::m_3d m_mInverse; - olc::m_3d m_mScale; - olc::m_3d m_mTranslate; - olc::m_3d m_mRotate; - olc::m_3d m_mShear; + std::stack> m_stackForward; + std::stack> m_stackInverse; }; + typedef t_2d tf2d; typedef t_2d td2d; } @@ -10490,13 +10564,14 @@ namespace olc::host } else if (xev.type == FocusIn) { - if(auto* pge_window = get_pge_window(xev.xbutton.window); pge_window) { + + if(auto* pge_window = get_pge_window(xev.xfocus.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) { + if(auto* pge_window = get_pge_window(xev.xfocus.window); pge_window) { pge_window->olc_OnMouseFocus(false); } } @@ -10684,11 +10759,31 @@ namespace olc::host bool Host_Linux_X11::SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) { - return false; + auto win = mapUID2X11Window.at(pWindow->GetUID()); + + // NOTE: xwayland will only allow warping when we have an active grab on a + // hidden mouse cursor. + + X11::XGrabPointer( + olc_Display, win, True, + ButtonPressMask | ButtonReleaseMask | PointerMotionMask, + GrabModeAsync, GrabModeAsync, + win, X11::None, CurrentTime + ); + + X11::XWarpPointer(olc_Display, X11::None, win, 0, 0, 0, 0, vPos.x, vPos.y); + X11::XFlush(olc_Display); + + X11::XUngrabPointer(olc_Display, CurrentTime); + X11::XFlush(olc_Display); + + return true; } bool Host_Linux_X11::SetMouseVisible(olc::Window* pWindow, const bool bVisible) { + // NOTE: works on X11 and Xwayland, but does not work correctly in WSL2 + // nothing to change, do nothing if(bMouseIsVisible == bVisible) return true; @@ -10700,7 +10795,7 @@ namespace olc::host if(bMouseIsVisible) { - X11::XDefineCursor(olc_Display, win, X11::None); + X11::XUndefineCursor(olc_Display, win); return true; } @@ -11054,7 +11149,6 @@ namespace olc::host void Host_Linux_Wayland::registry_handle_global(wl_registry* registry, uint32_t name, const char* interface, uint32_t version) { - std::cout << "Global registry event: " << interface << std::endl; if(std::strcmp(interface, wl_compositor_interface.name) == 0) { compositor = static_cast(wl_registry_bind(registry, name, &wl_compositor_interface, version)); } diff --git a/utilities/olcUTIL3_Camera2D.h b/utilities/olcUTIL3_Camera2D.h index 50916a85..b6839154 100644 --- a/utilities/olcUTIL3_Camera2D.h +++ b/utilities/olcUTIL3_Camera2D.h @@ -246,8 +246,9 @@ namespace olc::utils if (bUpdateTransform) { + transform.identity(); transform.scale(m_vViewScale); - transform.translate(m_vViewPos * -m_vViewScale); + transform.translate(-m_vViewPos); } @@ -286,17 +287,17 @@ namespace olc::utils bPanning = false; } - // Get current mouse position + // Squash current transform to a single matrix. This is required + // so we dont contnuously add to the transform stack + transform.squash(); + + // Get current mouse position olc::vf2d vMousePos = mouse.GetPosition(); + olc::vf2d vMouseWorldPos = transform.inverse(vMousePos); + olc::vf2d vLastMouseWorldPos = transform.inverse(vLastMouseScreenPos); + + // NOTE!!! Scale & Rotate BEFORE translation - // 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 @@ -305,36 +306,42 @@ namespace olc::utils if (mouse.GetWheel() != 0) { // Cache the mouse position before transformation - auto posWorldBeforeRotate = transform.inverse(vLastMouseScreenPos); + auto posWorldBefore = vMouseWorldPos; // 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); + //float fRotateDelta = (mouse.GetWheel() > 0) ? 0.1f : -0.1f; + //transform.rotate(fRotateDelta, 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); + olc::vf2d vScaleDelta = olc::vf2d{ 1.0f, 1.0f } * ((mouse.GetWheel() > 0) ? 1.1f : 0.9f); + transform.scale(vScaleDelta); //} // Get the new screen position of the point under the mouse - auto posScreenAfterRotate = transform.forward(posWorldBeforeRotate); + auto posWorldAfter = transform.inverse(mouse.GetPosition()); // Adjust translation to keep mouse position stable - auto posScreenDisplacement = vLastMouseScreenPos - posScreenAfterRotate; + auto posScreenDisplacement = posWorldBefore - posWorldAfter; // Apply adjustment - transform.translate(transform.translate() + posScreenDisplacement); + transform.translate(-posScreenDisplacement); } + // 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(vMouseWorldPos - vLastMouseWorldPos); + } + + + // Cache last mouse position vLastMouseScreenPos = vMousePos; } From e4781386d145a3ec0d80a21b5b7ab5f1e55c5a4a Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Mon, 2 Mar 2026 21:48:44 +0000 Subject: [PATCH 098/101] i hate these things - hopefully this is done forever --- .../olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj | 14 +- .../olcPGE3_BuildSH.vcxproj.filters | 3 + dev/src/matrix3d.h | 75 +++++- dev/src/transform2d.h | 169 ++++++------ examples/olcPGE3_ImageTiling.cpp | 67 +++-- examples/olcPGE3_LocalTransforms.cpp | 138 ++++++++++ examples/olcPGE3_WorldTransform.cpp | 2 +- olcPixelGameEngine3.h | 244 ++++++++++++------ utilities/olcUTIL3_Camera2D.h | 51 ++-- 9 files changed, 540 insertions(+), 223 deletions(-) create mode 100644 examples/olcPGE3_LocalTransforms.cpp diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj index fb620449..47b250e2 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj @@ -50,10 +50,10 @@ true - false - false - false - false + true + true + true + true true @@ -79,6 +79,12 @@ true true + + false + false + false + false + true true diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters index c6f04276..6ebcfc8f 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters @@ -90,6 +90,9 @@ Source Files + + Source Files + diff --git a/dev/src/matrix3d.h b/dev/src/matrix3d.h index e6a123a9..aac67835 100644 --- a/dev/src/matrix3d.h +++ b/dev/src/matrix3d.h @@ -82,6 +82,14 @@ namespace olc me(2, 2) = 1; } + // Create instant identity matrix + static inline constexpr auto identity_matrix() + { + m_3d out; + out.identity(); + return out; + } + inline constexpr std::array m4x4() { auto& me = (*this); @@ -110,6 +118,15 @@ namespace olc translate(v.x, v.y); } + // Create instant translation matrix + template + static inline constexpr auto translation(const olc::v_2d& v) + { + m_3d out; + out.translate(v.x, v.y); + return out; + } + // Create scaling matrix via components template inline constexpr void scale(const Q x, const Q y) @@ -127,6 +144,15 @@ namespace olc scale(v.x, v.y); } + // Create instant scaling matrix + template + static inline constexpr auto scaling(const olc::v_2d& v) + { + m_3d out; + out.scale(v.x, v.y); + return out; + } + // Create rotation matrix with radians template inline constexpr void rotate(const Q rads) @@ -139,6 +165,15 @@ namespace olc me(1, 1) = me(0, 0); } + // Create instant rotation matrix with radians + template + static inline constexpr auto rotation(const Q rads) + { + m_3d out; + out.rotate(rads); + return out; + } + // Create shearing matrix via components template inline constexpr void shear(const Q x, const Q y) @@ -156,6 +191,40 @@ namespace olc shear(v.x, v.y); } + // Create instant shearing matrix + template + static inline constexpr auto shearing(const olc::v_2d& v) + { + m_3d out; + out.shear(v.x, v.y); + return out; + } + + + // Return inverted matrix + //inline constexpr auto invert() const + //{ + // // https://stackoverflow.com/a/18504573 + // olc::m_3d out; + // auto& me = (*this); + + // T det = me(0, 0) * (me(1, 1) * me(2, 2) - me(2, 1) * me(1, 2)) - + // me(0, 1) * (me(1, 0) * me(2, 2) - me(1, 2) * me(2, 0)) + + // me(0, 2) * (me(1, 0) * me(2, 1) - me(1, 1) * me(2, 0)); + + // T invdet = T(1) / det; + + // out(0, 0) = (me(1, 1) * me(2, 2) - me(2, 1) * me(1, 2)) * invdet; + // out(0, 1) = (me(0, 2) * me(2, 1) - me(0, 1) * me(2, 2)) * invdet; + // out(0, 2) = (me(0, 1) * me(1, 2) - me(0, 2) * me(1, 1)) * invdet; + // out(1, 0) = (me(1, 2) * me(2, 0) - me(1, 0) * me(2, 2)) * invdet; + // out(1, 1) = (me(0, 0) * me(2, 2) - me(0, 2) * me(2, 0)) * invdet; + // out(1, 2) = (me(1, 0) * me(0, 2) - me(0, 0) * me(1, 2)) * invdet; + // out(2, 0) = (me(1, 0) * me(2, 1) - me(2, 0) * me(1, 1)) * invdet; + // out(2, 1) = (me(2, 0) * me(0, 1) - me(0, 0) * me(2, 1)) * invdet; + // out(2, 2) = (me(0, 0) * me(1, 1) - me(1, 0) * me(0, 1)) * invdet; + // return out; + //} // Return inverted matrix inline constexpr auto invert() const @@ -165,8 +234,8 @@ namespace olc auto& me = (*this); T det = me(0, 0) * (me(1, 1) * me(2, 2) - me(2, 1) * me(1, 2)) - - me(0, 1) * (me(1, 0) * me(2, 2) - me(1, 2) * me(2, 0)) + - me(0, 2) * (me(1, 0) * me(2, 1) - me(1, 1) * me(2, 0)); + me(0, 1) * (me(1, 0) * me(2, 2) - me(1, 2) * me(2, 0)) + + me(0, 2) * (me(1, 0) * me(2, 1) - me(1, 1) * me(2, 0)); T invdet = T(1) / det; @@ -202,7 +271,7 @@ namespace olc olc::m_3d out; for (size_t c = 0; c < 3; c++) for (size_t r = 0; r < 3; r++) - out(r, c) = me(r, 0) * rhs(0, c) + me(r, 1) * rhs(1, c) + me(r, 2) * rhs(2, c); + out(c, r) = me(0, r) * rhs(c, 0) + me(1, r) * rhs(c, 1) + me(2, r) * rhs(c, 2); return out; } diff --git a/dev/src/transform2d.h b/dev/src/transform2d.h index 3b350f81..c4cec130 100644 --- a/dev/src/transform2d.h +++ b/dev/src/transform2d.h @@ -7,6 +7,7 @@ #include #include #include +#include //! END STDHEADER //! START CUSTOMHEADER @@ -40,7 +41,10 @@ namespace olc public: // Constructor - inline constexpr t_2d() = default; + inline constexpr t_2d() + { + identity(); + } // Copy constructor inline constexpr t_2d(const t_2d& t) = default; @@ -48,17 +52,27 @@ namespace olc // Assignment operator inline constexpr t_2d& operator=(const t_2d& t) = default; + inline constexpr void identity() + { + // Clear stacks and reset to identity + m_stackForward = std::stack>(); + m_stackInverse = std::stack>(); + + m_stackForward.push(olc::m_3d::identity_matrix()); + m_stackInverse.push(olc::m_3d::identity_matrix()); + } + // Transform a vector by this transform template inline constexpr auto forward(const olc::v_2d& v) const { - return m_mForward * v; + return m_stackForward.top() * v; } template inline constexpr auto forwardRound(const olc::v_2d& v) const { - return (m_mForward * v).round(); + return (m_stackForward.top() * v).round(); } // Transform a vector of v_2d by this transform @@ -66,7 +80,7 @@ namespace olc inline constexpr auto forward(const std::vector>& v) const { std::vector> o(v.size()); - std::transform(v.begin(), v.end(), o.begin(), [this](const olc::v_2d& i) {return m_mForward * i; }); + std::transform(v.begin(), v.end(), o.begin(), [this](const olc::v_2d& i) {return m_stackForward.top() * i; }); return o; } @@ -74,7 +88,7 @@ namespace olc inline constexpr auto forwardRound(const std::vector>& v) const { std::vector> o(v.size()); - std::transform(v.begin(), v.end(), o.begin(), [this](const olc::v_2d& i) {return (m_mForward * i).round(); }); + std::transform(v.begin(), v.end(), o.begin(), [this](const olc::v_2d& i) {return (m_stackForward.top() * i).round(); }); return o; } @@ -82,14 +96,14 @@ namespace olc template inline constexpr auto forwardX(std::vector>&& v) const { - std::transform(v.begin(), v.end(), v.begin(), [this](const olc::v_2d& i) {return (m_mForward * i); }); + std::transform(v.begin(), v.end(), v.begin(), [this](const olc::v_2d& i) {return (m_stackForward.top() * i); }); return v; } template inline constexpr auto forwardRoundX(std::vector>&& v) const { - std::transform(v.begin(), v.end(), v.begin(), [this](const olc::v_2d& i) {return (m_mForward * i).round(); }); + std::transform(v.begin(), v.end(), v.begin(), [this](const olc::v_2d& i) {return (m_stackForward.top() * i).round(); }); return v; } @@ -97,7 +111,7 @@ namespace olc template inline constexpr auto inverse(const olc::v_2d& v) const { - return m_mInverse * v; + return m_stackInverse.top() * v; } // Transform a vector by this transform @@ -105,7 +119,7 @@ namespace olc inline constexpr auto inverse(const std::vector>& v) const { std::vector> o(v.size()); - std::transform(v.begin(), v.end(), o.begin(), [this](const olc::v_2d& i) {return m_mInverse * i; }); + std::transform(v.begin(), v.end(), o.begin(), [this](const olc::v_2d& i) {return m_stackInverse.top() * i; }); return o; } @@ -113,7 +127,7 @@ namespace olc template inline constexpr auto inverseX(std::vector>&& v) const { - std::transform(v.begin(), v.end(), v.begin(), [this](const olc::v_2d& i) {return m_mInverse * i; }); + std::transform(v.begin(), v.end(), v.begin(), [this](const olc::v_2d& i) {return m_stackInverse.top() * i; }); return v; } @@ -122,135 +136,126 @@ namespace olc template inline constexpr void scale(const olc::v_2d& v) { - m_vScale = v; - m_mScale.scale(m_vScale); - update(); + m_stackForward.push(m_stackForward.top() * olc::m_3d::scaling(v)); + m_stackInverse.push(m_stackForward.top().invert()); } // Get scaling component of this transformation - inline constexpr const auto& scale() const + inline constexpr const auto scale() const { - return m_vScale; + const auto& mat = m_stackForward.top(); + return olc::v_2d + { + T(olc::internal::sgn(mat(0, 0)) * std::hypot(mat(0, 0), mat(1, 0))), + T(olc::internal::sgn(mat(1, 1)) * std::hypot(mat(0, 1), mat(1, 1))) + }; } // Set translation component of this transformation template inline constexpr void translate(const olc::v_2d& v) { - m_vTranslate = v; - m_mTranslate.translate(m_vTranslate); - update(); + m_stackForward.push(m_stackForward.top() * olc::m_3d::translation(v)); + m_stackInverse.push(m_stackForward.top().invert()); } // Get translation component of this transformation - inline constexpr const auto& translate() const + inline constexpr const auto translation() const { - return m_vTranslate; + const auto& mat = m_stackForward.top(); + return olc::v_2d + { + T(mat(2, 0)), + T(mat(2, 1)) + }; } // Set translation component of this transformation template inline constexpr void rotate(const Q& v, const olc::v_2d& p = { 0,0 }) { - m_dTheta = T(v); - m_vRotatePoint = p; - m_3d matTrans1; - matTrans1.translate(-m_vRotatePoint); - m_3d matTrans2; - matTrans2.translate(m_vRotatePoint); - m_3d matRotate; - matRotate.rotate(m_dTheta); - m_mRotate = matTrans1 * matRotate * matTrans2; - update(); + m_stackForward.push(m_stackForward.top() * (olc::m_3d::translation(-p) * olc::m_3d::rotation(v) * olc::m_3d::translation(p))); + m_stackInverse.push(m_stackForward.top().invert()); } // Get translation component of this transformation - inline constexpr const auto& rotate() const + inline constexpr const auto rotation() const { - return m_dTheta; + const auto& mat = m_stackForward.top(); + return T(std::atan2(mat(0, 1), mat(1, 1))); } // Set shear component of this transformation template inline constexpr void shear(const olc::v_2d& v) { - m_vShear = v; - m_mShear.shear(m_vShear); - update(); - } - - // Get shear component of this transformation - inline constexpr const auto& shear() const - { - return m_vShear; + m_stackForward.push(m_stackForward.top() * olc::m_3d::shearing(v)); + m_stackInverse.push(m_stackForward.top().invert()); } // Get forward transformation matrix inline constexpr const auto& forward_matrix() const { - return m_mForward; + return m_stackForward.top(); } // Get inverse transformation matrix inline constexpr const auto& inverse_matrix() const { - return m_mInverse; + return m_stackInverse.top(); } - // Construct transform from an existing transformation matrix template - inline constexpr void from_matrix(const olc::m_3d& mat) + inline constexpr auto operator * (const olc::t_2d& rhs) const { - // https://math.stackexchange.com/a/13165 - m_vTranslate = - { - T(mat(2, 0)), - T(mat(2, 1)) - }; - - m_vScale = - { - T(olc::internal::sgn(mat(0, 0)) * std::hypot(mat(0, 0), mat(1, 0))), - T(olc::internal::sgn(mat(1, 1)) * std::hypot(mat(0, 1), mat(1, 1))) - }; + auto& me = *this; + return me.forward_matrix() * rhs.forward_matrix(); + } - m_dTheta = T(std::atan2(mat(0, 1), mat(1, 1))); + // Push a new transform onto the stack, multiplying it with the + // current transform (arbitrary affine matrix) + template + inline constexpr auto push(const olc::m_3d& m) + { + m_stackForward.push(m_stackForward.top() * m); + m_stackInverse.push(m_stackForward.top().invert()); - m_mTranslate.translate(m_vTranslate); - m_mScale.scale(m_vScale); - m_mRotate.rotate(m_dTheta); - update(); } - template - inline constexpr auto operator * (const olc::t_2d& rhs) const + // Pop the last transform off the stack, reverting to the + // previous transform + inline constexpr auto pop() { - auto& me = *this; - return me.forward_matrix() * rhs.forward_matrix(); + if (!m_stackForward.empty()) + { + m_stackForward.pop(); + m_stackInverse.pop(); + + if (m_stackForward.empty()) + identity(); + } } - protected: - // Constructs resultant matrices when transformation changes - inline constexpr void update() + // Condense the transform stack into a single matrix, leaving + // the current transformation intact but removing all history + inline constexpr void squash() { - m_mForward = m_mScale * m_mRotate * m_mShear * m_mTranslate; - m_mInverse = m_mForward.invert(); + if (m_stackForward.size() > 1) + { + auto top = m_stackForward.top(); + m_stackForward = std::stack>(); + m_stackInverse = std::stack>(); + m_stackForward.push(top); + m_stackInverse.push(top.invert()); + } } protected: - T m_dTheta = 0; - olc::v_2d m_vScale = { 1,1 }; - olc::v_2d m_vTranslate = { 0,0 }; - olc::v_2d m_vRotatePoint = { 0,0 }; - olc::v_2d m_vShear = { 0,0 }; - olc::m_3d m_mForward; - olc::m_3d m_mInverse; - olc::m_3d m_mScale; - olc::m_3d m_mTranslate; - olc::m_3d m_mRotate; - olc::m_3d m_mShear; + std::stack> m_stackForward; + std::stack> m_stackInverse; }; + typedef t_2d tf2d; typedef t_2d td2d; } diff --git a/examples/olcPGE3_ImageTiling.cpp b/examples/olcPGE3_ImageTiling.cpp index f42840df..3985d9e4 100644 --- a/examples/olcPGE3_ImageTiling.cpp +++ b/examples/olcPGE3_ImageTiling.cpp @@ -49,6 +49,18 @@ class Example_ImageTiling : public olc::PixelGameEngine bool OnUserCreate() override { + olc::tf2d t1; + t1.translate(olc::vf2d{ 10,5 }); + t1.scale(olc::vf2d{ 2,2 }); + t1.translate(olc::vf2d{ 10,5 }); + t1.scale(olc::vf2d{ 4.0f, 4.0f }); + t1.translate(olc::vf2d{ 10,5 }); + olc::vf2d v = t1.translation(); + olc::vf2d s = t1.scale(); + + auto i1 = t1.forward_matrix().invert(); + auto i2 = i1.invert(); + CreateImageFromFile(imTiles, "./assets/tile_atlas_4x4_16x16.png"); return true; @@ -191,10 +203,10 @@ class Example_ImageTiling : public olc::PixelGameEngine bool bPanning = false; olc::vf2d vLastMouseScreenPos = { 0.0f, 0.0f }; + // The "Affine Transform" representing the world olc::tf2d transform; - // Function to handle panning and zooming void HandlePanAndZoom() { @@ -219,54 +231,57 @@ class Example_ImageTiling : public olc::PixelGameEngine 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()); - } + // Squash current transform to a single matrix. This is required + // so we dont contnuously add to the transform stack + transform.squash(); + // Get current mouse position + olc::vf2d vMousePos = mouse.GetPosition(); + olc::vf2d vMouseWorldPos = transform.inverse(vMousePos); + olc::vf2d vLastMouseWorldPos = transform.inverse(vLastMouseScreenPos); + // NOTE!!! Scale & Rotate BEFORE translation + // 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); + auto posWorldBefore = vMouseWorldPos; // 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); + float fRotateDelta = (mouse.GetWheel() > 0) ? 0.1f : -0.1f; + transform.rotate(fRotateDelta, posWorldBefore); } else { - // Adjust scale depending on wheel direction - if (mouse.GetWheel() > 0) - transform.scale(transform.scale() * 1.1f); - else - transform.scale(transform.scale() * 0.9f); + olc::vf2d vScaleDelta = olc::vf2d{ 1.0f, 1.0f } * ((mouse.GetWheel() > 0) ? 1.1f : 0.9f); + transform.scale(vScaleDelta); } // Get the new screen position of the point under the mouse - auto posScreenAfterRotate = transform.forward(posWorldBeforeRotate); + auto posWorldAfter = transform.inverse(mouse.GetPosition()); // Adjust translation to keep mouse position stable - auto posScreenDisplacement = vLastMouseScreenPos - posScreenAfterRotate; + auto posScreenDisplacement = posWorldBefore - posWorldAfter; // Apply adjustment - transform.translate(transform.translate() + posScreenDisplacement); + transform.translate(-posScreenDisplacement); + } + + + // If we are panning, update translation component of transform + if (bPanning) + { + // Update translation by the mouse delta + transform.translate(vMouseWorldPos - vLastMouseWorldPos); } + + // Finally, apply the updated transform to the PGE draw system draw.SetWorldTransform(transform); diff --git a/examples/olcPGE3_LocalTransforms.cpp b/examples/olcPGE3_LocalTransforms.cpp new file mode 100644 index 00000000..c186eef3 --- /dev/null +++ b/examples/olcPGE3_LocalTransforms.cpp @@ -0,0 +1,138 @@ +/* + olc::PixelGameEngine3 Example - Local Transforms + + Draws a tree with local transforms, demonstrating how the + transform can be used to create hierarchical relationships + between objects + + 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 transformed drawing. This class +// overrides the olc::PixelGameEngine base class by implementing +// the OnUserCreate() and OnUserUpdate() functions +class Example_LocalTransforms : public olc::PixelGameEngine +{ +public: + Example_LocalTransforms() + { + } + +protected: + bool bFirstFrame = true; + +public: + // Called once at the start, so create things here + bool OnUserCreate() override + { + // Nothing to do here, so return true + return true; + } + + // Called every frame, so update things here + bool OnUserUpdate(float fElapsedTime) override + { + if (!bFirstFrame && !keyboard.GetKey(olc::Key::SPACE).bPressed) + return true; + + // Only generate the tree on the first frame, or when space is pressed + bFirstFrame = false; + + // Clear screen to sky + draw.FilledRect({ 0,0 }, ScreenSize(), + olc::Colour::CYAN, olc::Colour::CYAN, + olc::Colour::DARK_CYAN, olc::Colour::DARK_CYAN); + + auto RandomFloat = []() -> float + { + return float(rand()) / float(RAND_MAX); + }; + + auto RandomFloatRange = [&](float fMin, float fMax) -> float + { + return fMin + RandomFloat() * (fMax - fMin); + }; + + auto RandomIntRange = [&](int iMin, int iMax) -> int + { + return iMin + rand() % (iMax - iMin + 1); + }; + + auto RandomColour = [&]() ->olc::Pixel + { + return olc::PixelF(RandomFloat(), RandomFloat(), RandomFloat()); + }; + + // Root Transform + olc::tf2d transform; + transform.translate(ScreenSize() / 2.0f + olc::vf2d(0.0f, 40.0f)); + + std::function DrawBranch = [&](int depth) -> void + { + if (depth <= 0) + { + // Draw a leaf - note its always the same circle! + draw.FilledCircle({ 0, -40 }, 40, olc::Pixel(0, 180, 0), olc::Pixel(0, 150, 0), olc::Colour::WHITE); + return; + } + + // Set the lopcal transform + draw.SetWorldTransform(transform); + + // Draw the branch - note its always teh same rectangle! + draw.FilledRect({ -2, -40 }, { 4, 40 }, olc::Pixel(150,75,0)); + + // Randomly generate some child branches + int numBranches = RandomIntRange(1, 5); + for (int b = 0; b < numBranches; b++) + { + // Move the transform to the end of the branch, so that child branches will be positioned correctly + transform.translate(olc::vf2d{ 0.0f, -40.0f }); + transform.scale(olc::vf2d{ 0.75, RandomFloatRange(0.5f, 0.75f)}); + transform.rotate(RandomFloatRange(-0.5f, 0.5f)); + + // Recursively draw child branches + DrawBranch(depth - 1); + + // Pop the transform to return to the previous state for the next branch + transform.pop(); + transform.pop(); + transform.pop(); + } + + }; + + // Go! + DrawBranch(5); + + draw.WorldReset(); + draw.StringProp({ 10, 10 }, "Press SPACE to regenerate tree", olc::Colour::BLACK); + + // Successful frame + return true; + } +}; + + +// Main entry point for the application +int main() +{ + // Construct demo application + Example_LocalTransforms 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/examples/olcPGE3_WorldTransform.cpp b/examples/olcPGE3_WorldTransform.cpp index 375ff547..0d78c74e 100644 --- a/examples/olcPGE3_WorldTransform.cpp +++ b/examples/olcPGE3_WorldTransform.cpp @@ -124,9 +124,9 @@ class Example_WorldTransform: public olc::PixelGameEngine DrawScene(olc::Colour::WHITE); // Draw in yellow the transformed world - draw.WorldOffset(vWorldOffset); draw.WorldScale(vWorldScale); draw.WorldRotate(fWorldRotation); + draw.WorldOffset(vWorldOffset); DrawScene(olc::Colour::YELLOW); // Get Mouse in "transformed world" space diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 00c8a762..217f0df7 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -118,6 +118,7 @@ #include #include #include +#include #include #include #include @@ -1382,6 +1383,14 @@ namespace olc me(2, 2) = 1; } + // Create instant identity matrix + static inline constexpr auto identity_matrix() + { + m_3d out; + out.identity(); + return out; + } + inline constexpr std::array m4x4() { auto& me = (*this); @@ -1410,6 +1419,15 @@ namespace olc translate(v.x, v.y); } + // Create instant translation matrix + template + static inline constexpr auto translation(const olc::v_2d& v) + { + m_3d out; + out.translate(v.x, v.y); + return out; + } + // Create scaling matrix via components template inline constexpr void scale(const Q x, const Q y) @@ -1427,6 +1445,15 @@ namespace olc scale(v.x, v.y); } + // Create instant scaling matrix + template + static inline constexpr auto scaling(const olc::v_2d& v) + { + m_3d out; + out.scale(v.x, v.y); + return out; + } + // Create rotation matrix with radians template inline constexpr void rotate(const Q rads) @@ -1439,6 +1466,15 @@ namespace olc me(1, 1) = me(0, 0); } + // Create instant rotation matrix with radians + template + static inline constexpr auto rotation(const Q rads) + { + m_3d out; + out.rotate(rads); + return out; + } + // Create shearing matrix via components template inline constexpr void shear(const Q x, const Q y) @@ -1456,6 +1492,40 @@ namespace olc shear(v.x, v.y); } + // Create instant shearing matrix + template + static inline constexpr auto shearing(const olc::v_2d& v) + { + m_3d out; + out.shear(v.x, v.y); + return out; + } + + + // Return inverted matrix + //inline constexpr auto invert() const + //{ + // // https://stackoverflow.com/a/18504573 + // olc::m_3d out; + // auto& me = (*this); + + // T det = me(0, 0) * (me(1, 1) * me(2, 2) - me(2, 1) * me(1, 2)) - + // me(0, 1) * (me(1, 0) * me(2, 2) - me(1, 2) * me(2, 0)) + + // me(0, 2) * (me(1, 0) * me(2, 1) - me(1, 1) * me(2, 0)); + + // T invdet = T(1) / det; + + // out(0, 0) = (me(1, 1) * me(2, 2) - me(2, 1) * me(1, 2)) * invdet; + // out(0, 1) = (me(0, 2) * me(2, 1) - me(0, 1) * me(2, 2)) * invdet; + // out(0, 2) = (me(0, 1) * me(1, 2) - me(0, 2) * me(1, 1)) * invdet; + // out(1, 0) = (me(1, 2) * me(2, 0) - me(1, 0) * me(2, 2)) * invdet; + // out(1, 1) = (me(0, 0) * me(2, 2) - me(0, 2) * me(2, 0)) * invdet; + // out(1, 2) = (me(1, 0) * me(0, 2) - me(0, 0) * me(1, 2)) * invdet; + // out(2, 0) = (me(1, 0) * me(2, 1) - me(2, 0) * me(1, 1)) * invdet; + // out(2, 1) = (me(2, 0) * me(0, 1) - me(0, 0) * me(2, 1)) * invdet; + // out(2, 2) = (me(0, 0) * me(1, 1) - me(1, 0) * me(0, 1)) * invdet; + // return out; + //} // Return inverted matrix inline constexpr auto invert() const @@ -1465,8 +1535,8 @@ namespace olc auto& me = (*this); T det = me(0, 0) * (me(1, 1) * me(2, 2) - me(2, 1) * me(1, 2)) - - me(0, 1) * (me(1, 0) * me(2, 2) - me(1, 2) * me(2, 0)) + - me(0, 2) * (me(1, 0) * me(2, 1) - me(1, 1) * me(2, 0)); + me(0, 1) * (me(1, 0) * me(2, 2) - me(1, 2) * me(2, 0)) + + me(0, 2) * (me(1, 0) * me(2, 1) - me(1, 1) * me(2, 0)); T invdet = T(1) / det; @@ -1502,7 +1572,7 @@ namespace olc olc::m_3d out; for (size_t c = 0; c < 3; c++) for (size_t r = 0; r < 3; r++) - out(r, c) = me(r, 0) * rhs(0, c) + me(r, 1) * rhs(1, c) + me(r, 2) * rhs(2, c); + out(c, r) = me(0, r) * rhs(c, 0) + me(1, r) * rhs(c, 1) + me(2, r) * rhs(c, 2); return out; } @@ -1919,7 +1989,10 @@ namespace olc public: // Constructor - inline constexpr t_2d() = default; + inline constexpr t_2d() + { + identity(); + } // Copy constructor inline constexpr t_2d(const t_2d& t) = default; @@ -1927,17 +2000,27 @@ namespace olc // Assignment operator inline constexpr t_2d& operator=(const t_2d& t) = default; + inline constexpr void identity() + { + // Clear stacks and reset to identity + m_stackForward = std::stack>(); + m_stackInverse = std::stack>(); + + m_stackForward.push(olc::m_3d::identity_matrix()); + m_stackInverse.push(olc::m_3d::identity_matrix()); + } + // Transform a vector by this transform template inline constexpr auto forward(const olc::v_2d& v) const { - return m_mForward * v; + return m_stackForward.top() * v; } template inline constexpr auto forwardRound(const olc::v_2d& v) const { - return (m_mForward * v).round(); + return (m_stackForward.top() * v).round(); } // Transform a vector of v_2d by this transform @@ -1945,7 +2028,7 @@ namespace olc inline constexpr auto forward(const std::vector>& v) const { std::vector> o(v.size()); - std::transform(v.begin(), v.end(), o.begin(), [this](const olc::v_2d& i) {return m_mForward * i; }); + std::transform(v.begin(), v.end(), o.begin(), [this](const olc::v_2d& i) {return m_stackForward.top() * i; }); return o; } @@ -1953,7 +2036,7 @@ namespace olc inline constexpr auto forwardRound(const std::vector>& v) const { std::vector> o(v.size()); - std::transform(v.begin(), v.end(), o.begin(), [this](const olc::v_2d& i) {return (m_mForward * i).round(); }); + std::transform(v.begin(), v.end(), o.begin(), [this](const olc::v_2d& i) {return (m_stackForward.top() * i).round(); }); return o; } @@ -1961,14 +2044,14 @@ namespace olc template inline constexpr auto forwardX(std::vector>&& v) const { - std::transform(v.begin(), v.end(), v.begin(), [this](const olc::v_2d& i) {return (m_mForward * i); }); + std::transform(v.begin(), v.end(), v.begin(), [this](const olc::v_2d& i) {return (m_stackForward.top() * i); }); return v; } template inline constexpr auto forwardRoundX(std::vector>&& v) const { - std::transform(v.begin(), v.end(), v.begin(), [this](const olc::v_2d& i) {return (m_mForward * i).round(); }); + std::transform(v.begin(), v.end(), v.begin(), [this](const olc::v_2d& i) {return (m_stackForward.top() * i).round(); }); return v; } @@ -1976,7 +2059,7 @@ namespace olc template inline constexpr auto inverse(const olc::v_2d& v) const { - return m_mInverse * v; + return m_stackInverse.top() * v; } // Transform a vector by this transform @@ -1984,7 +2067,7 @@ namespace olc inline constexpr auto inverse(const std::vector>& v) const { std::vector> o(v.size()); - std::transform(v.begin(), v.end(), o.begin(), [this](const olc::v_2d& i) {return m_mInverse * i; }); + std::transform(v.begin(), v.end(), o.begin(), [this](const olc::v_2d& i) {return m_stackInverse.top() * i; }); return o; } @@ -1992,7 +2075,7 @@ namespace olc template inline constexpr auto inverseX(std::vector>&& v) const { - std::transform(v.begin(), v.end(), v.begin(), [this](const olc::v_2d& i) {return m_mInverse * i; }); + std::transform(v.begin(), v.end(), v.begin(), [this](const olc::v_2d& i) {return m_stackInverse.top() * i; }); return v; } @@ -2001,135 +2084,126 @@ namespace olc template inline constexpr void scale(const olc::v_2d& v) { - m_vScale = v; - m_mScale.scale(m_vScale); - update(); + m_stackForward.push(m_stackForward.top() * olc::m_3d::scaling(v)); + m_stackInverse.push(m_stackForward.top().invert()); } // Get scaling component of this transformation - inline constexpr const auto& scale() const + inline constexpr const auto scale() const { - return m_vScale; + const auto& mat = m_stackForward.top(); + return olc::v_2d + { + T(olc::internal::sgn(mat(0, 0)) * std::hypot(mat(0, 0), mat(1, 0))), + T(olc::internal::sgn(mat(1, 1)) * std::hypot(mat(0, 1), mat(1, 1))) + }; } // Set translation component of this transformation template inline constexpr void translate(const olc::v_2d& v) { - m_vTranslate = v; - m_mTranslate.translate(m_vTranslate); - update(); + m_stackForward.push(m_stackForward.top() * olc::m_3d::translation(v)); + m_stackInverse.push(m_stackForward.top().invert()); } // Get translation component of this transformation - inline constexpr const auto& translate() const + inline constexpr const auto translation() const { - return m_vTranslate; + const auto& mat = m_stackForward.top(); + return olc::v_2d + { + T(mat(2, 0)), + T(mat(2, 1)) + }; } // Set translation component of this transformation template inline constexpr void rotate(const Q& v, const olc::v_2d& p = { 0,0 }) { - m_dTheta = T(v); - m_vRotatePoint = p; - m_3d matTrans1; - matTrans1.translate(-m_vRotatePoint); - m_3d matTrans2; - matTrans2.translate(m_vRotatePoint); - m_3d matRotate; - matRotate.rotate(m_dTheta); - m_mRotate = matTrans1 * matRotate * matTrans2; - update(); + m_stackForward.push(m_stackForward.top() * (olc::m_3d::translation(-p) * olc::m_3d::rotation(v) * olc::m_3d::translation(p))); + m_stackInverse.push(m_stackForward.top().invert()); } // Get translation component of this transformation - inline constexpr const auto& rotate() const + inline constexpr const auto rotation() const { - return m_dTheta; + const auto& mat = m_stackForward.top(); + return T(std::atan2(mat(0, 1), mat(1, 1))); } // Set shear component of this transformation template inline constexpr void shear(const olc::v_2d& v) { - m_vShear = v; - m_mShear.shear(m_vShear); - update(); - } - - // Get shear component of this transformation - inline constexpr const auto& shear() const - { - return m_vShear; + m_stackForward.push(m_stackForward.top() * olc::m_3d::shearing(v)); + m_stackInverse.push(m_stackForward.top().invert()); } // Get forward transformation matrix inline constexpr const auto& forward_matrix() const { - return m_mForward; + return m_stackForward.top(); } // Get inverse transformation matrix inline constexpr const auto& inverse_matrix() const { - return m_mInverse; + return m_stackInverse.top(); } - // Construct transform from an existing transformation matrix template - inline constexpr void from_matrix(const olc::m_3d& mat) + inline constexpr auto operator * (const olc::t_2d& rhs) const { - // https://math.stackexchange.com/a/13165 - m_vTranslate = - { - T(mat(2, 0)), - T(mat(2, 1)) - }; - - m_vScale = - { - T(olc::internal::sgn(mat(0, 0)) * std::hypot(mat(0, 0), mat(1, 0))), - T(olc::internal::sgn(mat(1, 1)) * std::hypot(mat(0, 1), mat(1, 1))) - }; + auto& me = *this; + return me.forward_matrix() * rhs.forward_matrix(); + } - m_dTheta = T(std::atan2(mat(0, 1), mat(1, 1))); + // Push a new transform onto the stack, multiplying it with the + // current transform (arbitrary affine matrix) + template + inline constexpr auto push(const olc::m_3d& m) + { + m_stackForward.push(m_stackForward.top() * m); + m_stackInverse.push(m_stackForward.top().invert()); - m_mTranslate.translate(m_vTranslate); - m_mScale.scale(m_vScale); - m_mRotate.rotate(m_dTheta); - update(); } - template - inline constexpr auto operator * (const olc::t_2d& rhs) const + // Pop the last transform off the stack, reverting to the + // previous transform + inline constexpr auto pop() { - auto& me = *this; - return me.forward_matrix() * rhs.forward_matrix(); + if (!m_stackForward.empty()) + { + m_stackForward.pop(); + m_stackInverse.pop(); + + if (m_stackForward.empty()) + identity(); + } } - protected: - // Constructs resultant matrices when transformation changes - inline constexpr void update() + // Condense the transform stack into a single matrix, leaving + // the current transformation intact but removing all history + inline constexpr void squash() { - m_mForward = m_mScale * m_mRotate * m_mShear * m_mTranslate; - m_mInverse = m_mForward.invert(); + if (m_stackForward.size() > 1) + { + auto top = m_stackForward.top(); + m_stackForward = std::stack>(); + m_stackInverse = std::stack>(); + m_stackForward.push(top); + m_stackInverse.push(top.invert()); + } } protected: - T m_dTheta = 0; - olc::v_2d m_vScale = { 1,1 }; - olc::v_2d m_vTranslate = { 0,0 }; - olc::v_2d m_vRotatePoint = { 0,0 }; - olc::v_2d m_vShear = { 0,0 }; - olc::m_3d m_mForward; - olc::m_3d m_mInverse; - olc::m_3d m_mScale; - olc::m_3d m_mTranslate; - olc::m_3d m_mRotate; - olc::m_3d m_mShear; + std::stack> m_stackForward; + std::stack> m_stackInverse; }; + typedef t_2d tf2d; typedef t_2d td2d; } diff --git a/utilities/olcUTIL3_Camera2D.h b/utilities/olcUTIL3_Camera2D.h index 50916a85..b6839154 100644 --- a/utilities/olcUTIL3_Camera2D.h +++ b/utilities/olcUTIL3_Camera2D.h @@ -246,8 +246,9 @@ namespace olc::utils if (bUpdateTransform) { + transform.identity(); transform.scale(m_vViewScale); - transform.translate(m_vViewPos * -m_vViewScale); + transform.translate(-m_vViewPos); } @@ -286,17 +287,17 @@ namespace olc::utils bPanning = false; } - // Get current mouse position + // Squash current transform to a single matrix. This is required + // so we dont contnuously add to the transform stack + transform.squash(); + + // Get current mouse position olc::vf2d vMousePos = mouse.GetPosition(); + olc::vf2d vMouseWorldPos = transform.inverse(vMousePos); + olc::vf2d vLastMouseWorldPos = transform.inverse(vLastMouseScreenPos); + + // NOTE!!! Scale & Rotate BEFORE translation - // 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 @@ -305,36 +306,42 @@ namespace olc::utils if (mouse.GetWheel() != 0) { // Cache the mouse position before transformation - auto posWorldBeforeRotate = transform.inverse(vLastMouseScreenPos); + auto posWorldBefore = vMouseWorldPos; // 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); + //float fRotateDelta = (mouse.GetWheel() > 0) ? 0.1f : -0.1f; + //transform.rotate(fRotateDelta, 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); + olc::vf2d vScaleDelta = olc::vf2d{ 1.0f, 1.0f } * ((mouse.GetWheel() > 0) ? 1.1f : 0.9f); + transform.scale(vScaleDelta); //} // Get the new screen position of the point under the mouse - auto posScreenAfterRotate = transform.forward(posWorldBeforeRotate); + auto posWorldAfter = transform.inverse(mouse.GetPosition()); // Adjust translation to keep mouse position stable - auto posScreenDisplacement = vLastMouseScreenPos - posScreenAfterRotate; + auto posScreenDisplacement = posWorldBefore - posWorldAfter; // Apply adjustment - transform.translate(transform.translate() + posScreenDisplacement); + transform.translate(-posScreenDisplacement); } + // 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(vMouseWorldPos - vLastMouseWorldPos); + } + + + // Cache last mouse position vLastMouseScreenPos = vMousePos; } From 01e133cd6223b9ac2dbfd9308a0980ad90dfa856 Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Mon, 2 Mar 2026 21:55:40 +0000 Subject: [PATCH 099/101] sync header --- olcPixelGameEngine3.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 07c682cb..217f0df7 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -10869,13 +10869,13 @@ namespace olc::host { if(auto* pge_window = get_pge_window(xev.xfocus.window); pge_window) { - pge_window->olc_OnMouseFocus(true); + pge_window->olc_OnFocus(true); } } else if (xev.type == FocusOut) { if(auto* pge_window = get_pge_window(xev.xfocus.window); pge_window) { - pge_window->olc_OnMouseFocus(false); + pge_window->olc_OnFocus(false); } } else if (xev.type == ClientMessage) From 3fb37836f4ab025719b1d2f62d1f486314518c83 Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Mon, 2 Mar 2026 22:10:33 +0000 Subject: [PATCH 100/101] fix fish demo to use new transforms --- dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj | 16 ++++++++-------- dev/src/host_win_winapi.cpp | 6 ++++++ dev/src/host_win_winapi.h | 3 ++- examples/olcPGE3_Fish.cpp | 2 +- olcPixelGameEngine3.h | 15 ++++++++++++--- 5 files changed, 29 insertions(+), 13 deletions(-) diff --git a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj index 47b250e2..0966737d 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj @@ -56,10 +56,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/dev/src/host_win_winapi.cpp b/dev/src/host_win_winapi.cpp index 2a82dde8..8241929f 100644 --- a/dev/src/host_win_winapi.cpp +++ b/dev/src/host_win_winapi.cpp @@ -396,6 +396,12 @@ namespace olc::host SetCursorPos(p.x, p.y); return true; } + + bool Host_Windows_WinAPI::SetFullScreen(olc::Window* pWindow, const bool bFullScreen) + { + + return false; + } LRESULT Host_Windows_WinAPI::OnWindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { diff --git a/dev/src/host_win_winapi.h b/dev/src/host_win_winapi.h index 98b3413c..7138fac7 100644 --- a/dev/src/host_win_winapi.h +++ b/dev/src/host_win_winapi.h @@ -76,7 +76,8 @@ namespace olc public: // Platform specific Mouse Control bool SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) override; - bool SetMouseVisible(olc::Window* pWindow, const bool bVisible) override; + bool SetMouseVisible(olc::Window* pWindow, const bool bVisible) override; + bool SetFullScreen(olc::Window* pWindow, const bool bFullScreen) override; public: // OS Specific Environment Information olc::KeyboardLayout GetKeyboardLayout() const override; diff --git a/examples/olcPGE3_Fish.cpp b/examples/olcPGE3_Fish.cpp index e1959328..3168d6ca 100644 --- a/examples/olcPGE3_Fish.cpp +++ b/examples/olcPGE3_Fish.cpp @@ -288,7 +288,7 @@ class Fish { 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& center_pos = pge.GetWorldTransform().translation(); const auto& end_pos = pge.GetWorldTransform().forwardRound(olc::vf2d{1.0f * fin_scale, 0.0f}); fin_points[2 * i] = center_pos; diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index 4145c9cb..a89bae57 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -4282,7 +4282,8 @@ namespace olc public: // Platform specific Mouse Control bool SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) override; - bool SetMouseVisible(olc::Window* pWindow, const bool bVisible) override; + bool SetMouseVisible(olc::Window* pWindow, const bool bVisible) override; + bool SetFullScreen(olc::Window* pWindow, const bool bFullScreen) override; public: // OS Specific Environment Information olc::KeyboardLayout GetKeyboardLayout() const override; @@ -5646,6 +5647,10 @@ namespace olc::host #include #include +#ifndef WL_KEYBOARD_KEY_STATE_REPEATED +#define WL_KEYBOARD_KEY_STATE_REPEATED 2 +#endif + namespace olc::host { struct WaylandWindow { @@ -7403,6 +7408,12 @@ namespace olc::host SetCursorPos(p.x, p.y); return true; } + + bool Host_Windows_WinAPI::SetFullScreen(olc::Window* pWindow, const bool bFullScreen) + { + + return false; + } LRESULT Host_Windows_WinAPI::OnWindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { @@ -11894,7 +11905,6 @@ 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) { @@ -11910,7 +11920,6 @@ 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 d51caad87c4dcec1b90f6d383d9e3a05b16f3caf Mon Sep 17 00:00:00 2001 From: Javidx9 <25419386+OneLoneCoder@users.noreply.github.com> Date: Mon, 2 Mar 2026 22:27:06 +0000 Subject: [PATCH 101/101] close enough for now --- dev/src/host_win_winapi.cpp | 22 +++++++++++++++++++++- dev/tests/test_mh.cpp | 5 +++++ olcPixelGameEngine3.h | 22 +++++++++++++++++++++- 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/dev/src/host_win_winapi.cpp b/dev/src/host_win_winapi.cpp index 8241929f..3fe19d1a 100644 --- a/dev/src/host_win_winapi.cpp +++ b/dev/src/host_win_winapi.cpp @@ -399,8 +399,28 @@ namespace olc::host bool Host_Windows_WinAPI::SetFullScreen(olc::Window* pWindow, const bool bFullScreen) { + HWND hWnd = mapUID2HWND.at(pWindow->GetUID()); - return false; + if (bFullScreen) + { + // Maximise, make on top, remove border and titlebar + SetWindowLongPtr(hWnd, GWL_STYLE, WS_POPUP | WS_VISIBLE); + SetWindowLongPtr(hWnd, GWL_EXSTYLE, WS_EX_TOPMOST); + ShowWindow(hWnd, SW_MAXIMIZE); + } + else + { + // Restore original window style and position + SetWindowLongPtr(hWnd, GWL_STYLE, WS_CAPTION | WS_SYSMENU | WS_VISIBLE | WS_THICKFRAME); + SetWindowLongPtr(hWnd, GWL_EXSTYLE, WS_EX_APPWINDOW | WS_EX_WINDOWEDGE); + ShowWindow(hWnd, SW_RESTORE); + } + + UpdateWindow(hWnd); + SetForegroundWindow(hWnd); + SetFocus(hWnd); + SetActiveWindow(hWnd); + return true; } LRESULT Host_Windows_WinAPI::OnWindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) diff --git a/dev/tests/test_mh.cpp b/dev/tests/test_mh.cpp index aa2b90f2..9ab64439 100644 --- a/dev/tests/test_mh.cpp +++ b/dev/tests/test_mh.cpp @@ -245,6 +245,11 @@ class Example : public olc::PixelGameEngine //return true; + if (keyboard.GetKey(olc::Key::F1).bPressed) + ShowFullScreen(true); + if (keyboard.GetKey(olc::Key::F2).bPressed) + ShowFullScreen(false); + if (mouse.GetButton(1).bHeld) { fAngle += 0.5f * fElapsedTime; diff --git a/olcPixelGameEngine3.h b/olcPixelGameEngine3.h index a89bae57..3b6b07c5 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -7411,8 +7411,28 @@ namespace olc::host bool Host_Windows_WinAPI::SetFullScreen(olc::Window* pWindow, const bool bFullScreen) { + HWND hWnd = mapUID2HWND.at(pWindow->GetUID()); - return false; + if (bFullScreen) + { + // Maximise, make on top, remove border and titlebar + SetWindowLongPtr(hWnd, GWL_STYLE, WS_POPUP | WS_VISIBLE); + SetWindowLongPtr(hWnd, GWL_EXSTYLE, WS_EX_TOPMOST); + ShowWindow(hWnd, SW_MAXIMIZE); + } + else + { + // Restore original window style and position + SetWindowLongPtr(hWnd, GWL_STYLE, WS_CAPTION | WS_SYSMENU | WS_VISIBLE | WS_THICKFRAME); + SetWindowLongPtr(hWnd, GWL_EXSTYLE, WS_EX_APPWINDOW | WS_EX_WINDOWEDGE); + ShowWindow(hWnd, SW_RESTORE); + } + + UpdateWindow(hWnd); + SetForegroundWindow(hWnd); + SetFocus(hWnd); + SetActiveWindow(hWnd); + return true; } LRESULT Host_Windows_WinAPI::OnWindowEvent(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)