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/CMakeLists.txt b/CMakeLists.txt index 935b0ba1..d8f8a04a 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( @@ -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/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 -``` diff --git a/dev/msvc/olcPGE3.sln b/dev/msvc/olcPGE3.sln index d1e9d390..33b66c2e 100644 --- a/dev/msvc/olcPGE3.sln +++ b/dev/msvc/olcPGE3.sln @@ -1,7 +1,7 @@  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 diff --git a/dev/msvc/olcPGE3.vcxproj b/dev/msvc/olcPGE3.vcxproj index 2f7fcc46..d7b7a70b 100644 --- a/dev/msvc/olcPGE3.vcxproj +++ b/dev/msvc/olcPGE3.vcxproj @@ -195,13 +195,17 @@ - + + true + true + true + true true @@ -236,6 +240,9 @@ true + true + true + true @@ -252,12 +259,14 @@ + + @@ -269,13 +278,16 @@ - - + + true + true + true + true true @@ -307,6 +319,9 @@ true + true + true + true true @@ -320,36 +335,20 @@ true true - - - + 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 df812651..58f63f12 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} + @@ -69,45 +75,15 @@ 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 - - - Header Files - Header Files @@ -159,53 +135,65 @@ Hosts\Android Specific + + Header Files + + + Header Files + + + Hosts + + + GPUs + + + GPUs + + + GPUs + + + GPUs + + + Hardware + + + Hardware + + + Hardware + + + Hardware + + + Hosts + + + Header Files + - - Source Files\tests - - - Source Files\tests - - - Source Files\tests - Source Files Source Files - - Source Files\tests - - - Source Files - - - Source Files - Source Files Source Files\tests - - Source Files - - - Source Files - - + Source Files Source Files - - Source Files - Hosts\Mac Specific @@ -233,14 +221,32 @@ Hosts\Wayland Specific - - Source Files - Hosts\Android Specific Hosts\Android Specific + + GPUs + + + GPUs + + + GPUs + + + Hardware + + + Hardware + + + Hosts + + + 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..0966737d 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj @@ -19,6 +19,12 @@ + + true + true + true + true + true true @@ -43,13 +49,48 @@ true true + + true + true + true + true + + + false + false + false + false + true true true true - + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + true true @@ -116,6 +157,18 @@ true true + + 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..6ebcfc8f 100644 --- a/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters +++ b/dev/msvc/olcPGE3_BuildSH/olcPGE3_BuildSH.vcxproj.filters @@ -69,6 +69,30 @@ Source Files + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + diff --git a/dev/src/api_macos.cpp b/dev/src/api_macos.cpp index 47fb2d9d..fe6df724 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"; @@ -29,6 +39,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:"; @@ -61,6 +72,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:"; @@ -74,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"; @@ -126,7 +145,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; @@ -140,9 +159,11 @@ 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 +// Objective-C method type encoding constants // Type encoding for methods returning BOOL with no parameters: "c@:" static constexpr const char* kBoolMethodTypeEncoding = "c@:"; @@ -157,43 +178,131 @@ 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 = 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; + + // 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; + 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 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; + 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 @@ -209,6 +318,13 @@ namespace ObjectiveCSEL { activateIgnoringOtherAppsSel = sel_registerName(kActivateIgnoringOtherAppsSel); 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); @@ -241,6 +357,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); @@ -254,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); @@ -347,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; @@ -500,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 @@ -582,6 +701,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}; @@ -592,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 @@ -616,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}; @@ -644,7 +769,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}; @@ -781,6 +906,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 @@ -829,7 +963,6 @@ MouseEventData extractMouseEventData(id event) { // Convert to content view coordinates and flip Y coordinate convertToContentViewCoordinates(data.location); - return data; } @@ -957,6 +1090,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; @@ -1016,7 +1204,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); @@ -1029,7 +1220,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); @@ -1075,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); } @@ -1212,6 +1410,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 +1425,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); @@ -1345,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; @@ -1353,8 +1567,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); @@ -1474,6 +1687,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; @@ -1887,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; @@ -1912,6 +2166,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; @@ -1968,6 +2227,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 af4adc8c..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) @@ -49,6 +50,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); @@ -68,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); @@ -105,6 +109,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 (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); @@ -116,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 c2eceb67..52be9a2b 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_; } @@ -444,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; @@ -485,6 +523,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()); @@ -650,6 +695,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; @@ -682,6 +740,7 @@ namespace olc { Window& window_; std::function keyDownHandler_; std::function keyUpHandler_; + std::function flagsChangedHandler_; std::function mouseDownHandler_; std::function mouseUpHandler_; std::function mouseMovedHandler_; @@ -693,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 @@ -702,6 +763,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) { @@ -720,6 +789,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_); } @@ -734,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); @@ -804,6 +891,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); @@ -858,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/api_opengl.cpp b/dev/src/api_opengl.cpp index 7d7e465d..4923ef82 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 @@ -83,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; } @@ -250,6 +255,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 +511,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 b46fe1cc..4ba39a28 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,14 +11,22 @@ #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 #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))) @@ -27,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 @@ -45,7 +50,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 @@ -170,11 +176,17 @@ 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); #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 { @@ -231,10 +243,16 @@ 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 +#if OLC_HOST == OLC_HOST_LINUX_X11 + public: + glXSwapIntervalEXT_t* XSwapIntervalEXT = nullptr; +#endif public: // Proxies allow switchable, clutter-free error checking @@ -281,6 +299,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); @@ -302,7 +322,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); @@ -327,8 +347,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_X = 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()); @@ -337,8 +358,3 @@ namespace olc } // olc namespace //! END DECLARATION - - - - - diff --git a/dev/src/config.h b/dev/src/config.h index 9967d0a6..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,42 +68,69 @@ #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 #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 + #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 +// 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 @@ -132,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 7cec77a0..7b3a0449 100644 --- a/dev/src/core.cpp +++ b/dev/src/core.cpp @@ -1,46 +1,74 @@ #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" -#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 { + PGEWindow::PGEWindow() : Window(), draw() + { + } + bool PGEWindow::Create(const olc::vi2d& vScreenSize, const olc::vi2d& vPixelSize) { //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 @@ -67,49 +95,72 @@ namespace olc bool PGEWindow::olc_WindowUpdate(const float fElapsedTime, const float fTotalElapsedTime) { + float fDT = fElapsedTime; + // Input Changes mouse.UpdateState(); keyboard.UpdateState(); draw.SetGPU(pRenderer); - draw.SetTarget(GetDefaultImage()); + 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 (GetDefaultImage().GetConfig().MSAA) + for (const auto& pgex : vecWindowExtensions) { - pRenderer->ResolveMSAA(uint32_t(GetDefaultImage().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 === @@ -117,7 +168,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); @@ -140,10 +191,13 @@ 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); - 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 @@ -153,6 +207,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 @@ -229,12 +289,12 @@ namespace olc pImageLoader = imload; } - olc::Image& PGEWindow::GetDefaultImage() + olc::Image& PGEWindow::GetScreen() { return imgPrimary; } - olc::Draw2D& PGEWindow::GetDraw() + olc::Draw& PGEWindow::GetDraw() { return draw; } @@ -251,7 +311,14 @@ namespace olc const olc::vi2d& PGEWindow::ScreenSize() { - return GetDefaultImage().Size(); + 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) @@ -265,8 +332,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; } @@ -295,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. @@ -305,31 +391,7 @@ 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( - olc::host::Host_Android::androidApp->activity->assetManager - ); -#endif + imageloader = std::make_unique(); // Allow host to prepare itself return host->OnApplicationStart(this); @@ -371,7 +433,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) @@ -391,7 +453,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); @@ -399,7 +461,24 @@ namespace olc // Prepare Draw2D system draw.SetGPU(gpu.get()); gpu->ApplyDefaultShader(); - draw.SetTarget(GetDefaultImage()); + 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()) @@ -412,7 +491,24 @@ namespace olc draw.ProcessGPUTasks(); // Set to known default state - draw.SetTarget(GetDefaultImage()); + draw.SetTarget(GetScreen()); + 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(); @@ -465,7 +561,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; @@ -478,9 +574,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 @@ -542,39 +656,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) // { @@ -602,69 +692,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 ea46277c..81f9b757 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 "draw.h" +#include "extension.h" //! END CUSTOMHEADER GLOBAL //! START DECLARATION @@ -57,12 +59,15 @@ 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 class PGEWindow : public Window { public: + PGEWindow(); bool Create(const olc::vi2d& vScreenSize, const olc::vi2d& vPixelSize); public: @@ -94,8 +99,8 @@ namespace olc public: // Returns the image that represents the primary drawing surface - olc::Image& GetDefaultImage(); - olc::Draw2D& GetDraw(); + olc::Image& GetScreen(); + olc::Draw& GetDraw(); // Input devices are handled by a regular olc::Window, but for convenience... olc::hw::Mouse& GetMouse(); @@ -104,6 +109,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; @@ -111,8 +120,7 @@ namespace olc virtual bool olc_WindowUpdate(const float fElapsedTime, const float fTotalElapsedTime); protected: - olc::Draw2D draw; - + olc::Draw draw; private: olc::Image imgPrimary; @@ -121,9 +129,16 @@ 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; + + // Application Name + std::string sAppName = ""; }; // The olc::PixelGameEngine3 core, manages the main window, child windows, engine loop, timing and devices @@ -156,6 +171,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 @@ -174,6 +193,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/draw.cpp b/dev/src/draw.cpp new file mode 100644 index 00000000..91208319 --- /dev/null +++ b/dev/src/draw.cpp @@ -0,0 +1,1171 @@ +#include "draw.h" + +#include "gpu_iface.h" + +//! START IMPLEMENTATION +using namespace olc; + +// Some local pools to reduce allocations +thread_local Draw::buffer Draw::buffPoints; +thread_local Draw::buffer Draw::buffColours; +thread_local Draw::buffer Draw::buffUnitCirclePoints; +thread_local Draw::buffer Draw::vecGPUTasks; + +Draw::Draw() +{ + vecGPUTasks.reserve(256); +} + +void Draw::SetGPU(olc::gpu::Renderer* const renderer) +{ + pRenderer = renderer; +} + +void Draw::SetTarget(olc::Image& image) +{ + // Perform any outstanding tasks for current target + ProcessGPUTasks(); + + // Only resolve if we're ACTUALLY changing targets + if (pTarget && pTarget != &image && pTarget->GetConfig().MSAA) + { + pRenderer->ResolveMSAA(uint32_t(pTarget->GetGPUID())); + } + + // Endure new target exists in GPU up to date + PrepareImageForHW(image); + + // Store the target image + pTarget = ℑ + + // Reset Affine transform to unity + WorldReset(); + + // Configure default render target + pRenderer->AssignTextureTarget(0, uint32_t(pTarget->GetGPUID())); + pRenderer->SetViewport({ 0,0 }, pTarget->Size()); +} + +olc::Image& olc::Draw::GetTarget() +{ + return *pTarget; +} + +olc::vi2d olc::Draw::GetTargetSize() +{ + return pTarget->Size(); +} + +void olc::Draw::ProcessGPUTasks() +{ + for (const auto& task : vecGPUTasks.data) + pRenderer->DoGPUTask(task); + + drawMetrics.nGPUTasks += uint32_t(vecGPUTasks.data.size()); + + vecGPUTasks.data.clear(); +} + +void Draw::PrepareTargetForSW() +{ + if (pTarget->BoundToGPU()) + { + // Process GPU queue bound for the target + ProcessGPUTasks(); + + // Image resource is primed for GPU operations, bring it to CPU + pRenderer->ReadTexture(uint32_t(pTarget->GetGPUID()), *pTarget); + + // Image is now CPU bound + pTarget->BindCPU(); + + drawMetrics.nGPUtoCPUTransfers++; + } +} + +void Draw::PrepareTargetForHW() +{ + if (pTarget->BoundToCPU()) + { + // Image resource is primed for CPU operations, send it to GPU + pRenderer->WriteTexture(uint32_t(pTarget->GetGPUID()), *pTarget); + + // Image is now GPU bound + pTarget->BindGPU(); + + drawMetrics.nCPUtoGPUTransfers++; + } +} + +void Draw::PrepareImageForSW(olc::Image& image) +{ + if (image.BoundToGPU()) + { + // Process GPU queue bound for the target + ProcessGPUTasks(); + + // Image resource is primed for GPU operations, bring it to CPU + pRenderer->ReadTexture(uint32_t(image.GetGPUID()), image); + + // Image is now CPU bound + image.BindCPU(); + + drawMetrics.nGPUtoCPUTransfers++; + } +} + +void Draw::PrepareImageForHW(olc::Image& image) +{ + if (image.BoundToCPU()) + { + // Image resource is primed for CPU operations, send it to GPU + pRenderer->WriteTexture(uint32_t(image.GetGPUID()), image); + + // Image is now GPU bound + image.BindGPU(); + + drawMetrics.nCPUtoGPUTransfers++; + } + + // Only resolve MSAA if this image is NOT the current render target + // (i.e., we're preparing it to be SAMPLED from, not rendered to) + if (image.GetConfig().MSAA && &image != pTarget) + { + pRenderer->ResolveMSAA(uint32_t(image.GetGPUID())); + } +} + +bool olc::Draw::SetShader(const olc::gpu::Shader& shader) +{ + // Finish all drawing with current shader + ProcessGPUTasks(); + + drawMetrics.nShaderChanges++; + + // Set new shader + return pRenderer->ApplyShader(shader); +} + +bool olc::Draw::ResetShader() +{ + ProcessGPUTasks(); + drawMetrics.nShaderChanges++; + return pRenderer->ApplyDefaultShader(); +} + +bool olc::Draw::SetShaderUniform(const std::string& name, const float value) +{ + return pRenderer->SetUniform(name, value); +} + +bool olc::Draw::SetShaderUniform(const std::string& name, const olc::vf2d& value) +{ + return pRenderer->SetUniform(name, value); +} + +bool olc::Draw::SetShaderUniform(const std::string& name, const olc::Pixel value) +{ + return pRenderer->SetUniform(name, value); +} + +bool olc::Draw::SetShaderTexture(const uint32_t nSlot, olc::Image& image) +{ + PrepareImageForHW(image); + return pRenderer->AssignTextureSource(nSlot, image.GetGPUID()); +} + +void olc::Draw::ResetDrawMetrics() +{ + drawMetrics = sDrawMetrics(); +} + +olc::Draw::sDrawMetrics olc::Draw::GetDrawMetrics() const +{ + return drawMetrics; +} + + +void olc::Draw::WorldReset() +{ + transformAffine = olc::tf2d(); +} + +void olc::Draw::WorldScale(const olc::vf2d& vScale) +{ + transformAffine.scale(vScale); +} + +void olc::Draw::WorldOffset(const olc::vf2d& vOffset) +{ + transformAffine.translate(vOffset); +} + +void olc::Draw::WorldRotate(const float& fTheta, const olc::vf2d& vPoint) +{ + transformAffine.rotate(fTheta, vPoint); +} + +void olc::Draw::SetWorldTransform(const olc::tf2d& trans) +{ + transformAffine = trans; +} + +olc::tf2d& olc::Draw::GetWorldTransform() +{ + return transformAffine; +} + +olc::vf2d olc::Draw::WorldToScreen(const olc::vf2d& v) const +{ + return transformAffine.forward(v); +} + +olc::vf2d olc::Draw::ScreenToWorld(const olc::vf2d& v) const +{ + return transformAffine.inverse(v); +} + +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); + if (tpos.x >= 0 && tpos.y >= 0 && tpos.x < float(pTarget->Size().x) && tpos.y < float(pTarget->Size().y)) + { + PrepareTargetForSW(); + pTarget->Pixel(tpos) = col.blend(tint); + } + + // otherwise do nothing +} + +olc::Pixel olc::Draw::GetPixel(olc::Image& image, const olc::vf2d& pos) +{ + PrepareImageForSW(image); + return image.Pixel(pos); +} + +olc::Pixel olc::Draw::GetPixel(const olc::vf2d& pos) +{ + PrepareImageForSW(GetTarget()); + return GetTarget().Pixel(pos); +} + +void olc::Draw::Clear(const olc::Pixel& col) +{ + PrepareTargetForHW(); + pRenderer->ClearViewport(col, true, true); +} + +GPUTask olc::Draw::TaskDrawLine(const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) +{ + GPUTask task; + task.structure = olc::Structure::Line; + task.vertexBuffer.resize((vPoints.size()-1) * 2); + for (size_t i = 0; i < vPoints.size() - 1; i++) + { + task.vertexBuffer[i*2+0] = { {vPoints[i].x, vPoints[i].y, 1.0f, 1.0f}, vColours[i], {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + task.vertexBuffer[i*2+1] = { {vPoints[i + 1].x, vPoints[i + 1].y, 1.0f, 1.0f}, vColours[i + 1], {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + } + task.tint = tint; + return task; +} + +GPUTask olc::Draw::TaskDrawPolygon(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) +{ + GPUTask task; + task.structure = structure; + task.bWireframe = true; + task.vertexBuffer.resize(vPoints.size()); + for (size_t i = 0; i < vPoints.size(); i++) + task.vertexBuffer[i] = { {vPoints[i].x, vPoints[i].y, 1.0f, 1.0f}, vColours[i], {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + task.tint = tint; + return task; +} + +GPUTask olc::Draw::TaskDrawPolygon(olc::Structure structure, const std::vector& vPoints, const olc::Pixel colour, const olc::Pixel tint) +{ + GPUTask task; + task.structure = structure; + task.bWireframe = true; + task.vertexBuffer.resize(vPoints.size()); + for (size_t i = 0; i < vPoints.size(); i++) + task.vertexBuffer[i] = {{vPoints[i].x, vPoints[i].y, 1.0f, 1.0f}, colour, {0, 0}, {0, 0}, {0, 0}, {0, 0}}; + task.tint = tint; + return task; +} + +GPUTask olc::Draw::TaskFillPolygon(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) +{ + GPUTask task; + task.structure = structure; + task.vertexBuffer.resize(vPoints.size()); + for (size_t i = 0; i < vPoints.size(); i++) + task.vertexBuffer[i] = { {vPoints[i].x, vPoints[i].y, 1.0f, 1.0f}, vColours[i], {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + task.tint = tint; + return task; +} + +GPUTask olc::Draw::TaskFillPolygon(olc::Structure structure, const std::vector& vPoints, const olc::Pixel colour, const olc::Pixel tint) +{ + GPUTask task; + task.structure = structure; + task.vertexBuffer.resize(vPoints.size()); + for (size_t i = 0; i < vPoints.size(); i++) + task.vertexBuffer[i] = { {vPoints[i].x, vPoints[i].y, 1.0f, 1.0f}, colour, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + task.tint = tint; + return task; +} + +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; + task.vertexBuffer.resize(vPoints.size()); + for (size_t i = 0; i& 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; + task.vertexBuffer.resize(vPoints.size()); + for (size_t i = 0; i < vPoints.size(); i++) + task.vertexBuffer[i] = { {vPoints[i].x, vPoints[i].y, vZWs[i].x, vZWs[i].y}, vColours[i], {vTexCoords[i].x, vTexCoords[i].y}, {0, 0}, {0, 0}, {0, 0} }; + task.pImage = image; + task.tint = tint; + 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& Draw::Line(const olc::vf2d& p1, const olc::vf2d& p2, const olc::Pixel col, const olc::Pixel tint) +{ + PrepareTargetForHW(); + + return vecGPUTasks.data.emplace_back(std::move( + TaskDrawLine( + transformAffine.forwardRoundX({ p1, p2 }), + { col,col }, + 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(); + + return vecGPUTasks.data.emplace_back(std::move( + TaskDrawPolygon( + olc::Structure::Line, + transformAffine.forwardRoundX({ p1, p2 }), + { c1, c2 }, + 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 + + PrepareTargetForHW(); + + return vecGPUTasks.data.emplace_back(std::move( + TaskDrawLine( + transformAffine.forwardRoundX({ + olc::vf2d(pos.x, pos.y), + olc::vf2d(pos.x + size.x, pos.y), + olc::vf2d(pos.x + size.x, pos.y + size.y), + olc::vf2d(pos.x, pos.y + size.y), + olc::vf2d(pos.x, pos.y) + }), + { col, col, col, col, col }, + 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(); + + return vecGPUTasks.data.emplace_back(std::move( + TaskDrawLine( + transformAffine.forwardRoundX({ + olc::vf2d(pos.x, pos.y), + olc::vf2d(pos.x + size.x, pos.y), + olc::vf2d(pos.x + size.x, pos.y + size.y), + olc::vf2d(pos.x, pos.y + size.y), + olc::vf2d(pos.x, pos.y) + }), + { colTL, colTR, colBR, colBL, colTL }, + tint + ))); +} + + + +const GPUTask& olc::Draw::FilledRect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col, const olc::Pixel tint) +{ + PrepareTargetForHW(); + + return vecGPUTasks.data.emplace_back(std::move( + TaskFillPolygon( + olc::Structure::Fan, + transformAffine.forwardRoundX({ + { pos.x, pos.y }, + { pos.x + size.x, pos.y }, + { pos.x + size.x, pos.y + size.y }, + { pos.x, pos.y + size.y } }), + col, + 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( + TaskFillPolygon( + olc::Structure::Fan, + transformAffine.forwardRoundX({ + { pos.x, pos.y }, + { pos.x + size.x, pos.y }, + { pos.x + size.x, pos.y + size.y }, + { pos.x, pos.y + size.y } }), + { colTL, colTR, colBR, colBL }, + tint + ))); +} + + +void olc::Draw::RedefineUnitCircleBuffer(const int32_t nFacets) +{ + buffUnitCirclePoints.reserve(nFacets + 1); + for (int32_t i = 0; i <= nFacets; i++) + { + float theta = float(i) / float(nFacets) * 2.0f * 3.14159265358979323846f; + buffUnitCirclePoints.data[i] = { cosf(theta), sinf(theta) }; + } +} + +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 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 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 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(); + + if (nFacets != int32_t(buffUnitCirclePoints.data.size() - 1)) + RedefineUnitCircleBuffer(nFacets); + + buffPoints.reserve(nFacets + 1); + buffColours.reserve(nFacets + 1); + + for (int32_t i = 0; i <= nFacets; i++) + { + buffPoints.data[i] = transformAffine.forwardRound({ pos.x + rx * buffUnitCirclePoints.data[i].x, pos.y + ry * buffUnitCirclePoints.data[i].y }); + buffColours.data[i] = col; + } + + return vecGPUTasks.data.emplace_back(std::move( + TaskDrawPolygon( + olc::Structure::Line, + buffPoints.data, + buffColours.data, + tint + ))); +} + + +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 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 + + PrepareTargetForHW(); + + if (nFacets != int32_t(buffUnitCirclePoints.data.size() - 1)) + RedefineUnitCircleBuffer(nFacets); + + buffPoints.reserve(nFacets + 2); + buffColours.reserve(nFacets + 2); + buffPoints.data[0] = transformAffine.forwardRound(pos); + buffColours.data[0] = colInner; + + for (int32_t i = 0; i <= nFacets; i++) + { + buffPoints.data[i + 1] = transformAffine.forwardRound({ pos.x + rx * buffUnitCirclePoints.data[i].x, pos.y + ry * buffUnitCirclePoints.data[i].y }); + buffColours.data[i + 1] = colOuter; + } + + return vecGPUTasks.data.emplace_back(std::move( + TaskFillPolygon( + olc::Structure::Fan, + buffPoints.data, + buffColours.data, + tint + ))); +} + + +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(); + + 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}); + + + return vecGPUTasks.data.emplace_back(std::move( + TaskDrawPolygon( + olc::Structure::Line, + transformAffine.forwardRound(buffPoints.data), + std::vector(buffPoints.data.size(), col), + tint + ))); +} + + +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(); + + 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 + buffPoints.data.push_back({ adjustedPos.x, adjustedPos.y }); + 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 }); + + return vecGPUTasks.data.emplace_back(std::move( + TaskFillPolygon( + olc::Structure::Fan, + transformAffine.forwardRound(buffPoints.data), + std::vector(buffPoints.data.size(), col), + 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 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(); + + return vecGPUTasks.data.emplace_back(std::move( + TaskDrawPolygon( + olc::Structure::Fan, + transformAffine.forwardRoundX({ p1, p2, p3 }), + { c1, c2, c3 }, + 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 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(); + + return vecGPUTasks.data.emplace_back(std::move( + TaskFillPolygon( + olc::Structure::Fan, + transformAffine.forwardRoundX({ p1, p2, p3 }), + { c1, c2, c3 }, + 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); + + return vecGPUTasks.data.emplace_back(std::move( + TaskTexturedPolygon( + olc::Structure::Fan, + transformAffine.forwardRoundX({ p1, p2, p3 }), + { c1, c2, c3 }, + { t1, t2, t3 }, + &texture, + 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 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 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::Draw::Polygon(const olc::Structure structure, const std::vector& vecPoints, const std::vector& vecColours, const olc::Pixel tint) +{ + PrepareTargetForHW(); + + return vecGPUTasks.data.emplace_back(std::move( + TaskDrawPolygon( + structure, + transformAffine.forwardRound(vecPoints), + vecColours, + 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::Draw::FilledPolygon(const olc::Structure structure, const std::vector& vecPoints, const std::vector& vecColours, const olc::Pixel tint) +{ + PrepareTargetForHW(); + + return vecGPUTasks.data.emplace_back(std::move( + TaskFillPolygon( + structure, + transformAffine.forwardRound(vecPoints), + vecColours, + 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); + + return vecGPUTasks.data.emplace_back(std::move( + TaskTexturedPolygon( + structure, + transformAffine.forwardRound(vecPoints), + vecColours, + vecTexCoords, + &texture, + tint + ))); +} + +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(); + + olc::vf2d spos = { 0.0f, 0.0f }; + + auto task = CreateImageBatch(font.imgFont); + + for (auto c : text) + { + const auto& glyph = font.glyphs[c]; + + if (c == '\n') + { + spos.x = 0; + spos.y += font.fLineHeight * scale.y; + } + else if (c == '\t') + { + spos.x += font.fTabWidth * scale.x; + } + else + { + 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; + } + } + + return Batch(task); +} + +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(); + + olc::vf2d spos = { 0.0f, 0.0f }; + auto task = CreateImageBatch(font.imgFont); + + for (auto c : text) + { + const auto& glyph = font.glyphs[c]; + + if (c == '\n') + { + spos.x = 0; + spos.y += font.fLineHeight * scale.y; + } + else if (c == '\t') + { + spos.x += font.fTabWidth * scale.x; + } + else + { + Draw::Image(task, font.glyphs[c].imgGlyph, pos + spos, scale, col); + spos.x += glyph.vPropSize.x * scale.x; + } + } + + return Batch(task); +} + +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 }; + + for (auto c : text) + { + const auto& glyph = font.glyphs[c]; + + if (c == '\n') + { + pos.y += font.fLineHeight * scale.y; + pos.x = 0; + } + else if (c == '\t') + { + pos.x += font.fTabWidth * scale.x; + } + else + { + if (bProportional) + pos.x += glyph.vPropSize.x * scale.x; + else + pos.x += glyph.vMonoSize.x * scale.x; + + } + + size = size.max(pos); + } + + return size; +} + +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(); + + ImageBatch b; + b.task.structure = olc::Structure::List; + b.task.pImage = ℑ + return b; +} + + +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; + + return vecGPUTasks.data.emplace_back(std::move( + TaskTexturedPolygon( + olc::Structure::Fan, + transformAffine.forwardRoundX({ + { pos.x, pos.y }, + { pos.x + size.x, pos.y }, + { pos.x + size.x, pos.y + size.y }, + { pos.x, pos.y + size.y } + }), + { tint, tint, tint, tint }, + // Tex coords are clockwise + { + image.coords[0], + image.coords[1], + image.coords[2], + image.coords[3], + }, + &image.image.get() + ))); + +} + +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); + PrepareTargetForHW(); + + 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); + + 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); + + return vecGPUTasks.data.emplace_back(std::move( + TaskTexturedPolygon( + olc::Structure::Fan, + transformAffine.forward(vPoints), + { tint, tint, tint, tint}, + { image.coords[0], image.coords[1], image.coords[2], image.coords[3] }, + &image.image.get() + ))); +} + + +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); + PrepareTargetForHW(); + + + 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], + } }; + + return vecGPUTasks.data.emplace_back(std::move( + TaskTexturedPolygon( + olc::Structure::Fan, + transformAffine.forwardRoundX({ vTL, vTR, vBR, vBL }), + { {q[0], 1.0f}, {q[1], 1.0f}, {q[2], 1.0f}, {q[3], 1.0f} }, + { tint, tint, tint, tint}, + { image.coords[0] * q[0], image.coords[1] * q[1], image.coords[2] * q[2], image.coords[3] * q[3] }, + &image.image.get() + ))); + + } + + // Default is just return a textured quad + return vecGPUTasks.data.emplace_back(std::move( + TaskTexturedPolygon( + olc::Structure::Fan, + transformAffine.forwardRoundX({ vTL, vTR, vBR, vBL }), + { tint, tint, tint, tint }, + { image.coords[0], image.coords[1], image.coords[2], image.coords[3] }, + &image.image.get() + ))); +} + + +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 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); + + PrepareTargetForHW(); + + return vecGPUTasks.data.emplace_back(std::move( + TaskTexturedPolygon( + olc::Structure::Fan, + transformAffine.forwardRoundX({ { pos.x, pos.y }, { pos.x + size.x, pos.y }, { pos.x + size.x, pos.y + size.y }, { pos.x, pos.y + size.y } }), + { tint, tint, tint, tint }, + // Tex coords are clockwise + { image.coords[0], image.coords[1], image.coords[2], image.coords[3] }, + &image.image.get() + ))); +} + + +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 81% rename from dev/src/draw2d.h rename to dev/src/draw.h index 285d747f..b1ead2c0 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" @@ -56,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]) @@ -148,6 +153,16 @@ [#] Batch(FilledBatch, [tint]) [#] Batch(ImageBatch, [tint]) + + 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]) + */ @@ -160,6 +175,8 @@ namespace olc class Renderer; class Shader; } + + class Draw3D; // These "opaque" structs are merely to help with // type differentiation of various GPUTask types @@ -170,10 +187,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); @@ -229,19 +248,22 @@ namespace olc // === Lines === - // Draws a single pixel wide line - const GPUTask& Line( - const olc::vf2d& p1, - const olc::vf2d& p2, + // Draws a single pixel wide line into a batch + const LineBatch& Line( + olc::LineBatch& batch, + 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 + + // 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 col = olc::Colour::WHITE); + const olc::Pixel c2, + const olc::Pixel tint = olc::Colour::WHITE); // Draws a single pixel wide line with a gradient const GPUTask& Line( @@ -251,21 +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 @@ -273,7 +298,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( @@ -285,29 +311,31 @@ 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, 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 colBR, const olc::Pixel tint = olc::Colour::WHITE); - + // Draws a filled, single colour rectangle into a batch const FilledBatch& FilledRect( 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( @@ -318,26 +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( @@ -345,22 +365,26 @@ 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 - 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); // Draws a shaded circle with a radial gradient @@ -372,26 +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, @@ -399,10 +421,12 @@ 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 - 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, @@ -410,39 +434,52 @@ 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, 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 + // 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, const olc::Pixel colInner, const olc::Pixel colOuter, + const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); // === 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, @@ -450,17 +487,17 @@ 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, 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 - 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, @@ -471,21 +508,27 @@ namespace olc // === Triangles === - // Draws a triangle outline with a single colour - const GPUTask& Triangle( + + + // Draws a triangle outline with a single colour into a batch + const LineBatch& Triangle( + olc::LineBatch& batch, 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 + // 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, - const olc::Pixel col = olc::Colour::WHITE); + const olc::Pixel c1, + const olc::Pixel c2, + const olc::Pixel c3, + const olc::Pixel tint = olc::Colour::WHITE); // Draws a multiple colour triangle outline const GPUTask& Triangle( @@ -497,31 +540,36 @@ 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 triangle outline with a single colour + const GPUTask& 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 col = olc::Colour::WHITE, + const olc::Pixel tint = olc::Colour::WHITE); - // Draws a filled, single colour triangle - const GPUTask& FilledTriangle( + + + // Draws a filled, single colour triangle into a batch + const FilledBatch& FilledTriangle( + olc::FilledBatch& batch, 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 + + // 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, - const olc::Pixel col = olc::Colour::WHITE); + const olc::Pixel c1, + const olc::Pixel c2, + const olc::Pixel c3, + const olc::Pixel tint = olc::Colour::WHITE); // Draws a filled, multiple colour triangle const GPUTask& FilledTriangle( @@ -533,15 +581,13 @@ 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, single colour triangle + const GPUTask& 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 col = olc::Colour::WHITE, + const olc::Pixel tint = olc::Colour::WHITE); // Draws a textured triangle, with per vertex colouring const GPUTask& TexturedTriangle( @@ -559,37 +605,42 @@ namespace olc // === Polygon Outlines === - // Draws a polygon outline with a single colour - const GPUTask& Polygon( + + + // Draws a polygon outline with a single colour into a batch + const LineBatch& Polygon( + olc::LineBatch& batch, 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 + + // Draws a polygon outline with multiple colours into a batch const LineBatch& Polygon( olc::LineBatch& batch, const std::vector& vecPoints, - const olc::Pixel col = olc::Colour::WHITE); - + 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 @@ -599,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, @@ -613,6 +672,8 @@ namespace olc 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, @@ -730,6 +791,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: @@ -802,153 +909,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: @@ -980,9 +960,6 @@ namespace olc private: sDrawMetrics drawMetrics; - - - protected: // Checks residency of image resource, and brings it to cpu RAM for r/w void PrepareTargetForSW(); @@ -998,26 +975,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.cpp b/dev/src/draw2d.cpp deleted file mode 100644 index 9842c1bd..00000000 --- a/dev/src/draw2d.cpp +++ /dev/null @@ -1,1337 +0,0 @@ -#include "draw2d.h" - -#include "gpu_iface.h" - -//! START IMPLEMENTATION -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; - -Draw2D::Draw2D() -{ - vecGPUTasks.reserve(256); -} - -void Draw2D::SetGPU(olc::gpu::Renderer* const renderer) -{ - pRenderer = renderer; -} - -void Draw2D::SetTarget(olc::Image& image) -{ - // Perform any outstanding tasks for current target - ProcessGPUTasks(); - - // Only resolve if we're ACTUALLY changing targets - if (pTarget && pTarget != &image && pTarget->GetConfig().MSAA) - { - pRenderer->ResolveMSAA(uint32_t(pTarget->GetGPUID())); - } - - // Endure new target exists in GPU up to date - PrepareImageForHW(image); - - // Store the target image - pTarget = ℑ - - // Reset Affine transform to unity - WorldReset(); - - // Configure default render target - pRenderer->AssignTextureTarget(0, uint32_t(pTarget->GetGPUID())); - pRenderer->SetViewport({ 0,0 }, pTarget->Size()); -} - -olc::Image& olc::Draw2D::GetTarget() -{ - return *pTarget; -} - -olc::vi2d olc::Draw2D::GetTargetSize() -{ - return pTarget->Size(); -} - -void olc::Draw2D::ProcessGPUTasks() -{ - for (const auto& task : vecGPUTasks.data) - pRenderer->DoGPUTask(task); - - drawMetrics.nGPUTasks += uint32_t(vecGPUTasks.data.size()); - - vecGPUTasks.data.clear(); -} - -void Draw2D::PrepareTargetForSW() -{ - if (pTarget->BoundToGPU()) - { - // Process GPU queue bound for the target - ProcessGPUTasks(); - - // Image resource is primed for GPU operations, bring it to CPU - pRenderer->ReadTexture(uint32_t(pTarget->GetGPUID()), *pTarget); - - // Image is now CPU bound - pTarget->BindCPU(); - - drawMetrics.nGPUtoCPUTransfers++; - - // Create a scanline buffer the height of this target - vScanlines.resize(size_t(pTarget->Size().y), {}); - } -} - -void Draw2D::PrepareTargetForHW() -{ - if (pTarget->BoundToCPU()) - { - // Image resource is primed for CPU operations, send it to GPU - pRenderer->WriteTexture(uint32_t(pTarget->GetGPUID()), *pTarget); - - // Image is now GPU bound - pTarget->BindGPU(); - - drawMetrics.nCPUtoGPUTransfers++; - } -} - -void Draw2D::PrepareImageForSW(olc::Image& image) -{ - if (image.BoundToGPU()) - { - // Process GPU queue bound for the target - ProcessGPUTasks(); - - // Image resource is primed for GPU operations, bring it to CPU - pRenderer->ReadTexture(uint32_t(image.GetGPUID()), image); - - // Image is now CPU bound - image.BindCPU(); - - drawMetrics.nGPUtoCPUTransfers++; - } -} - -void Draw2D::PrepareImageForHW(olc::Image& image) -{ - if (image.BoundToCPU()) - { - // Image resource is primed for CPU operations, send it to GPU - pRenderer->WriteTexture(uint32_t(image.GetGPUID()), image); - - // Image is now GPU bound - image.BindGPU(); - - drawMetrics.nCPUtoGPUTransfers++; - } - - // Only resolve MSAA if this image is NOT the current render target - // (i.e., we're preparing it to be SAMPLED from, not rendered to) - if (image.GetConfig().MSAA && &image != pTarget) - { - pRenderer->ResolveMSAA(uint32_t(image.GetGPUID())); - } -} - -bool olc::Draw2D::SetShader(const olc::gpu::Shader& shader) -{ - // Finish all drawing with current shader - ProcessGPUTasks(); - - drawMetrics.nShaderChanges++; - - // Set new shader - return pRenderer->ApplyShader(shader); -} - -bool olc::Draw2D::ResetShader() -{ - ProcessGPUTasks(); - drawMetrics.nShaderChanges++; - return pRenderer->ApplyDefaultShader(); -} - -bool olc::Draw2D::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) -{ - return pRenderer->SetUniform(name, value); -} - -bool olc::Draw2D::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) -{ - PrepareImageForHW(image); - return pRenderer->AssignTextureSource(nSlot, image.GetGPUID()); -} - -void olc::Draw2D::ResetDrawMetrics() -{ - drawMetrics = sDrawMetrics(); -} - -olc::Draw2D::sDrawMetrics olc::Draw2D::GetDrawMetrics() const -{ - return drawMetrics; -} - -void olc::Draw2D::WorldReset() -{ - transformAffine = olc::tf2d(); -} - -void olc::Draw2D::WorldScale(const olc::vf2d& vScale) -{ - transformAffine.scale(vScale); -} - -void olc::Draw2D::WorldOffset(const olc::vf2d& vOffset) -{ - transformAffine.translate(vOffset); -} - -void olc::Draw2D::WorldRotate(const float& fTheta, const olc::vf2d& vPoint) -{ - transformAffine.rotate(fTheta, vPoint); -} - -void olc::Draw2D::SetWorldTransform(const olc::tf2d& trans) -{ - transformAffine = trans; -} - -olc::tf2d& olc::Draw2D::GetWorldTransform() -{ - return transformAffine; -} - -olc::vf2d olc::Draw2D::WorldToScreen(const olc::vf2d& v) const -{ - return transformAffine.forward(v); -} - -olc::vf2d olc::Draw2D::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) -{ - // Check if in bounds - olc::vf2d tpos = transformAffine.forwardRound(pos); - if (tpos.x >= 0 && tpos.y >= 0 && tpos.x < float(pTarget->Size().x) && tpos.y < float(pTarget->Size().y)) - { - PrepareTargetForSW(); - pTarget->Pixel(tpos) = col.blend(tint); - } - - // otherwise do nothing -} - -olc::Pixel olc::Draw2D::GetPixel(olc::Image& image, const olc::vf2d& pos) -{ - PrepareImageForSW(image); - return image.Pixel(pos); -} - -olc::Pixel olc::Draw2D::GetPixel(const olc::vf2d& pos) -{ - PrepareImageForSW(GetTarget()); - return GetTarget().Pixel(pos); -} - -void olc::Draw2D::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 task; - task.structure = olc::Structure::Line; - task.vertexBuffer.resize((vPoints.size()-1) * 2); - for (size_t i = 0; i < vPoints.size() - 1; i++) - { - task.vertexBuffer[i*2+0] = { {vPoints[i].x, vPoints[i].y, 1.0f, 1.0f}, vColours[i], {0, 0}, {0, 0}, {0, 0}, {0, 0} }; - task.vertexBuffer[i*2+1] = { {vPoints[i + 1].x, vPoints[i + 1].y, 1.0f, 1.0f}, vColours[i + 1], {0, 0}, {0, 0}, {0, 0}, {0, 0} }; - } - task.tint = tint; - return task; -} - -GPUTask olc::Draw2D::TaskDrawPolygon(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) -{ - GPUTask task; - task.structure = structure; - task.bWireframe = true; - task.vertexBuffer.resize(vPoints.size()); - for (size_t i = 0; i < vPoints.size(); i++) - task.vertexBuffer[i] = { {vPoints[i].x, vPoints[i].y, 1.0f, 1.0f}, vColours[i], {0, 0}, {0, 0}, {0, 0}, {0, 0} }; - task.tint = tint; - return task; -} - -GPUTask olc::Draw2D::TaskDrawPolygon(olc::Structure structure, const std::vector& vPoints, const olc::Pixel colour, const olc::Pixel tint) -{ - GPUTask task; - task.structure = structure; - task.bWireframe = true; - task.vertexBuffer.resize(vPoints.size()); - for (size_t i = 0; i < vPoints.size(); i++) - task.vertexBuffer[i] = {{vPoints[i].x, vPoints[i].y, 1.0f, 1.0f}, colour, {0, 0}, {0, 0}, {0, 0}, {0, 0}}; - task.tint = tint; - return task; -} - -GPUTask olc::Draw2D::TaskFillPolygon(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) -{ - GPUTask task; - task.structure = structure; - task.vertexBuffer.resize(vPoints.size()); - for (size_t i = 0; i < vPoints.size(); i++) - task.vertexBuffer[i] = { {vPoints[i].x, vPoints[i].y, 1.0f, 1.0f}, vColours[i], {0, 0}, {0, 0}, {0, 0}, {0, 0} }; - task.tint = tint; - return task; -} - -GPUTask olc::Draw2D::TaskFillPolygon(olc::Structure structure, const std::vector& vPoints, const olc::Pixel colour, const olc::Pixel tint) -{ - GPUTask task; - task.structure = structure; - task.vertexBuffer.resize(vPoints.size()); - for (size_t i = 0; i < vPoints.size(); i++) - task.vertexBuffer[i] = { {vPoints[i].x, vPoints[i].y, 1.0f, 1.0f}, colour, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; - task.tint = tint; - 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 task; - task.structure = structure; - task.vertexBuffer.resize(vPoints.size()); - for (size_t i = 0; i& 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; - task.vertexBuffer.resize(vPoints.size()); - for (size_t i = 0; i < vPoints.size(); i++) - task.vertexBuffer[i] = { {vPoints[i].x, vPoints[i].y, vZWs[i].x, vZWs[i].y}, vColours[i], {vTexCoords[i].x, vTexCoords[i].y}, {0, 0}, {0, 0}, {0, 0} }; - task.pImage = image; - task.tint = tint; - return task; -} - - - -const GPUTask& Draw2D::Line(const olc::vf2d& p1, const olc::vf2d& p2, const olc::Pixel col, const olc::Pixel tint) -{ - PrepareTargetForHW(); - - return vecGPUTasks.data.emplace_back(std::move( - TaskDrawLine( - transformAffine.forwardRoundX({ p1, p2 }), - { col,col }, - tint - ))); -} - -const LineBatch& olc::Draw2D::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) -{ - PrepareTargetForHW(); - - return vecGPUTasks.data.emplace_back(std::move( - TaskDrawPolygon( - olc::Structure::Line, - transformAffine.forwardRoundX({ p1, p2 }), - { c1, c2 }, - tint - ))); -} - -const LineBatch& olc::Draw2D::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::Draw2D::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 - - PrepareTargetForHW(); - - return vecGPUTasks.data.emplace_back(std::move( - TaskDrawLine( - transformAffine.forwardRoundX({ - olc::vf2d(pos.x, pos.y), - olc::vf2d(pos.x + size.x, pos.y), - olc::vf2d(pos.x + size.x, pos.y + size.y), - olc::vf2d(pos.x, pos.y + size.y), - olc::vf2d(pos.x, pos.y) - }), - { col, col, col, col, col }, - tint - ))); -} - -const LineBatch& olc::Draw2D::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) -{ - PrepareTargetForHW(); - - return vecGPUTasks.data.emplace_back(std::move( - TaskDrawLine( - transformAffine.forwardRoundX({ - olc::vf2d(pos.x, pos.y), - olc::vf2d(pos.x + size.x, pos.y), - olc::vf2d(pos.x + size.x, pos.y + size.y), - olc::vf2d(pos.x, pos.y + size.y), - olc::vf2d(pos.x, pos.y) - }), - { colTL, colTR, colBR, colBL, colTL }, - tint - ))); -} - -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 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::Draw2D::FilledRect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col, const olc::Pixel tint) -{ - PrepareTargetForHW(); - - return vecGPUTasks.data.emplace_back(std::move( - TaskFillPolygon( - olc::Structure::Fan, - transformAffine.forwardRoundX({ - { pos.x, pos.y }, - { pos.x + size.x, pos.y }, - { pos.x + size.x, pos.y + size.y }, - { pos.x, pos.y + size.y } }), - col, - tint - ))); -} - -const FilledBatch& olc::Draw2D::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) -{ - PrepareTargetForHW(); - return vecGPUTasks.data.emplace_back(std::move( - TaskFillPolygon( - olc::Structure::Fan, - transformAffine.forwardRoundX({ - { pos.x, pos.y }, - { pos.x + size.x, pos.y }, - { pos.x + size.x, pos.y + size.y }, - { pos.x, pos.y + size.y } }), - { colTL, colTR, colBR, colBL }, - tint - ))); -} - -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) -{ - 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) -{ - buffUnitCirclePoints.reserve(nFacets + 1); - for (int32_t i = 0; i <= nFacets; i++) - { - float theta = float(i) / float(nFacets) * 2.0f * 3.14159265358979323846f; - buffUnitCirclePoints.data[i] = { cosf(theta), sinf(theta) }; - } -} - -const GPUTask& olc::Draw2D::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) -{ - 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) -{ - 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) -{ - 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) -{ - 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) -{ - 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) -{ - // Fundamental for outline circle/ellipse with colour solid/gradient - PrepareTargetForHW(); - - if (nFacets != int32_t(buffUnitCirclePoints.data.size() - 1)) - RedefineUnitCircleBuffer(nFacets); - - buffPoints.reserve(nFacets + 1); - buffColours.reserve(nFacets + 1); - - for (int32_t i = 0; i <= nFacets; i++) - { - buffPoints.data[i] = transformAffine.forwardRound({ pos.x + rx * buffUnitCirclePoints.data[i].x, pos.y + ry * buffUnitCirclePoints.data[i].y }); - buffColours.data[i] = col; - } - - return vecGPUTasks.data.emplace_back(std::move( - TaskDrawPolygon( - olc::Structure::Line, - buffPoints.data, - buffColours.data, - tint - ))); -} - -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) -{ - // 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::Draw2D::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) -{ - 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) -{ - // Fundamental for filled circle/ellipse with colour solid/gradient - - PrepareTargetForHW(); - - if (nFacets != int32_t(buffUnitCirclePoints.data.size() - 1)) - RedefineUnitCircleBuffer(nFacets); - - buffPoints.reserve(nFacets + 2); - buffColours.reserve(nFacets + 2); - buffPoints.data[0] = transformAffine.forwardRound(pos); - buffColours.data[0] = colInner; - - for (int32_t i = 0; i <= nFacets; i++) - { - buffPoints.data[i + 1] = transformAffine.forwardRound({ pos.x + rx * buffUnitCirclePoints.data[i].x, pos.y + ry * buffUnitCirclePoints.data[i].y }); - buffColours.data[i + 1] = colOuter; - } - - return vecGPUTasks.data.emplace_back(std::move( - TaskFillPolygon( - olc::Structure::Fan, - buffPoints.data, - buffColours.data, - tint - ))); -} - -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) -{ - // 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::Draw2D::RoundedRect(const olc::vf2d& pos, const olc::vf2d& size, const float& radius, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) -{ - PrepareTargetForHW(); - - 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}); - - - return vecGPUTasks.data.emplace_back(std::move( - TaskDrawPolygon( - olc::Structure::Line, - transformAffine.forwardRound(buffPoints.data), - std::vector(buffPoints.data.size(), col), - tint - ))); -} - -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) -{ - 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::Draw2D::FilledRoundedRect(const olc::vf2d& pos, const olc::vf2d& size, const float& radius, const olc::Pixel col, const olc::Pixel tint, int32_t nFacets) -{ - PrepareTargetForHW(); - - 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 - buffPoints.data.push_back({ adjustedPos.x, adjustedPos.y }); - 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 }); - - return vecGPUTasks.data.emplace_back(std::move( - TaskFillPolygon( - olc::Structure::Fan, - transformAffine.forwardRound(buffPoints.data), - std::vector(buffPoints.data.size(), col), - tint - ))); -} - - - -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) -{ - 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) -{ - 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) -{ - PrepareTargetForHW(); - - return vecGPUTasks.data.emplace_back(std::move( - TaskDrawPolygon( - olc::Structure::Fan, - transformAffine.forwardRoundX({ p1, p2, p3 }), - { c1, c2, c3 }, - tint - ))); -} - -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) -{ - Line(batch, p1, c1, p2, c2); - Line(batch, p2, c2, p3, c3); - Line(batch, p3, c3, p1, c1); - 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) -{ - 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) -{ - 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) -{ - PrepareTargetForHW(); - - return vecGPUTasks.data.emplace_back(std::move( - TaskFillPolygon( - olc::Structure::Fan, - transformAffine.forwardRoundX({ p1, p2, p3 }), - { c1, c2, c3 }, - tint - ))); -} - -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) -{ - 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::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) -{ - PrepareTargetForHW(); - PrepareImageForHW(texture); - - return vecGPUTasks.data.emplace_back(std::move( - TaskTexturedPolygon( - olc::Structure::Fan, - transformAffine.forwardRoundX({ p1, p2, p3 }), - { c1, c2, c3 }, - { t1, t2, t3 }, - &texture, - tint - ))); -} - -const GPUTask& olc::Draw2D::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) -{ - 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::Draw2D::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) -{ - for(size_t i = 0; i& 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) -{ - PrepareTargetForHW(); - - return vecGPUTasks.data.emplace_back(std::move( - TaskDrawPolygon( - structure, - transformAffine.forwardRound(vecPoints), - vecColours, - tint - ))); -} - -const GPUTask& olc::Draw2D::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) -{ - PrepareTargetForHW(); - - return vecGPUTasks.data.emplace_back(std::move( - TaskFillPolygon( - structure, - transformAffine.forwardRound(vecPoints), - vecColours, - tint - ))); -} - -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) -{ - PrepareTargetForHW(); - PrepareImageForHW(texture); - - return vecGPUTasks.data.emplace_back(std::move( - TaskTexturedPolygon( - structure, - transformAffine.forwardRound(vecPoints), - vecColours, - vecTexCoords, - &texture, - tint - ))); -} - -const GPUTask& olc::Draw2D::String(const olc::vf2d& pos, const std::string& text, const olc::Pixel col, const olc::vf2d& scale, olc::Font& font) -{ - PrepareTargetForHW(); - - olc::vf2d spos = { 0.0f, 0.0f }; - - auto task = CreateImageBatch(font.imgFont); - - for (auto c : text) - { - const auto& glyph = font.glyphs[c]; - - if (c == '\n') - { - spos.x = 0; - spos.y += font.fLineHeight * scale.y; - } - else if (c == '\t') - { - spos.x += font.fTabWidth * scale.x; - } - else - { - Draw2D::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; - } - } - - 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) -{ - PrepareTargetForHW(); - - olc::vf2d spos = { 0.0f, 0.0f }; - auto task = CreateImageBatch(font.imgFont); - - for (auto c : text) - { - const auto& glyph = font.glyphs[c]; - - if (c == '\n') - { - spos.x = 0; - spos.y += font.fLineHeight * scale.y; - } - else if (c == '\t') - { - spos.x += font.fTabWidth * scale.x; - } - else - { - Draw2D::Image(task, font.glyphs[c].imgGlyph, pos + spos, scale, col); - spos.x += glyph.vPropSize.x * scale.x; - } - } - - return Batch(task); -} - -olc::vf2d olc::Draw2D::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 }; - - for (auto c : text) - { - const auto& glyph = font.glyphs[c]; - - if (c == '\n') - { - pos.y += font.fLineHeight * scale.y; - pos.x = 0; - } - else if (c == '\t') - { - pos.x += font.fTabWidth * scale.x; - } - else - { - if (bProportional) - pos.x += glyph.vPropSize.x * scale.x; - else - pos.x += glyph.vMonoSize.x * scale.x; - - } - - size = size.max(pos); - } - - return size; -} - -ImageBatch olc::Draw2D::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::ImageBatch& batch, const olc::Pixel tint) -{ - batch.task.tint = tint; - return vecGPUTasks.data.emplace_back(batch.task); -} - -FilledBatch olc::Draw2D::CreateFilledBatch() -{ - FilledBatch b; - b.task.structure = olc::Structure::List; - return b; -} - -const GPUTask& olc::Draw2D::Batch(olc::FilledBatch& batch, const olc::Pixel tint) -{ - batch.task.tint = tint; - return vecGPUTasks.data.emplace_back(batch.task); -} - -LineBatch olc::Draw2D::CreateLineBatch() -{ - LineBatch b; - b.task.structure = olc::Structure::LineList; - return b; -} - -const GPUTask& olc::Draw2D::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) -{ - // 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::Draw2D::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; - - return vecGPUTasks.data.emplace_back(std::move( - TaskTexturedPolygon( - olc::Structure::Fan, - transformAffine.forwardRoundX({ - { pos.x, pos.y }, - { pos.x + size.x, pos.y }, - { pos.x + size.x, pos.y + size.y }, - { pos.x, pos.y + size.y } - }), - { tint, tint, tint, tint }, - // Tex coords are clockwise - { - image.coords[0], - image.coords[1], - image.coords[2], - image.coords[3], - }, - &image.image.get() - ))); - -} - -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) -{ - // Ensure source image is up to date in VRAM - PrepareImageForHW(image.image); - PrepareTargetForHW(); - - 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; - - 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); - - return vecGPUTasks.data.emplace_back(std::move( - TaskTexturedPolygon( - olc::Structure::Fan, - transformAffine.forward(vPoints), - { tint, tint, tint, tint}, - { image.coords[0], image.coords[1], image.coords[2], image.coords[3] }, - &image.image.get() - ))); -} - -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) -{ - // 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::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) -{ - // Ensure source image is up to date in VRAM - PrepareImageForHW(image.image); - PrepareTargetForHW(); - - - 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], - } }; - - return vecGPUTasks.data.emplace_back(std::move( - TaskTexturedPolygon( - olc::Structure::Fan, - transformAffine.forwardRoundX({ vTL, vTR, vBR, vBL }), - { {q[0], 1.0f}, {q[1], 1.0f}, {q[2], 1.0f}, {q[3], 1.0f} }, - { tint, tint, tint, tint}, - { image.coords[0] * q[0], image.coords[1] * q[1], image.coords[2] * q[2], image.coords[3] * q[3] }, - &image.image.get() - ))); - - } - - // Default is just return a textured quad - return vecGPUTasks.data.emplace_back(std::move( - TaskTexturedPolygon( - olc::Structure::Fan, - transformAffine.forwardRoundX({ vTL, vTR, vBR, vBL }), - { tint, tint, tint, tint }, - { image.coords[0], image.coords[1], image.coords[2], image.coords[3] }, - &image.image.get() - ))); -} - -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) -{ - 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 Draw2D::Image(batch, image, vTL, vBR - vTL, tint); -} - -const GPUTask& olc::Draw2D::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) -{ - 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) -{ - // Ensure source image is up to date in VRAM - PrepareImageForHW(image.image); - - PrepareTargetForHW(); - - return vecGPUTasks.data.emplace_back(std::move( - TaskTexturedPolygon( - olc::Structure::Fan, - transformAffine.forwardRoundX({ { pos.x, pos.y }, { pos.x + size.x, pos.y }, { pos.x + size.x, pos.y + size.y }, { pos.x, pos.y + size.y } }), - { tint, tint, tint, tint }, - // Tex coords are clockwise - { image.coords[0], image.coords[1], image.coords[2], image.coords[3] }, - &image.image.get() - ))); -} - -const ImageBatch& olc::Draw2D::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/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/draw_batch.cpp b/dev/src/draw_batch.cpp new file mode 100644 index 00000000..a78148d1 --- /dev/null +++ b/dev/src/draw_batch.cpp @@ -0,0 +1,452 @@ +#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; + +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, const olc::Pixel tint) +{ + 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 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.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 olc::Pixel tint) +{ + 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 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, 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 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, 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 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, 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, const olc::Pixel tint, int32_t 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, const olc::Pixel tint, int32_t 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, const olc::Pixel tint, int32_t 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, const olc::Pixel tint, 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, 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, const olc::Pixel tint, int32_t 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, const olc::Pixel tint, 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, 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, const olc::Pixel tint, 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, 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 olc::Pixel tint) +{ + 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 olc::Pixel tint) +{ + 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 olc::Pixel tint) +{ + 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 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.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 olc::Pixel tint) +{ + for (size_t i = 0; i < vecPoints.size(); i++) + { + 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 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()], tint); + } + + 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]); + } + + default: + break; + } + + 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 + // 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} }); + 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/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/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/gpu_opengl33.cpp b/dev/src/gpu_opengl33.cpp index 492a735a..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,12 +276,13 @@ void main() lastError = RendererError::FailedToSwitchRenderContext; return false; } + #endif #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); @@ -288,7 +314,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 @@ -352,19 +378,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 } @@ -438,7 +469,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_X, 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 +503,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 +848,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_X, + targetSize.x, + targetSize.y + ); + } + else + { + // Allocate regular depth buffer + gl.glRenderbufferStorage( + gl.GL_RENDERBUFFER_X, + gl.GL_DEPTH_COMPONENT24_X, + 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 +1113,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 +1151,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 +1202,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..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 @@ -100,6 +104,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/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/host_apple_macos.cpp b/dev/src/host_apple_macos.cpp index 02b4410a..d9c0e813 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,47 +136,11 @@ namespace olc::host { } - bool Host_Apple_MacOS::StartSystemEventLoop(bool bBlockIfPossible){ - (void)(bBlockIfPossible); // Remove unused variable warning - // Create MacOS Application instance - pMacApplication = std::make_unique(); - - // Set up application delegate event handlers - MacApplicationEventsHandler(); - - // Initialize and activate application first - pMacApplication->initialize(); - pMacApplication->activate(); - - // Initialize the MacOS Window - pMacOSWindow = std::make_unique(frameBounds.width, frameBounds.height, "OLC PGE 3 MacOS Demo"); - pMacOSWindow->setPosition(frameBounds.x, frameBounds.y); - pMacOSWindow->setContentViewPosition(0, 0); - - // Set up window event handlers - MacWindowEventsHandler(); - - // Create Input Event handler - pMacOSEventHandler = std::make_unique(*pMacOSWindow); - - // Setup Event handlers - MacEventsHandler(); - - // Create the window - pMacOSWindow->show(); - pMacOSEventHandler->enable(); - - // Start the main event loop (this will block) - pMacApplication->run(); - - return true; - } - - bool Host_Apple_MacOS::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen){ + 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->SetWindowSize(vWindowSize); pPGEwindow->LinkToHost(this); frameBounds.x = 0.0; @@ -186,10 +151,7 @@ namespace olc::host { return true; } - bool Host_Apple_MacOS::CloseWindowFrame(olc::Window* pWindow){ - if (!pMacOSWindow) return false; - if (!pWindow) return false; pWindow->olc_OnWindowClose(); return true; } @@ -203,8 +165,6 @@ namespace olc::host { } 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 - bSkipFrame = ExecutePendingMainThreadTasks(); // Ensure OpenGL renderer is created if(pMacOSOpenGLRenderer == nullptr) @@ -214,10 +174,6 @@ namespace olc::host { } - bool Host_Apple_MacOS::ConnectHostResourceToRenderer() - { - return false; - } bool Host_Apple_MacOS::SyncWithDesktopComposite() { @@ -236,6 +192,157 @@ namespace olc::host { 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; + } + + 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(); + + // Set up application delegate event handlers + MacApplicationEventsHandler(); + + // Initialize and activate application first + pMacApplication->initialize(); + pMacApplication->activate(); + + // Initialize the MacOS Window + pMacOSWindow = std::make_unique(frameBounds.width, frameBounds.height, "OLC PGE 3 MacOS Demo"); + pMacOSWindow->setPosition(frameBounds.x, frameBounds.y); + pMacOSWindow->setContentViewPosition(0, 0); + + // Set up window event handlers + MacWindowEventsHandler(); + + // Create Input Event handler + pMacOSEventHandler = std::make_unique(*pMacOSWindow); + + // Setup Event handlers + MacEventsHandler(); + + // Create the window + pMacOSWindow->show(); + pMacOSEventHandler->enable(); + + //--- 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; + + // 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; + } + + // 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; + } + }); + + + // 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::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; + } + + bool Host_Apple_MacOS::OnSystemThreadStart() + { + // Hold back threading until application is fully initialized + bSkipFrame = ExecutePendingMainThreadTasks(); + return pPrimaryPGE->OnContextStart(); + } + + bool Host_Apple_MacOS::OnSystemTick() + { + // Execute any pending main thread tasks + bSkipFrame = ExecutePendingMainThreadTasks(); + return pPrimaryPGE->OnContextTick(); + } + + bool Host_Apple_MacOS::OnSystemThreadEnd() + { + return pPrimaryPGE->OnContextEnd(); + } + + bool Host_Apple_MacOS::OnApplicationEnd() + { + return true; + } + + +//-- OS Window Event Handling ----- + olc::KeyboardLayout Host_Apple_MacOS::GetKeyboardLayout() const { // Get system locale from MacOS Application @@ -377,10 +484,18 @@ namespace olc::host { res = true; // Skip frame to allow resize to take effect break; } - case MINIMIZE_WINDOW: case DEMINIMIZE_WINDOW: case BECOME_ACTIVE: + { + pPGEwindow->olc_OnFocus(true); + break; + } + case MINIMIZE_WINDOW: case RESIGN_ACTIVE: + { + pPGEwindow->olc_OnFocus(false); + break; + } case NONE: default: { @@ -415,9 +530,7 @@ namespace olc::host { pPGEwindow->keyboard.UseKeyboardLayout(GetKeyboardLayout()); }); - pMacApplication->setWillTerminateCallback([&]() { - // TODO: Johnngy63 - Implement olc_OnDestory in window.h/cpp - }); + pMacApplication->setWillTerminateCallback([&]() { }); pMacApplication->setDidBecomeActiveCallback([]() { }); @@ -432,18 +545,17 @@ namespace olc::host { }); pMacOSWindow->setWindowWillCloseCallback([&]() { - // TODO: Johnngy63 - Implement any pre-close logic if needed + // 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([&]() { @@ -455,52 +567,39 @@ namespace olc::host { }); } - - bool Host_Apple_MacOS::ModifiersFlagsHandler(const olc::apis::macos::KeyEvent& event, bool pressed) { + + // 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; - bool bisHandled = false; - if (event.modifierFlags & NSEventModifierFlagCapsLock) { - pPGEwindow->olc_OnKeyPress(Key::CAPS_LOCK, pressed); + // handle num clear/lock key only on the down stroke. + if(isPressed && keyCode == 71) + { + bNumLockActive = !bNumLockActive; + return; } - if (event.modifierFlags & NSEventModifierFlagShift) { - pPGEwindow->olc_OnKeyPress(Key::SHIFT, pressed); - if(event.keyCode == 39) + + if(!bNumLockActive) + { + // 84 down, 86 left, 88 right, 91 up >>> 125 down, 123 left, 124 right, 126 up + switch(keyCode) { - // 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; + case 84: keyCode = 125; break; + case 86: keyCode = 123; break; + case 88: keyCode = 124; break; + case 91: keyCode = 126; break; + default: break; } - - } - if (event.modifierFlags & NSEventModifierFlagControl) { - pPGEwindow->olc_OnKeyPress(Key::CTRL, pressed); } - - if(event.modifierFlags & NSEventModifierFlagNumericPad) { - if(event.keyCode == 71 && pressed) // NumLock keycode - { - // We only tottle the NumLock state on key press to minic the latching of the key - bNumLockActive = !bNumLockActive; - } - - 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; - } - - } - - return bisHandled; + // 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() { // General MacOS key event handling code here @@ -508,16 +607,43 @@ namespace olc::host { // Set up keyboard event handlers pMacOSEventHandler->onKeyDown([&](const olc::apis::macos::KeyEvent& event) { - if(!ModifiersFlagsHandler(event, true)) - pPGEwindow->olc_OnKeyPress(mapKeys[event.keyCode], true); + KeyboardEventHandler(event, true); }); - + pMacOSEventHandler->onKeyUp([&](const olc::apis::macos::KeyEvent& event) { - if(!ModifiersFlagsHandler(event, false)) - pPGEwindow->olc_OnKeyPress(mapKeys[event.keyCode], false); + KeyboardEventHandler(event, false); + }); + + // Set up keyboard flag event handlers + pMacOSEventHandler->onFlagsChanged([&](const olc::apis::macos::FlagsChangedEvent& event) { + 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); + } + + 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; }); - + // Set up mouse event handlers pMacOSEventHandler->onMouseDown([&](const olc::apis::macos::MouseEvent& event) { pPGEwindow->olc_OnMouseButton(event.buttonNumber, true); @@ -528,7 +654,7 @@ namespace olc::host { }); 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 70578d93..15d75c09 100644 --- a/dev/src/host_apple_macos.h +++ b/dev/src/host_apple_macos.h @@ -48,20 +48,41 @@ 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; + + 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; @@ -108,6 +129,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 { @@ -121,12 +144,8 @@ 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); - - bool bNumLockActive = true; // Num Lock state, we assume it's active at start + 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/dev/src/host_iface.h b/dev/src/host_iface.h index 1461eaaf..9eb00a52 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 constexpr 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; @@ -94,6 +67,14 @@ 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; + // 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 dd011154..cf974aac 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; @@ -321,6 +322,49 @@ 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) + { + 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; + } + + 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) { @@ -339,6 +383,12 @@ 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)); + } + 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) @@ -350,11 +400,13 @@ 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); + cursor_shape_device = wp_cursor_shape_manager_v1_get_pointer(cursor_shape_manager, pointer); + wl_pointer_add_listener(pointer, &wayland::pointer_listener, this); } 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); } } @@ -366,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}); @@ -437,6 +491,9 @@ 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 = surface; pointer_state.surface_x = surface_x; pointer_state.surface_y = surface_y; } @@ -505,16 +562,25 @@ 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; + + // 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); + } } } } - - 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{ @@ -528,6 +594,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; } } @@ -590,7 +658,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) { @@ -636,7 +704,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_OnFocus(true); } void Host_Linux_Wayland::keyboard_leave_callback(void* data, wl_keyboard* keyboard, uint32_t serial, wl_surface* surface) @@ -647,7 +723,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_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) @@ -663,7 +740,27 @@ 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); + + // 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..29132e02 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 @@ -18,6 +20,8 @@ #include #include "xdg-shell.h" #include "xdg-decoration.h" +#include "pointer-warp.h" +#include "cursor-shape.h" #include #include #include @@ -27,6 +31,10 @@ #include #include +#ifndef WL_KEYBOARD_KEY_STATE_REPEATED +#define WL_KEYBOARD_KEY_STATE_REPEATED 2 +#endif + namespace olc::host { struct WaylandWindow { @@ -38,6 +46,9 @@ namespace olc::host size_t olc_window_uid{0}; 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 { @@ -82,12 +93,17 @@ 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; 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}; + wp_cursor_shape_device_v1* cursor_shape_device{nullptr}; + wp_cursor_shape_manager_v1* cursor_shape_manager{nullptr}; wayland::PointerState pointer_state; @@ -108,6 +124,9 @@ 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; + 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 5637473e..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; @@ -71,6 +72,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 +190,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) @@ -215,14 +225,19 @@ 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.xfocus.window); pge_window) { + 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_OnFocus(false); + } + } else if (xev.type == ClientMessage) { X11::XClientMessageEvent& xcme = xev.xclient; @@ -270,7 +285,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; @@ -296,13 +311,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); } @@ -391,5 +419,81 @@ namespace olc::host { return true; } + + bool Host_Linux_X11::SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) + { + 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; + + 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; + } + + 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 f2f070f8..318d0cd4 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 @@ -19,7 +20,10 @@ namespace X11 #include #include #include +#include #include +#undef None +constexpr int None = 0L; } namespace olc::host @@ -47,6 +51,12 @@ 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; + bool SetFullScreen(olc::Window* pWindow, const bool bFullScreen) override; + public: bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; bool StartSystem() override; @@ -62,12 +72,17 @@ 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; bool kbExtensionsFound = false; int xkbEventBase = 0; int xkbErrorBase = 0; + + // Mouse Variables + std::unordered_map mapUID2X11Cursor; + bool bMouseIsVisible = true; }; } diff --git a/dev/src/host_none.cpp b/dev/src/host_none.cpp new file mode 100644 index 00000000..0e28f2f4 --- /dev/null +++ b/dev/src/host_none.cpp @@ -0,0 +1,115 @@ +#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) + { + 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/host_web_emscripten.cpp b/dev/src/host_web_emscripten.cpp index f05eff90..eaeee040 100644 --- a/dev/src/host_web_emscripten.cpp +++ b/dev/src/host_web_emscripten.cpp @@ -7,121 +7,45 @@ 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) + Host_Web_Emscripten::Host_Web_Emscripten() { - auto pHost = reinterpret_cast(userData); - - if(!pHost->OnSystemTick()) + 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); }); + + 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") { - pHost->StopSystem(); + keyboardLayout = KeyboardLayout::QWERTZ; } - } - - // 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()) + else if(locale == "fr-ca") { - // PGE->ContextStart() failed, or user aborted OnUserCreate() - return false; + 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; } - - 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"; - - // 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; - } - - }); - }); // Map Emscripten Defined DOM_PK_ Codes to olc::KeyCodes mapKeys[DOM_PK_UNKNOWN] = Key::NONE; @@ -237,6 +161,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) @@ -286,12 +218,150 @@ 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)); 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 keyboardLayout; + } + + // 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) @@ -357,29 +427,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) { @@ -387,39 +434,51 @@ 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); + // middle/next/back buttons require the event to be consumed to prevent browser behavior + if(it->second >= 2) return EM_TRUE; + } + } + break; + case EMSCRIPTEN_EVENT_MOUSEUP: + { + auto it = pCallbackData->pHost->mapMouseButtons.find(e->button); + 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; + } + } + break; + default: break; } - + 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) @@ -450,24 +509,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) - { - // ptrPGE->olc_UpdateKeyFocus(false); - olc_OnMouseFocus(pCallbackData->pWindow, false); - } - else if (eventType == EMSCRIPTEN_EVENT_FOCUS) - { - // ptrPGE->olc_UpdateKeyFocus(true); - olc_OnMouseFocus(pCallbackData->pWindow, true); - } - - return 0; - } //TY Moros EM_BOOL Host_Web_Emscripten::fullscreen_change_callback(int eventType, const EmscriptenFullscreenChangeEvent *event, void *userData) @@ -518,39 +559,40 @@ namespace olc::host return 0; } - bool Host_Web_Emscripten::CloseWindowFrame(olc::Window* pWindow) + //TY Gorbit + EM_BOOL Host_Web_Emscripten::focus_callback(int eventType, const EmscriptenFocusEvent* focusEvent, void* userData) { - std::cout << "Emscripten: CloseWindowFrame not implemented.\n"; - return true; - } + CallbackData* pCallbackData = reinterpret_cast(userData); + + if (eventType == EMSCRIPTEN_EVENT_BLUR) + { + olc_OnFocus(pCallbackData->pWindow, false); + } + else if (eventType == EMSCRIPTEN_EVENT_FOCUS) + { + olc_OnFocus(pCallbackData->pWindow, true); + } - bool Host_Web_Emscripten::UpdateWindowFrameTitle(olc::Window* pWindow) - { - // not implemented for emscripten platform - return true; + return 0; } - std::vector Host_Web_Emscripten::GetHostWindowDescriptor(olc::Window* pWindow) + EM_BOOL Host_Web_Emscripten::visibility_callback(int eventType, const EmscriptenVisibilityChangeEvent *visibilityChangeEvent, void *userData) { - const auto window_handle = mapUID2CanvasId.find(pWindow->GetUID()); - if(window_handle != mapUID2CanvasId.end()) + CallbackData *pCallbackData = reinterpret_cast(userData); + + if (visibilityChangeEvent->hidden) { - return { (void*)(window_handle->second.c_str()) }; + 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; } - - 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) @@ -568,9 +610,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) @@ -593,6 +635,18 @@ 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 f16ea1a9..47a12351 100644 --- a/dev/src/host_web_emscripten.h +++ b/dev/src/host_web_emscripten.h @@ -1,8 +1,11 @@ #pragma once +#include "core.h" //! START STDHEADER GLOBAL #include +#include #include +#include #include #include //! END STDHEADER @@ -22,16 +25,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,19 +67,20 @@ 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); 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); 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); @@ -74,9 +90,9 @@ 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); - + private: // Emscripten internal funcs + std::string getNavigatorLocale(); + public: // Callback data type struct CallbackData { Host_Web_Emscripten* pHost; @@ -91,6 +107,10 @@ 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; + std::chrono::steady_clock::time_point timeHidden; + olc::KeyboardLayout keyboardLayout{OLC_DEFAULT_KEYBOARD_LAYOUT}; }; diff --git a/dev/src/host_win_winapi.cpp b/dev/src/host_win_winapi.cpp index b233244b..3fe19d1a 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; @@ -321,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); @@ -332,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); @@ -371,6 +373,56 @@ 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; + } + + bool Host_Windows_WinAPI::SetFullScreen(olc::Window* pWindow, const bool bFullScreen) + { + HWND hWnd = mapUID2HWND.at(pWindow->GetUID()); + + 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) { if (!mapHWND2PTR.contains(hWnd)) @@ -409,12 +461,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); @@ -479,6 +553,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... @@ -524,6 +626,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..7138fac7 100644 --- a/dev/src/host_win_winapi.h +++ b/dev/src/host_win_winapi.h @@ -74,6 +74,11 @@ 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; + bool SetFullScreen(olc::Window* pWindow, const bool bFullScreen) override; + public: // OS Specific Environment Information olc::KeyboardLayout GetKeyboardLayout() const override; @@ -100,6 +105,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/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/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/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/dev/src/imload_stb_image.cpp b/dev/src/imload_stb_image.cpp new file mode 100644 index 00000000..39ff2baa --- /dev/null +++ b/dev/src/imload_stb_image.cpp @@ -0,0 +1,89 @@ +#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) + { + 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) + { + 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) + { + 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..7f073d01 --- /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_STB_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_STB_DECLARED 1 +#endif +//! END DECLARATION 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 diff --git a/dev/src/matrix3d.h b/dev/src/matrix3d.h index 1d90418b..aac67835 100644 --- a/dev/src/matrix3d.h +++ b/dev/src/matrix3d.h @@ -5,7 +5,8 @@ #include #include #include -#include +#include +#include //! END STDHEADER //! START CUSTOMHEADER @@ -81,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); @@ -109,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) @@ -126,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) @@ -138,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) @@ -155,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 @@ -164,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; @@ -201,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/matrix4d.h b/dev/src/matrix4d.h new file mode 100644 index 00000000..0f859e4d --- /dev/null +++ b/dev/src/matrix4d.h @@ -0,0 +1,368 @@ +#pragma once + +//! START STDHEADER GLOBAL +#include +#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..216b7c21 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" @@ -20,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/src/sh_template.h b/dev/src/sh_template.h index 40ca4410..96dc33e2 100644 --- a/dev/src/sh_template.h +++ b/dev/src/sh_template.h @@ -94,12 +94,15 @@ Primary Contributors ~~~~~~~~~~~~~~~~~~~~ @javidx9 (aka David Barr, OneLoneCoder) - @Moros1198, @dandistine, @johnnyg63, @iCiaran + @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, 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 @@ -128,7 +135,7 @@ //! GRAB gpu_iface.h DECLARATION -//! GRAB draw2d.h DECLARATION +//! GRAB draw.h DECLARATION //! GRAB hw_input.h DECLARATION @@ -140,6 +147,8 @@ //! GRAB host_iface.h DECLARATION +//! GRAB extension.h DECLARATION + //! GRAB core.h DECLARATION @@ -150,6 +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 @@ -191,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 @@ -213,17 +232,20 @@ //! 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) +#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 @@ -254,18 +276,24 @@ #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 #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 +#if defined(OLC_PGE3_APPLICATION) && !defined(PGE_DRAW_IMPLEMENTED) +//! GRAB draw.cpp IMPLEMENTATION +//! GRAB draw_batch.cpp IMPLEMENTATION +#define PGE_DRAW_IMPLEMENTED 1 #endif #if defined(OLC_PGE3_APPLICATION) && !defined(PGE_CORE_IMPLEMENTED) @@ -308,9 +336,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/dev/src/transform2d.h b/dev/src/transform2d.h index 6dfeec74..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_mRotate * m_mShear * m_mScale * 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/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/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/src/window.cpp b/dev/src/window.cpp index 2145b829..b8e7c728 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; } @@ -122,5 +122,25 @@ 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); + } + + void Window::ShowFullScreen(const bool bFullScreen) + { + pHost->SetFullScreen(this, bFullScreen); + } + + 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 2e500546..bb35f5ac 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 @@ -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); @@ -90,9 +90,22 @@ 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); + + // Set the Window to be FullScreen or Not Fullscreen + void ShowFullScreen(const bool bFullScreen); + + // Focus + bool IsFocused() const; + protected: bool bRequestToClose = false; bool bShouldRemove = false; + bool bWindowIsFocused = false; protected: size_t nUniqueID = size_t(-1); diff --git a/dev/tests/CMakeLists.txt b/dev/tests/CMakeLists.txt index 4d2ba57d..20a76baf 100644 --- a/dev/tests/CMakeLists.txt +++ b/dev/tests/CMakeLists.txt @@ -1,22 +1,340 @@ -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) +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 +) + +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 +) +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 + ${GPU_HEADLESS_SOURCE_FILES} + ${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} + ${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(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() + 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 + + 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 +endif() + +# 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() + 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() + + 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} ${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 + 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}) diff --git a/dev/tests/blend.png b/dev/tests/blend.png deleted file mode 100644 index 912ff431..00000000 Binary files a/dev/tests/blend.png and /dev/null differ diff --git a/dev/tests/olc.png b/dev/tests/olc.png deleted file mode 100644 index fb4a6a39..00000000 Binary files a/dev/tests/olc.png and /dev/null differ 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 b1b4c299..9ab64439 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; } @@ -34,7 +34,9 @@ class Example : public olc::PixelGameEngine public: Example() { - + // 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; @@ -125,7 +127,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; } @@ -149,16 +151,106 @@ 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 (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; } @@ -247,7 +339,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], @@ -266,7 +358,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 +373,79 @@ class Example : public olc::PixelGameEngine 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 @@ -621,7 +786,9 @@ int main() cfg.vPixelSize = { 4,4 }; 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/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/dev/xcode_macos/olcPGE3.xcodeproj/project.pbxproj b/dev/xcode_macos/olcPGE3.xcodeproj/project.pbxproj index 3e092141..904e7939 100644 --- a/dev/xcode_macos/olcPGE3.xcodeproj/project.pbxproj +++ b/dev/xcode_macos/olcPGE3.xcodeproj/project.pbxproj @@ -8,10 +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 */; }; + 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 */; }; @@ -26,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 */ @@ -47,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 = ""; }; @@ -66,10 +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 = ""; }; + 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 = ""; }; @@ -88,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 = ""; }; @@ -152,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; @@ -166,8 +161,13 @@ 8998CE322E057F92007DDD96 /* src */ = { isa = PBXGroup; children = ( + 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 */, @@ -179,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 */, @@ -272,10 +270,9 @@ buildActionMask = 2147483647; files = ( 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 */, @@ -286,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; }; diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 09920f29..4f010482 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 @@ -32,61 +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") + 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() - 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() + set(C_FILENAME "${CMAKE_CURRENT_BINARY_DIR}/${PROTOCOL_TARGET}.c") + set(H_FILENAME "${CMAKE_CURRENT_BINARY_DIR}/${PROTOCOL_TARGET}.h") - 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() + add_custom_command( + OUTPUT ${C_FILENAME} + COMMAND ${WAYLAND_SCANNER} private-code ${PROTOCOL_FILE} ${C_FILENAME} + DEPENDS ${PROTOCOL_FILE} + VERBATIM + ) - # 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 ${H_FILENAME} + COMMAND ${WAYLAND_SCANNER} client-header ${PROTOCOL_FILE} ${H_FILENAME} + DEPENDS ${PROTOCOL_FILE} + VERBATIM + ) - add_custom_command( - OUTPUT ${XDG_SHELL_H} - COMMAND ${WAYLAND_SCANNER} client-header ${XDG_SHELL_PROTOCOL} ${XDG_SHELL_H} - DEPENDS ${XDG_SHELL_PROTOCOL} - VERBATIM - ) + set_source_files_properties(${C_FILENAME} ${H_FILENAME} PROPERTIES GENERATED TRUE) - add_custom_command( - OUTPUT ${XDG_DECORATION_C} - COMMAND ${WAYLAND_SCANNER} private-code ${XDG_DECORATION_PROTOCOL} ${XDG_DECORATION_C} - DEPENDS ${XDG_DECORATION_PROTOCOL} - VERBATIM - ) + add_library(wayland_${PROTOCOL_TARGET} STATIC ${C_FILENAME} ${H_FILENAME}) + endfunction() - add_custom_command( - OUTPUT ${XDG_DECORATION_H} - COMMAND ${WAYLAND_SCANNER} client-header ${XDG_DECORATION_PROTOCOL} ${XDG_DECORATION_H} - DEPENDS ${XDG_DECORATION_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} + 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() @@ -95,6 +85,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") diff --git a/examples/assets/graph_paper.png b/examples/assets/graph_paper.png new file mode 100644 index 00000000..d63dfdf6 Binary files /dev/null and b/examples/assets/graph_paper.png differ diff --git a/examples/assets/pixelfish.png b/examples/assets/pixelfish.png new file mode 100644 index 00000000..4bb811ac Binary files /dev/null and b/examples/assets/pixelfish.png differ diff --git a/examples/olcPGE3_3DCube.cpp b/examples/olcPGE3_3DCube.cpp new file mode 100644 index 00000000..a51cdbc3 --- /dev/null +++ b/examples/olcPGE3_3DCube.cpp @@ -0,0 +1,232 @@ +/* + 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); + draw.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; + 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 + 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; + 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. + draw.SetCullMode(olc::GPUTask::CullMode::CounterClockWise); + + // Draw the cube using the sanity cube's layout, and vectors of vertices, colours + // and texture coordinates. + draw.Mesh(meshSanityCube.layout, meshSanityCube.pos, meshSanityCube.col, meshSanityCube.uv, imSanityCube); + + // Draw a little RGB axis indicator + matWorld.translate(-1,-1,-1); + 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); + + // 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 = true; + + + // 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; + config.sAppName = "Example - 3D Sanity Cube"; + + if (demo.Construct(config)) + { + // Start the application + demo.Start(); + } + + return 0; +} \ No newline at end of file diff --git a/examples/olcPGE3_AntiAliasing.cpp b/examples/olcPGE3_AntiAliasing.cpp index 9202a595..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: @@ -34,7 +35,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 @@ -44,20 +45,16 @@ 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 = [&]() { // 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 +101,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..8efe6ea5 100644 --- a/examples/olcPGE3_BatchesOfFills.cpp +++ b/examples/olcPGE3_BatchesOfFills.cpp @@ -20,13 +20,10 @@ class Example_BatchesOfFills : public olc::PixelGameEngine public: Example_BatchesOfFills() { - + sAppName = "Example - Batches of Fills"; } protected: - // We accumulate total time for some animation - float fTotalTime = 0.0f; - std::vector vecBubblePos; std::vector vecBubbleVel; std::vector vecBubbleCol; @@ -39,8 +36,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, @@ -79,7 +76,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 +92,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 @@ -118,22 +115,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_BatchesOfLines.cpp b/examples/olcPGE3_BatchesOfLines.cpp index 7ed23c51..9a169e98 100644 --- a/examples/olcPGE3_BatchesOfLines.cpp +++ b/examples/olcPGE3_BatchesOfLines.cpp @@ -20,13 +20,9 @@ class Example_BatchesOfLines : public olc::PixelGameEngine public: Example_BatchesOfLines() { - + sAppName = "Example - Batches of Lines"; } -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_Camera2D_Tiles.cpp b/examples/olcPGE3_Camera2D_Tiles.cpp new file mode 100644 index 00000000..cb1467fd --- /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() + { + sAppName = "Example Camera2D - Tiles, Panning and Zooming"; + } + +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/examples/olcPGE3_CirclesEllipses.cpp b/examples/olcPGE3_CirclesEllipses.cpp index eada4595..51a6fabf 100644 --- a/examples/olcPGE3_CirclesEllipses.cpp +++ b/examples/olcPGE3_CirclesEllipses.cpp @@ -20,13 +20,10 @@ class Example_RoundThings : public olc::PixelGameEngine public: Example_RoundThings() { - + sAppName = "Example - Circles and Ellipses"; } protected: - // We accumulate total time for some animation - float fTotalTime = 0.0f; - std::vector vecBubblePos; std::vector vecBubbleVel; std::vector vecBubbleCol; @@ -39,8 +36,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 +109,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_Extensions.cpp b/examples/olcPGE3_Extensions.cpp new file mode 100644 index 00000000..582e5633 --- /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() + { + // 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); + + sAppName = "Example - Extensions"; + } + +protected: + + // We need to create an instance of our extension class, and then install it in the PGE constructor + ExamplePGEX pgex; + +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::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... + float fTotalTime = TotalTimeElapsed(); + 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/examples/olcPGE3_Fish.cpp b/examples/olcPGE3_Fish.cpp new file mode 100644 index 00000000..3168d6ca --- /dev/null +++ b/examples/olcPGE3_Fish.cpp @@ -0,0 +1,450 @@ +/* + 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::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; + + 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::Draw& 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::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]; + 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::Draw& 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::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}; + 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().translation(); + 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() + { + sAppName = "Example - Fish... lots of really cool fish"; + } + +protected: + Fish fish{olc::vf2d(128.0f, 120.0f), 40, 80.0f, 1.0f}; + + std::vector others; + std::vector targets; + bool fullscreen = false; +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}; + 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); + + // 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 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/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 new file mode 100644 index 00000000..7cf23046 --- /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() + { + sAppName = "Example - ImageRotated (and scaled)"; + } + +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/examples/olcPGE3_ImageTiling.cpp b/examples/olcPGE3_ImageTiling.cpp index f42840df..d33cb409 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: @@ -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_Keyboard.cpp b/examples/olcPGE3_Keyboard.cpp index d8467dfb..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: @@ -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(); @@ -58,7 +66,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 diff --git a/examples/olcPGE3_Lines.cpp b/examples/olcPGE3_Lines.cpp index 43218cc7..0408ea23 100644 --- a/examples/olcPGE3_Lines.cpp +++ b/examples/olcPGE3_Lines.cpp @@ -20,13 +20,9 @@ class Example_Lines : public olc::PixelGameEngine public: Example_Lines() { - + sAppName = "Example - Lines"; } -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_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_Mouse.cpp b/examples/olcPGE3_Mouse.cpp new file mode 100644 index 00000000..b47be4b4 --- /dev/null +++ b/examples/olcPGE3_Mouse.cpp @@ -0,0 +1,205 @@ +/* + 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() + { + sAppName = "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 + + 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, scrollTracker); + + // 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); + + if(mouse.GetButton(3).bPressed) + SpawnParticles(mousePos, olc::Colour::MAGENTA, 20); + + if(mouse.GetButton(4).bPressed) + SpawnParticles(mousePos, olc::Colour::TANGERINE, 20); + + // Update and Draw Particles + for(auto &p : vecParticles) + { + p.life -= fElapsedTime; + p.pos += p.vel * fElapsedTime; + 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; + 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); + + if(mouse.GetButton(3).bHeld) + draw.Circle(mousePos, 26, olc::Colour::MAGENTA); + + 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; + } + + 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_v; + 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 scrollTracker = 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 = false; + 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 diff --git a/examples/olcPGE3_PixelShaders.cpp b/examples/olcPGE3_PixelShaders.cpp index 8d5a66d2..cbc8dbf0 100644 --- a/examples/olcPGE3_PixelShaders.cpp +++ b/examples/olcPGE3_PixelShaders.cpp @@ -30,13 +30,10 @@ class Example_CustomPixelShader : public olc::PixelGameEngine public: Example_CustomPixelShader() { - + sAppName = "Example - Custom Pixel Shader"; } protected: - // We accumulate total time for some animation - float fTotalTime = 0.0f; - olc::gpu::Shader_GLSL33 shaderExample; olc::Image imgWithoutFX; @@ -133,7 +130,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"); @@ -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; @@ -184,7 +181,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_Pixels.cpp b/examples/olcPGE3_Pixels.cpp index 5eab4bd7..25ae3d7e 100644 --- a/examples/olcPGE3_Pixels.cpp +++ b/examples/olcPGE3_Pixels.cpp @@ -20,13 +20,9 @@ class Example_Pixels : public olc::PixelGameEngine public: Example_Pixels() { - + sAppName = "Example - Pixels ...lots of pixels"; } -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..7f291a08 100644 --- a/examples/olcPGE3_Polygons.cpp +++ b/examples/olcPGE3_Polygons.cpp @@ -20,13 +20,10 @@ class Example_Pixels : public olc::PixelGameEngine public: Example_Pixels() { - + sAppName = "Example - Polygons"; } 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..34ac72cc 100644 --- a/examples/olcPGE3_Rectangles.cpp +++ b/examples/olcPGE3_Rectangles.cpp @@ -20,13 +20,9 @@ class Example_Rectangles : public olc::PixelGameEngine public: Example_Rectangles() { - + sAppName = "Example - Rectangles"; } -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 255dfc58..d2819e1c 100644 --- a/examples/olcPGE3_Shockwave.cpp +++ b/examples/olcPGE3_Shockwave.cpp @@ -29,13 +29,10 @@ class Example_Shockwave : public olc::PixelGameEngine public: Example_Shockwave() { - + sAppName = "Example - Shockwave Pixel Shader"; } protected: - // We accumulate total time for some animation - float fTotalTime = 0.0f; - olc::gpu::Shader_GLSL33 shaderExample; olc::Image imgWithoutFX; @@ -121,7 +118,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 +153,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_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; +} diff --git a/examples/olcPGE3_TextBasics.cpp b/examples/olcPGE3_TextBasics.cpp index 9ede7bca..05ced303 100644 --- a/examples/olcPGE3_TextBasics.cpp +++ b/examples/olcPGE3_TextBasics.cpp @@ -20,12 +20,10 @@ class Example_Text : public olc::PixelGameEngine public: Example_Text() { - + sAppName = "Example - Text Basics"; } 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), @@ -58,7 +56,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/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 new file mode 100644 index 00000000..699b6bc6 --- /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() + { + sAppName = "Example - World Transformations"; + } + +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.WorldScale(vWorldScale); + draw.WorldRotate(fWorldRotation); + draw.WorldOffset(vWorldOffset); + 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 4e5930ee..3b6b07c5 100644 --- a/olcPixelGameEngine3.h +++ b/olcPixelGameEngine3.h @@ -94,12 +94,15 @@ Primary Contributors ~~~~~~~~~~~~~~~~~~~~ @javidx9 (aka David Barr, OneLoneCoder) - @Moros1198, @dandistine, @johnnyg63, @iCiaran + @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, 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... @@ -115,6 +118,7 @@ #include #include #include +#include #include #include #include @@ -160,13 +164,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) @@ -201,42 +206,69 @@ #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 #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 + #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 +// 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 @@ -266,6 +298,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 @@ -911,6 +947,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) @@ -941,260 +984,1015 @@ namespace olc #define PGE_VECTOR2D_DECLARED 1 #endif -#if !defined(PGE_MATRIX3D_DECLARED) +#if !defined(PGE_VECTOR4D_DECLARED) namespace olc { - /* - A complete 3x3 Matrix structure, with a variety - of useful utility functions and operator overloads - specifically targeting 2D graphical transformations - - as per https://en.wikipedia.org/wiki/Affine_transformation - - Access: column, row + A complete 4D geometric vector structure, with a variety + of useful utility functions and operator overloads. */ template - struct m_3d + struct v_4d { - static_assert(std::is_arithmetic::value, "olc::m_2d must be numeric"); + static_assert(std::is_arithmetic::value, "olc::v_4d must be numeric"); - // The 3x3 elements! - std::array m{ {0 } }; - - // Constructor created identity matrix - inline constexpr m_3d() + union { - identity(); - } +#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 m_3d(const m_3d& mat) = default; + inline constexpr v_4d(const v_4d& v) = default; // Assignment operator - inline constexpr m_3d& operator=(const m_3d& mat) = default; + inline constexpr v_4d& operator=(const v_4d& v) = default; - // Retrieve a specific element's 1D index - inline constexpr size_t idx(const size_t c, const size_t r) const + + inline constexpr std::array a() const { - return r * 3 + c; + return xyzw; } - // Retrieve non-const access to specific element - inline constexpr T& operator()(const size_t col, const size_t row) + inline constexpr v_2d xy() const { - return m[idx(col, row)]; + return v_2d(x, y); } - // Retrieve const access to specific element - inline constexpr const T& operator()(const size_t col, const size_t row) const + inline constexpr v_2d zw() const { - return m[idx(col, row)]; + return v_2d(z, w); } - // Set all elements to 0 - inline constexpr void clear() + // Returns magnitude of vector + inline constexpr auto mag() const { - std::fill(m.begin(), m.end(), T(0)); + return std::sqrt(x * x + y * y + z * z + w * w); } - // Create identity matrix - inline constexpr void identity() + // Returns magnitude squared of vector (useful for fast comparisons) + inline constexpr T mag2() const { - clear(); - auto& me = (*this); - me(0, 0) = 1; - me(1, 1) = 1; - me(2, 2) = 1; + return x * x + y * y + z * z + w * w; } - inline constexpr std::array m4x4() + // Returns normalised version of vector + inline constexpr v_4d norm() const { - auto& me = (*this); - return { - me(0,0), me(1,0), me(2,0), 0, - me(0,1), me(1,1), me(2,1), 0, - me(0,2), me(1,2), me(2,2), 0, - 0, 0, 0, 1 - }; + auto r = 1 / mag(); + return v_4d(x * r, y * r, z * r, w * r); } - // Create translation matrix via components - template - inline constexpr void translate(const Q x, const Q y) + // Rounds all components down + inline constexpr v_4d floor() const { - identity(); - auto& me = (*this); - me(2, 0) = T(x); - me(2, 1) = T(y); + return v_4d(std::floor(x), std::floor(y), std::floor(z), std::floor(w)); } - // Create translation matrix via vector - template - inline constexpr void translate(const olc::v_2d& v) + // Rounds all components accurately + inline constexpr v_4d round() const { - translate(v.x, v.y); + return v_4d(std::round(x), std::round(y), std::round(z), std::round(w)); } - // Create scaling matrix via components - template - inline constexpr void scale(const Q x, const Q y) + // Rounds all components up + inline constexpr v_4d ceil() const { - identity(); - auto& me = (*this); - me(0, 0) = T(x); - me(1, 1) = T(y); + return v_4d(std::ceil(x), std::ceil(y), std::ceil(z), std::ceil(w)); } - // Create scaling matrix via vector - template - inline constexpr void scale(const olc::v_2d& v) + // Returns 'element-wise' max of this and another vector + inline constexpr v_4d max(const v_4d& v) const { - scale(v.x, v.y); + return v_4d(std::max(x, v.x), std::max(y, v.y), std::max(z, v.z), std::max(w, v.w)); } - // Create rotation matrix with radians - template - inline constexpr void rotate(const Q rads) + // Returns 'element-wise' min of this and another vector + inline constexpr v_4d min(const v_4d& v) const { - identity(); - auto& me = (*this); - me(0, 0) = std::cos(rads); - me(0, 1) = std::sin(rads); - me(1, 0) = -me(0, 1); - me(1, 1) = me(0, 0); + return v_4d(std::min(x, v.x), std::min(y, v.y), std::min(z, v.z), std::min(w, v.w)); } - // Create shearing matrix via components - template - inline constexpr void shear(const Q x, const Q y) + // Returns 'element-wise' abs of this vector + inline constexpr v_4d abs() const { - identity(); - auto& me = (*this); - me(0, 1) = T(y); - me(1, 0) = T(x); + return v_4d(std::abs(x), std::abs(y), std::abs(z), std::abs(w)); } - // Create shearing matrix via vector - template - inline constexpr void shear(const olc::v_2d& v) + // Calculates scalar dot product between this and another vector + inline constexpr auto dot(const v_4d& rhs) const { - shear(v.x, v.y); + return this->x * rhs.x + this->y * rhs.y + this->z * rhs.z + this->w * rhs.w; } - - // Return inverted matrix - inline constexpr auto invert() const + // Calculates cross product between this and another vector + inline constexpr v_4d cross(const v_4d& rhs) 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 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); } - // Transform a vector by this matrix - template - inline constexpr auto operator * (const olc::v_2d& v) const + // 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 { - auto& me = *this; - olc::v_2d vOut; - vOut.x = Q(me(0, 0) * v.x + me(1, 0) * v.y + me(2, 0) * Q(1)); - vOut.y = Q(me(0, 1) * v.x + me(1, 1) * v.y + me(2, 1) * Q(1)); - Q z = Q(me(0, 2) * v.x + me(1, 2) * v.y + me(2, 2) * Q(1)); - return (vOut / z); + return this->max(v1).min(v2); } - // Multiply this matrix with another - template - inline constexpr auto operator * (const olc::m_3d& rhs) const + // 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 { - auto& me = *this; - 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); - return out; + return (*this) * (T(1.0 - t)) + (v1 * T(t)); } - - // Transform a vector of v_2d by this matrix - template - inline constexpr auto transform(const std::vector>& v) + // Compare if this vector is numerically equal to another + inline constexpr bool operator == (const v_4d& rhs) const { - std::vector> o(v.size()); - std::transform(v.begin(), v.end(), o.begin(), [this](const olc::v_2d& i) {return (*this) * i; }); - return o; + 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 matrix as a std::string, of the form "[c1r1, c2r1, c3r1]\n[c1r2, c2r2, c3r2]\n[c1r3, c2r3, c3r3]" + // Return this vector as a std::string, of the form "(x,y,z,w)" 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)) + "]\n" - + "[" + std::to_string(me(0, 1)) + "," + std::to_string(me(1, 1)) + "," + std::to_string(me(2, 1)) + "]\n" - + "[" + std::to_string(me(0, 2)) + "," + std::to_string(me(1, 2)) + "," + std::to_string(me(2, 2)) + "]\n"; + 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) }; } }; - // Allow olc::m_3d to play nicely with std::cout - template - inline std::ostream& operator << (std::ostream& os, const m_3d& rhs) + // Multiplication operator overloads between vectors and scalars, and vectors and vectors + template + inline constexpr auto operator * (const TL& lhs, const v_4d& rhs) { - os << rhs.str(); - return os; + return v_4d(lhs * rhs.x, lhs * rhs.y, lhs * rhs.z, lhs * rhs.w); } - // Convenient types ready-to-go - typedef m_3d mf3d; - typedef m_3d md3d; -} -#define PGE_MATRIX3D_DECLARED 1 -#endif - -#if !defined(PGE_TRANSFORM2D_DECLARED) -namespace olc -{ - namespace internal + template + inline constexpr auto operator * (const v_4d& lhs, const TR& rhs) { - // Return sign of number as -1 or +1 - template - inline constexpr int sgn(T val) - { - return (T(0) < val) - (val < T(0)); - } + 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; + } - /* - A complete 2D transformation structure - */ - template - class t_2d + // Division operator overloads between vectors and scalars, and vectors and vectors + template + inline constexpr auto operator / (const TL& lhs, const v_4d& rhs) { - static_assert(std::is_floating_point::value, "olc::t_2d must be floating point type"); + return v_4d(lhs / rhs.x, lhs / rhs.y, lhs / rhs.z, lhs / rhs.w); + } - public: + 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 +{ + + /* + A complete 3x3 Matrix structure, with a variety + of useful utility functions and operator overloads + specifically targeting 2D graphical transformations + + as per https://en.wikipedia.org/wiki/Affine_transformation + + Access: column, row + */ + template + struct m_3d + { + static_assert(std::is_arithmetic::value, "olc::m_2d must be numeric"); + + // The 3x3 elements! + std::array m{ {0 } }; + + // Constructor created identity matrix + inline constexpr m_3d() + { + identity(); + } + + // Copy constructor + inline constexpr m_3d(const m_3d& mat) = default; + + // Assignment operator + inline constexpr m_3d& operator=(const m_3d& 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 * 3 + c; + } + + // 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) = 1; + me(1, 1) = 1; + 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); + return { + me(0,0), me(1,0), me(2,0), 0, + me(0,1), me(1,1), me(2,1), 0, + me(0,2), me(1,2), me(2,2), 0, + 0, 0, 0, 1 + }; + } + + // Create translation matrix via components + template + inline constexpr void translate(const Q x, const Q y) + { + identity(); + auto& me = (*this); + me(2, 0) = T(x); + me(2, 1) = T(y); + } + + // Create translation matrix via vector + template + inline constexpr void translate(const olc::v_2d& v) + { + 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) + { + identity(); + auto& me = (*this); + me(0, 0) = T(x); + me(1, 1) = T(y); + } + + // Create scaling matrix via vector + template + inline constexpr void scale(const olc::v_2d& v) + { + 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) + { + identity(); + auto& me = (*this); + me(0, 0) = std::cos(rads); + me(0, 1) = std::sin(rads); + me(1, 0) = -me(0, 1); + 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) + { + identity(); + auto& me = (*this); + me(0, 1) = T(y); + me(1, 0) = T(x); + } + + // Create shearing matrix via vector + template + inline constexpr void shear(const olc::v_2d& v) + { + 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 + { + // 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; + } + + // Transform a vector by this matrix + template + inline constexpr auto operator * (const olc::v_2d& v) const + { + auto& me = *this; + olc::v_2d vOut; + vOut.x = Q(me(0, 0) * v.x + me(1, 0) * v.y + me(2, 0) * Q(1)); + vOut.y = Q(me(0, 1) * v.x + me(1, 1) * v.y + me(2, 1) * Q(1)); + Q z = Q(me(0, 2) * v.x + me(1, 2) * v.y + me(2, 2) * Q(1)); + return (vOut / z); + } + + // Multiply this matrix with another + template + inline constexpr auto operator * (const olc::m_3d& rhs) const + { + auto& me = *this; + olc::m_3d out; + for (size_t c = 0; c < 3; c++) + for (size_t r = 0; r < 3; r++) + out(c, r) = me(0, r) * rhs(c, 0) + me(1, r) * rhs(c, 1) + me(2, r) * rhs(c, 2); + return out; + } + + + // Transform a vector of v_2d 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_2d& i) {return (*this) * i; }); + return o; + } + + + + // Return this matrix as a std::string, of the form "[c1r1, c2r1, c3r1]\n[c1r2, c2r2, c3r2]\n[c1r3, c2r3, c3r3]" + 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)) + "]\n" + + "[" + std::to_string(me(0, 1)) + "," + std::to_string(me(1, 1)) + "," + std::to_string(me(2, 1)) + "]\n" + + "[" + std::to_string(me(0, 2)) + "," + std::to_string(me(1, 2)) + "," + std::to_string(me(2, 2)) + "]\n"; + } + }; + + // Allow olc::m_3d to play nicely with std::cout + template + inline std::ostream& operator << (std::ostream& os, const m_3d& rhs) + { + os << rhs.str(); + return os; + } + + // Convenient types ready-to-go + typedef m_3d mf3d; + typedef m_3d md3d; +} +#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 +{ + namespace internal + { + // Return sign of number as -1 or +1 + template + inline constexpr int sgn(T val) + { + return (T(0) < val) - (val < T(0)); + } + } + + + + /* + A complete 2D transformation structure + */ + template + class t_2d + { + static_assert(std::is_floating_point::value, "olc::t_2d must be floating point type"); + + public: // Constructor - inline constexpr t_2d() = default; + inline constexpr t_2d() + { + identity(); + } // Copy constructor inline constexpr t_2d(const t_2d& t) = default; @@ -1202,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 @@ -1220,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; } @@ -1228,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; } @@ -1236,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; } @@ -1251,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 @@ -1259,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; } @@ -1267,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; } @@ -1276,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_mRotate * m_mShear * m_mScale * 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; } @@ -1722,6 +2521,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; @@ -1897,6 +2699,8 @@ namespace olc class Renderer; class Shader; } + + class Draw3D; // These "opaque" structs are merely to help with // type differentiation of various GPUTask types @@ -1907,10 +2711,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); @@ -1966,19 +2772,22 @@ 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, 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 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( @@ -1988,21 +2797,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 @@ -2010,7 +2822,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( @@ -2022,29 +2835,31 @@ 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, 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 colBR, const olc::Pixel tint = olc::Colour::WHITE); - + // Draws a filled, single colour rectangle into a batch const FilledBatch& FilledRect( 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( @@ -2055,26 +2870,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( @@ -2082,22 +2889,26 @@ 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 - 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); // Draws a shaded circle with a radial gradient @@ -2109,26 +2920,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, @@ -2136,10 +2945,12 @@ 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 - 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, @@ -2147,39 +2958,52 @@ 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, 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 + // 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, const olc::Pixel colInner, const olc::Pixel colOuter, + const olc::Pixel tint = olc::Colour::WHITE, int32_t nFacets = OLC_DEFAULT_CIRCLE_FACETS); // === 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, @@ -2187,17 +3011,17 @@ 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, 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 - 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, @@ -2208,21 +3032,27 @@ namespace olc // === Triangles === - // Draws a triangle outline with a single colour - const GPUTask& Triangle( + + + // Draws a triangle outline with a single colour into a batch + const LineBatch& Triangle( + olc::LineBatch& batch, 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 + // 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, - const olc::Pixel col = olc::Colour::WHITE); + const olc::Pixel c1, + const olc::Pixel c2, + const olc::Pixel c3, + const olc::Pixel tint = olc::Colour::WHITE); // Draws a multiple colour triangle outline const GPUTask& Triangle( @@ -2234,31 +3064,36 @@ 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 triangle outline with a single colour + const GPUTask& 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 col = olc::Colour::WHITE, + const olc::Pixel tint = olc::Colour::WHITE); - // Draws a filled, single colour triangle - const GPUTask& FilledTriangle( + + + // Draws a filled, single colour triangle into a batch + const FilledBatch& FilledTriangle( + olc::FilledBatch& batch, 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 + + // 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, - const olc::Pixel col = olc::Colour::WHITE); + const olc::Pixel c1, + const olc::Pixel c2, + const olc::Pixel c3, + const olc::Pixel tint = olc::Colour::WHITE); // Draws a filled, multiple colour triangle const GPUTask& FilledTriangle( @@ -2270,15 +3105,13 @@ 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, single colour triangle + const GPUTask& 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 col = olc::Colour::WHITE, + const olc::Pixel tint = olc::Colour::WHITE); // Draws a textured triangle, with per vertex colouring const GPUTask& TexturedTriangle( @@ -2296,37 +3129,42 @@ namespace olc // === Polygon Outlines === - // Draws a polygon outline with a single colour - const GPUTask& Polygon( + + + // Draws a polygon outline with a single colour into a batch + const LineBatch& Polygon( + olc::LineBatch& batch, 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 + + // Draws a polygon outline with multiple colours into a batch const LineBatch& Polygon( olc::LineBatch& batch, const std::vector& vecPoints, - const olc::Pixel col = olc::Colour::WHITE); - + 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 @@ -2336,6 +3174,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, @@ -2350,6 +3196,8 @@ namespace olc 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, @@ -2467,6 +3315,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: @@ -2539,153 +3433,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: @@ -2717,9 +3484,6 @@ namespace olc private: sDrawMetrics drawMetrics; - - - protected: // Checks residency of image resource, and brings it to cpu RAM for r/w void PrepareTargetForSW(); @@ -2735,26 +3499,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: @@ -2884,7 +3640,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 @@ -2996,7 +3752,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); @@ -3024,9 +3780,22 @@ 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); + + // Set the Window to be FullScreen or Not Fullscreen + void ShowFullScreen(const bool bFullScreen); + + // Focus + bool IsFocused() const; + protected: bool bRequestToClose = false; bool bShouldRemove = false; + bool bWindowIsFocused = false; protected: size_t nUniqueID = size_t(-1); @@ -3053,37 +3822,10 @@ namespace olc { inline static size_t uuid = 0; - #if OLC_HOST == OLC_HOST_WINDOWS - inline constexpr 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; @@ -3126,6 +3868,14 @@ 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; + // 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; @@ -3155,6 +3905,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 @@ -3188,12 +4004,15 @@ 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 class PGEWindow : public Window { public: + PGEWindow(); bool Create(const olc::vi2d& vScreenSize, const olc::vi2d& vPixelSize); public: @@ -3225,8 +4044,8 @@ namespace olc public: // Returns the image that represents the primary drawing surface - olc::Image& GetDefaultImage(); - olc::Draw2D& GetDraw(); + olc::Image& GetScreen(); + olc::Draw& GetDraw(); // Input devices are handled by a regular olc::Window, but for convenience... olc::hw::Mouse& GetMouse(); @@ -3235,6 +4054,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; @@ -3242,8 +4065,7 @@ namespace olc virtual bool olc_WindowUpdate(const float fElapsedTime, const float fTotalElapsedTime); protected: - olc::Draw2D draw; - + olc::Draw draw; private: olc::Image imgPrimary; @@ -3252,9 +4074,16 @@ 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; + + // Application Name + std::string sAppName = ""; }; // The olc::PixelGameEngine3 core, manages the main window, child windows, engine loop, timing and devices @@ -3287,6 +4116,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 @@ -3305,6 +4138,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; @@ -3333,6 +4170,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) @@ -3395,6 +4280,11 @@ 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; + bool SetFullScreen(olc::Window* pWindow, const bool bFullScreen) override; + public: // OS Specific Environment Information olc::KeyboardLayout GetKeyboardLayout() const override; @@ -3421,6 +4311,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); @@ -3440,6 +4332,7 @@ namespace olc #include #include #include +#include extern "C" { // NSRect (OSX rectangle structure same as GCRect C structure) @@ -3466,6 +4359,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); @@ -3485,7 +4379,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); @@ -3522,6 +4418,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 (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); @@ -3533,6 +4430,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); @@ -3641,6 +4540,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 +4654,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_; } @@ -3990,6 +4904,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; @@ -4031,6 +4968,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()); @@ -4196,6 +5140,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; @@ -4228,6 +5185,7 @@ namespace olc { Window& window_; std::function keyDownHandler_; std::function keyUpHandler_; + std::function flagsChangedHandler_; std::function mouseDownHandler_; std::function mouseUpHandler_; std::function mouseMovedHandler_; @@ -4239,6 +5197,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 @@ -4248,6 +5208,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) { @@ -4266,6 +5234,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_); } @@ -4280,6 +5252,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); @@ -4350,6 +5336,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); @@ -4404,6 +5395,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 { @@ -4448,20 +5449,41 @@ 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; + + 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; @@ -4508,6 +5530,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 { @@ -4521,12 +5545,8 @@ 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); - - bool bNumLockActive = true; // Num Lock state, we assume it's active at start + void KeyboardEventHandler(const olc::apis::macos::KeyEvent& event, bool isPressed); + bool bNumLockActive = true; // Num Lock state, we assume it's active at start }; } @@ -4542,7 +5562,10 @@ namespace X11 #include #include #include +#include #include +#undef None +constexpr int None = 0L; } namespace olc::host @@ -4570,6 +5593,12 @@ 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; + bool SetFullScreen(olc::Window* pWindow, const bool bFullScreen) override; + public: bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; bool StartSystem() override; @@ -4585,12 +5614,17 @@ 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; bool kbExtensionsFound = false; int xkbEventBase = 0; int xkbErrorBase = 0; + + // Mouse Variables + std::unordered_map mapUID2X11Cursor; + bool bMouseIsVisible = true; }; } @@ -4602,6 +5636,8 @@ namespace olc::host #include #include "xdg-shell.h" #include "xdg-decoration.h" +#include "pointer-warp.h" +#include "cursor-shape.h" #include #include #include @@ -4611,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 { @@ -4622,6 +5662,9 @@ namespace olc::host size_t olc_window_uid{0}; 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 { @@ -4666,12 +5709,17 @@ 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; 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}; + wp_cursor_shape_device_v1* cursor_shape_device{nullptr}; + wp_cursor_shape_manager_v1* cursor_shape_manager{nullptr}; wayland::PointerState pointer_state; @@ -4692,6 +5740,9 @@ 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; + bool SetFullScreen(olc::Window* pWindow, const bool bFullScreen) override; public: bool OnApplicationStart(olc::PixelGameEngine* pPrimary) override; @@ -4793,16 +5844,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; @@ -4823,19 +5886,20 @@ 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); 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); 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); @@ -4845,9 +5909,9 @@ 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); - + private: // Emscripten internal funcs + std::string getNavigatorLocale(); + public: // Callback data type struct CallbackData { Host_Web_Emscripten* pHost; @@ -4862,6 +5926,10 @@ 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; + std::chrono::steady_clock::time_point timeHidden; + olc::KeyboardLayout keyboardLayout{OLC_DEFAULT_KEYBOARD_LAYOUT}; }; @@ -4875,6 +5943,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 +5959,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 +5990,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 @@ -5090,13 +6179,94 @@ 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 - #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))) @@ -5104,9 +6274,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 @@ -5122,7 +6290,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 @@ -5245,11 +6414,17 @@ 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); #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 { @@ -5306,10 +6481,16 @@ 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 +#if OLC_HOST == OLC_HOST_LINUX_X11 + public: + glXSwapIntervalEXT_t* XSwapIntervalEXT = nullptr; +#endif public: // Proxies allow switchable, clutter-free error checking @@ -5356,6 +6537,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); @@ -5377,7 +6560,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); @@ -5402,8 +6585,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_X = 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()); @@ -5508,6 +6692,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 @@ -5550,7 +6738,9 @@ namespace olc #pragma comment(lib, "Shlwapi.lib") #include #include +#if !defined(__MINGW32__) && !defined(__MINGW64__) #include +#endif #include #undef _WINSOCKAPI_ @@ -5640,28 +6830,51 @@ namespace olc::imload // 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 OLC_IMAGELOADER == OLC_IMAGELOADER_NDK_IMAGEDECODER +#include + +#if !defined(PGE_IMAGELOADER_NDK_IMAGEDECODER_DECLARED) +namespace olc::imload +{ + + // Create an image resource based on an image file asset + class ImageLoader_NDKImageDecoder : public ImageLoader + { + public: + // 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 +#define PGE_IMAGELOADER_NDK_IMAGEDECODER_DECLARED 1 #endif #endif -#if OLC_HOST == OLC_HOST_ANDROID -#include - -#if !defined(PGE_IMAGELOADER_NDK_IMAGEDECODER_DECLARED) +#if OLC_IMAGELOADER == OLC_IMAGELOADER_STB_IMAGE +#if !defined(PGE_IMAGELOADER_STB_DECLARED) namespace olc::imload { - - // Create an image resource based on an image file asset - class ImageLoader_NDKImageDecoder : public ImageLoader - { - public: - ImageLoader_NDKImageDecoder() = default; - ImageLoader_NDKImageDecoder(AAssetManager* assetManager) : assetManager(assetManager) {} - + 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; @@ -5677,21 +6890,129 @@ 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 + +#define PGE_IMAGELOADER_STB_DECLARED 1 #endif #endif +#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) + { + 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 defined(OLC_PGE3_APPLICATION) && !defined(PGE_HOST_IMPLEMENTED) #if OLC_HOST == OLC_HOST_WINDOWS namespace olc::host { @@ -5956,12 +7277,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; @@ -6012,6 +7334,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); @@ -6023,7 +7347,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); @@ -6062,6 +7385,56 @@ 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; + } + + bool Host_Windows_WinAPI::SetFullScreen(olc::Window* pWindow, const bool bFullScreen) + { + HWND hWnd = mapUID2HWND.at(pWindow->GetUID()); + + 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) { if (!mapHWND2PTR.contains(hWnd)) @@ -6100,12 +7473,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); @@ -6170,6 +7565,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... @@ -6215,6 +7638,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); @@ -6367,7 +7801,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 +7810,85 @@ 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::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; + } + + 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 +7917,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 @@ -6618,10 +8158,18 @@ namespace olc::host { res = true; // Skip frame to allow resize to take effect break; } - case MINIMIZE_WINDOW: case DEMINIMIZE_WINDOW: case BECOME_ACTIVE: + { + pPGEwindow->olc_OnFocus(true); + break; + } + case MINIMIZE_WINDOW: case RESIGN_ACTIVE: + { + pPGEwindow->olc_OnFocus(false); + break; + } case NONE: default: { @@ -6656,9 +8204,7 @@ namespace olc::host { pPGEwindow->keyboard.UseKeyboardLayout(GetKeyboardLayout()); }); - pMacApplication->setWillTerminateCallback([&]() { - // TODO: Johnngy63 - Implement olc_OnDestory in window.h/cpp - }); + pMacApplication->setWillTerminateCallback([&]() { }); pMacApplication->setDidBecomeActiveCallback([]() { }); @@ -6673,18 +8219,17 @@ namespace olc::host { }); pMacOSWindow->setWindowWillCloseCallback([&]() { - // TODO: Johnngy63 - Implement any pre-close logic if needed + // 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([&]() { @@ -6696,52 +8241,39 @@ namespace olc::host { }); } - - bool Host_Apple_MacOS::ModifiersFlagsHandler(const olc::apis::macos::KeyEvent& event, bool pressed) { + + // 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; - bool bisHandled = false; - if (event.modifierFlags & NSEventModifierFlagCapsLock) { - pPGEwindow->olc_OnKeyPress(Key::CAPS_LOCK, pressed); + // handle num clear/lock key only on the down stroke. + if(isPressed && keyCode == 71) + { + bNumLockActive = !bNumLockActive; + return; } - if (event.modifierFlags & NSEventModifierFlagShift) { - pPGEwindow->olc_OnKeyPress(Key::SHIFT, pressed); - if(event.keyCode == 39) + + if(!bNumLockActive) + { + // 84 down, 86 left, 88 right, 91 up >>> 125 down, 123 left, 124 right, 126 up + switch(keyCode) { - // 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; + case 84: keyCode = 125; break; + case 86: keyCode = 123; break; + case 88: keyCode = 124; break; + case 91: keyCode = 126; break; + default: break; } - } - if (event.modifierFlags & NSEventModifierFlagControl) { - pPGEwindow->olc_OnKeyPress(Key::CTRL, pressed); - } - - if(event.modifierFlags & NSEventModifierFlagNumericPad) { - if(event.keyCode == 71 && pressed) // NumLock keycode - { - // We only tottle the NumLock state on key press to minic the latching of the key - bNumLockActive = !bNumLockActive; - } - - 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; - } - - } - - return bisHandled; + // 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() { // General MacOS key event handling code here @@ -6749,16 +8281,43 @@ namespace olc::host { // Set up keyboard event handlers pMacOSEventHandler->onKeyDown([&](const olc::apis::macos::KeyEvent& event) { - if(!ModifiersFlagsHandler(event, true)) - pPGEwindow->olc_OnKeyPress(mapKeys[event.keyCode], true); + KeyboardEventHandler(event, true); }); - + pMacOSEventHandler->onKeyUp([&](const olc::apis::macos::KeyEvent& event) { - if(!ModifiersFlagsHandler(event, false)) - pPGEwindow->olc_OnKeyPress(mapKeys[event.keyCode], false); + KeyboardEventHandler(event, false); + }); + + // Set up keyboard flag event handlers + pMacOSEventHandler->onFlagsChanged([&](const olc::apis::macos::FlagsChangedEvent& event) { + + 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); + } + + 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; }); - + // Set up mouse event handlers pMacOSEventHandler->onMouseDown([&](const olc::apis::macos::MouseEvent& event) { pPGEwindow->olc_OnMouseButton(event.buttonNumber, true); @@ -6769,7 +8328,7 @@ namespace olc::host { }); 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) { @@ -6829,6 +8388,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"; @@ -6841,6 +8409,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:"; @@ -6873,6 +8442,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:"; @@ -6886,6 +8456,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"; @@ -6938,7 +8515,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; @@ -6952,9 +8529,11 @@ 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 +// Objective-C method type encoding constants // Type encoding for methods returning BOOL with no parameters: "c@:" static constexpr const char* kBoolMethodTypeEncoding = "c@:"; @@ -6969,43 +8548,131 @@ 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 = 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; + + // 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; + 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 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; + 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 @@ -7021,6 +8688,13 @@ namespace ObjectiveCSEL { activateIgnoringOtherAppsSel = sel_registerName(kActivateIgnoringOtherAppsSel); 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); @@ -7053,6 +8727,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); @@ -7066,7 +8741,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); @@ -7159,14 +8839,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; @@ -7312,6 +8984,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 @@ -7394,6 +9071,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}; @@ -7404,7 +9082,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 @@ -7428,6 +9110,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}; @@ -7456,7 +9139,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}; @@ -7593,6 +9276,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 @@ -7641,7 +9333,6 @@ MouseEventData extractMouseEventData(id event) { // Convert to content view coordinates and flip Y coordinate convertToContentViewCoordinates(data.location); - return data; } @@ -7769,6 +9460,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; @@ -7828,7 +9574,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); @@ -7841,7 +9590,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); @@ -7887,6 +9641,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); } @@ -8024,6 +9780,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 +9795,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); @@ -8157,6 +9919,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; @@ -8165,8 +9937,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); @@ -8286,6 +10057,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; @@ -8699,6 +10510,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; @@ -8724,6 +10536,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; @@ -8780,6 +10597,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; @@ -8900,6 +10727,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; @@ -8930,6 +10758,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) @@ -9042,28 +10876,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) @@ -9074,14 +10911,19 @@ 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.xfocus.window); pge_window) { + 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_OnFocus(false); + } + } else if (xev.type == ClientMessage) { X11::XClientMessageEvent& xcme = xev.xclient; @@ -9129,7 +10971,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; @@ -9155,13 +10997,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); } @@ -9250,6 +11105,82 @@ namespace olc::host { return true; } + + bool Host_Linux_X11::SetMousePosition(olc::Window* pWindow, const olc::vi2d& vPos) + { + 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; + + 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; + } + + 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 @@ -9355,6 +11286,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; @@ -9568,11 +11500,54 @@ namespace olc::host keyboardLayout = olc::KeyboardLayout::AZERTY; } - olc::KeyboardLayout Host_Linux_Wayland::GetKeyboardLayout() const + olc::KeyboardLayout Host_Linux_Wayland::GetKeyboardLayout() const + { + 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 keyboardLayout; + 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; } + 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) { @@ -9591,6 +11566,12 @@ 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)); + } + 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) @@ -9602,11 +11583,13 @@ 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); + cursor_shape_device = wp_cursor_shape_manager_v1_get_pointer(cursor_shape_manager, pointer); + wl_pointer_add_listener(pointer, &wayland::pointer_listener, this); } 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); } } @@ -9618,12 +11601,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}); @@ -9689,6 +11674,9 @@ 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 = surface; pointer_state.surface_x = surface_x; pointer_state.surface_y = surface_y; } @@ -9757,16 +11745,25 @@ 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; + + // 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); + } } } } - - 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{ @@ -9780,6 +11777,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; } } @@ -9842,7 +11841,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) { @@ -9888,7 +11887,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_OnFocus(true); } void Host_Linux_Wayland::keyboard_leave_callback(void* data, wl_keyboard* keyboard, uint32_t serial, wl_surface* surface) @@ -9899,7 +11906,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_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) @@ -9915,7 +11923,27 @@ 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); + + // 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); + } } } @@ -10014,121 +12042,45 @@ 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) + Host_Web_Emscripten::Host_Web_Emscripten() { - auto pHost = reinterpret_cast(userData); - - if(!pHost->OnSystemTick()) + 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); }); + + 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") { - pHost->StopSystem(); + keyboardLayout = KeyboardLayout::QWERTZ; } - } - - // 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()) + else if(locale == "fr-ca") { - // PGE->ContextStart() failed, or user aborted OnUserCreate() - return false; + 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; } - - 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"; - - // 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; - } - - }); - }); // Map Emscripten Defined DOM_PK_ Codes to olc::KeyCodes mapKeys[DOM_PK_UNKNOWN] = Key::NONE; @@ -10244,60 +12196,206 @@ 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) + { + std::cout << "Emscripten: AddWindowFrame called.\n"; + + // The user created olc::Window object is the SSoT for what a window + // should look like, so get that sort of thing from there + olc::vi2d vWinPos = vWindowPos; + olc::vi2d vWinSize = vWindowSize; + + // TODO: multi-window solutions + mapUID2CallbackData.insert_or_assign(pWindow->GetUID(), std::make_unique(CallbackData{ + this, + pWindow, + std::string{"#canvas"} + })); + + auto cbData = mapUID2CallbackData.at(pWindow->GetUID()).get(); + + mapUID2CanvasId.insert_or_assign(pWindow->GetUID(), cbData->canvasId); + mapCanvasId2PTR.insert_or_assign(cbData->canvasId, pWindow); + + emscripten_set_canvas_element_size(cbData->canvasId.c_str(), vWinSize.x, vWinSize.y); + pWindow->SetWindowSize(vWinSize); + + // Keyboard Callbacks + emscripten_set_keydown_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, keyboard_callback); + emscripten_set_keyup_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, keyboard_callback); + + // Mouse Callbacks + emscripten_set_wheel_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, wheel_callback); + emscripten_set_mousedown_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, mouse_callback); + emscripten_set_mouseup_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, mouse_callback); + emscripten_set_mousemove_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, mouse_callback); + + // Touch Callbacks + emscripten_set_touchstart_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, touch_callback); + emscripten_set_touchmove_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, touch_callback); + emscripten_set_touchend_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, touch_callback); + + // Canvas Focus Callbacks + emscripten_set_blur_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, focus_callback); + emscripten_set_focus_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, focus_callback); + + // Canvas Resize Callbacks + 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)); + + 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 keyboardLayout; + } + + // Called at very start of application + bool Host_Web_Emscripten::OnApplicationStart(olc::PixelGameEngine* pPrimary) + { + std::cout << "Emscripten: OnApplicationStart.\n"; + pPrimaryPGE = pPrimary; + return true; } - bool Host_Web_Emscripten::AddWindowFrame(olc::Window* pWindow, const olc::vi2d& vWindowPos, const olc::vi2d& vWindowSize, const bool bFullScreen) + // Called to start the host - this may mean different things on different hosts + bool Host_Web_Emscripten::StartSystem() { - std::cout << "Emscripten: AddWindowFrame called.\n"; + // Pre-context start hook + pPrimaryPGE->OnPreContextStart(); - // The user created olc::Window object is the SSoT for what a window - // should look like, so get that sort of thing from there - olc::vi2d vWinPos = vWindowPos; - olc::vi2d vWinSize = vWindowSize; + if(!OnSystemThreadStart()) + { + // PGE->ContextStart() failed, or user aborted OnUserCreate() + return false; + } - // TODO: multi-window solutions - mapUID2CallbackData.insert_or_assign(pWindow->GetUID(), std::make_unique(CallbackData{ - this, - pWindow, - std::string{"#canvas"} - })); + emscripten_set_main_loop_arg(Host_Web_Emscripten::MainLoop, reinterpret_cast(this), 0, 1); - auto cbData = mapUID2CallbackData.at(pWindow->GetUID()).get(); - - mapUID2CanvasId.insert_or_assign(pWindow->GetUID(), cbData->canvasId); - mapCanvasId2PTR.insert_or_assign(cbData->canvasId, pWindow); + // EMSCRIPTEN QUIRK: this code is never reached, the main loop is simulating a while(true); - emscripten_set_canvas_element_size(cbData->canvasId.c_str(), vWinSize.x, vWinSize.y); - pWindow->SetWindowSize(vWinSize); - - // Keyboard Callbacks - emscripten_set_keydown_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, keyboard_callback); - emscripten_set_keyup_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, keyboard_callback); - - // Mouse Callbacks - emscripten_set_wheel_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, wheel_callback); - emscripten_set_mousedown_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, mouse_callback); - emscripten_set_mouseup_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, mouse_callback); - emscripten_set_mousemove_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, mouse_callback); - - // Touch Callbacks - emscripten_set_touchstart_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, touch_callback); - emscripten_set_touchmove_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, touch_callback); - emscripten_set_touchend_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, touch_callback); - - // Canvas Focus Callbacks - emscripten_set_blur_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, focus_callback); - emscripten_set_focus_callback(cbData->canvasId.c_str(), reinterpret_cast(cbData), 1, focus_callback); + 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; + } - // Canvas Resize Callbacks - 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); + // 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; + } - // trigger resize after a short pause - emscripten_sleep(50); - resize_callback(EMSCRIPTEN_EVENT_RESIZE, nullptr, reinterpret_cast(cbData)); + void Host_Web_Emscripten::MainLoop(void* userData) + { + auto pHost = reinterpret_cast(userData); - return true; + if(!pHost->OnSystemTick()) + { + pHost->StopSystem(); + } } //TY Moros @@ -10364,29 +12462,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) { @@ -10394,39 +12469,51 @@ 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); + // middle/next/back buttons require the event to be consumed to prevent browser behavior + if(it->second >= 2) return EM_TRUE; + } + } + break; + case EMSCRIPTEN_EVENT_MOUSEUP: + { + auto it = pCallbackData->pHost->mapMouseButtons.find(e->button); + 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; + } + } + break; + default: break; } - + 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) @@ -10457,24 +12544,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) - { - // ptrPGE->olc_UpdateKeyFocus(false); - olc_OnMouseFocus(pCallbackData->pWindow, false); - } - else if (eventType == EMSCRIPTEN_EVENT_FOCUS) - { - // ptrPGE->olc_UpdateKeyFocus(true); - olc_OnMouseFocus(pCallbackData->pWindow, true); - } - - return 0; - } //TY Moros EM_BOOL Host_Web_Emscripten::fullscreen_change_callback(int eventType, const EmscriptenFullscreenChangeEvent *event, void *userData) @@ -10525,39 +12594,40 @@ namespace olc::host return 0; } - bool Host_Web_Emscripten::CloseWindowFrame(olc::Window* pWindow) + //TY Gorbit + EM_BOOL Host_Web_Emscripten::focus_callback(int eventType, const EmscriptenFocusEvent* focusEvent, void* userData) { - std::cout << "Emscripten: CloseWindowFrame not implemented.\n"; - return true; - } + CallbackData* pCallbackData = reinterpret_cast(userData); + + if (eventType == EMSCRIPTEN_EVENT_BLUR) + { + olc_OnFocus(pCallbackData->pWindow, false); + } + else if (eventType == EMSCRIPTEN_EVENT_FOCUS) + { + olc_OnFocus(pCallbackData->pWindow, true); + } - bool Host_Web_Emscripten::UpdateWindowFrameTitle(olc::Window* pWindow) - { - // not implemented for emscripten platform - return true; + return 0; } - std::vector Host_Web_Emscripten::GetHostWindowDescriptor(olc::Window* pWindow) + EM_BOOL Host_Web_Emscripten::visibility_callback(int eventType, const EmscriptenVisibilityChangeEvent *visibilityChangeEvent, void *userData) { - const auto window_handle = mapUID2CanvasId.find(pWindow->GetUID()); - if(window_handle != mapUID2CanvasId.end()) + CallbackData *pCallbackData = reinterpret_cast(userData); + + if (visibilityChangeEvent->hidden) { - return { (void*)(window_handle->second.c_str()) }; + 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; } - - 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) @@ -10575,9 +12645,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) @@ -10600,7 +12670,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; + } } #endif @@ -10744,62 +12826,132 @@ namespace olc::host host->OnAppCmd(app, cmd); } - static int32_t Android_onInputEvent(struct android_app* app, AInputEvent* event) + static int32_t Android_onInputEvent(struct android_app* app, AInputEvent* event) + { + auto host = reinterpret_cast(app->userData); + return host->OnInputEvent(app, event); + } + + bool Host_Android::StartSystem() + { + 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(); + } + + 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() { - auto host = reinterpret_cast(app->userData); - return host->OnInputEvent(app, event); + return pPrimaryPGE->OnContextEnd(); } - bool Host_Android::StartSystemEventLoop(bool bBlockIfPossible) + bool Host_Android::OnApplicationStart(olc::PixelGameEngine* pPrimary) { - 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); - } - - if (!androidApp || !initialized) return true; - - if (androidApp->destroyRequested != 0) return false; + 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 +12960,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 +13067,6 @@ namespace olc::host return { reinterpret_cast(androidApp->window) }; } - bool Host_Android::ConnectHostResourceToRenderer() - { - return true; - } - bool Host_Android::SyncWithDesktopComposite() { return true; @@ -11132,15 +13281,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 +13319,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); } @@ -11163,7 +13332,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() @@ -11229,6 +13398,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; @@ -11298,6 +13623,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 @@ -11310,6 +13637,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; } @@ -11477,6 +13807,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 @@ -11727,6 +14063,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 { @@ -11965,9 +14313,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; @@ -11978,12 +14351,13 @@ void main() lastError = RendererError::FailedToSwitchRenderContext; return false; } + #endif #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); @@ -12015,7 +14389,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 @@ -12079,19 +14453,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 } @@ -12165,7 +14544,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_X, 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 = { { @@ -12189,20 +14578,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 @@ -12520,6 +14923,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_X, + targetSize.x, + targetSize.y + ); + } + else + { + // Allocate regular depth buffer + gl.glRenderbufferStorage( + gl.GL_RENDERBUFFER_X, + gl.GL_DEPTH_COMPONENT24_X, + 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 = { { @@ -12731,42 +15188,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); @@ -12775,16 +15226,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()); @@ -12818,7 +15277,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; } @@ -12934,26 +15393,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(); @@ -12978,17 +15437,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); @@ -12998,7 +15457,7 @@ void olc::Draw2D::ProcessGPUTasks() vecGPUTasks.data.clear(); } -void Draw2D::PrepareTargetForSW() +void Draw::PrepareTargetForSW() { if (pTarget->BoundToGPU()) { @@ -13012,13 +15471,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()) { @@ -13032,7 +15488,7 @@ void Draw2D::PrepareTargetForHW() } } -void Draw2D::PrepareImageForSW(olc::Image& image) +void Draw::PrepareImageForSW(olc::Image& image) { if (image.BoundToGPU()) { @@ -13049,7 +15505,7 @@ void Draw2D::PrepareImageForSW(olc::Image& image) } } -void Draw2D::PrepareImageForHW(olc::Image& image) +void Draw::PrepareImageForHW(olc::Image& image) { if (image.BoundToCPU()) { @@ -13070,7 +15526,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(); @@ -13081,85 +15537,86 @@ 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); @@ -13172,25 +15629,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; @@ -13204,7 +15661,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; @@ -13216,7 +15673,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; @@ -13228,7 +15685,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; @@ -13239,44 +15696,93 @@ 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; + task.vertexBuffer.resize(vPoints.size()); + for (size_t i = 0; i < vPoints.size(); i++) + task.vertexBuffer[i] = { {vPoints[i].x, vPoints[i].y, 1.0f, 1.0f}, colour, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; + task.tint = tint; + return task; +} + +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; + task.vertexBuffer.resize(vPoints.size()); + for (size_t i = 0; i& 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; + task.vertexBuffer.resize(vPoints.size()); + for (size_t i = 0; i < vPoints.size(); i++) + task.vertexBuffer[i] = { {vPoints[i].x, vPoints[i].y, vZWs[i].x, vZWs[i].y}, vColours[i], {vTexCoords[i].x, vTexCoords[i].y}, {0, 0}, {0, 0}, {0, 0} }; + task.pImage = image; + task.tint = tint; + 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, 1.0f, 1.0f}, colour, {0, 0}, {0, 0}, {0, 0}, {0, 0} }; - task.tint = tint; - return task; + 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::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::TaskFillMesh(olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) { GPUTask task; task.structure = structure; - task.vertexBuffer.resize(vPoints.size()); - for (size_t i = 0; i& vPoints, const std::vector& vZWs, const std::vector& vColours, const std::vector& vTexCoords, olc::Image* const image, const olc::Pixel tint) +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()); - for (size_t i = 0; i < vPoints.size(); i++) - task.vertexBuffer[i] = { {vPoints[i].x, vPoints[i].y, vZWs[i].x, vZWs[i].y}, vColours[i], {vTexCoords[i].x, vTexCoords[i].y}, {0, 0}, {0, 0}, {0, 0} }; task.pImage = image; - task.tint = tint; + 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(); @@ -13288,12 +15794,8 @@ 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) -{ - 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(); @@ -13306,18 +15808,8 @@ 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) -{ - 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::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 @@ -13338,12 +15830,8 @@ 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) -{ - 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(); @@ -13361,24 +15849,9 @@ 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 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::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(); @@ -13395,14 +15868,9 @@ 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) -{ - 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( @@ -13418,14 +15886,8 @@ 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) -{ - 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++) @@ -13435,37 +15897,25 @@ 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) -{ - 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) -{ - 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) -{ - 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(); @@ -13491,36 +15941,14 @@ 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) -{ - // 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::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) -{ - 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 @@ -13549,28 +15977,8 @@ 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) -{ - // 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::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(); @@ -13620,53 +16028,8 @@ 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) -{ - 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::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(); @@ -13718,17 +16081,13 @@ 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) -{ - 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(); @@ -13741,25 +16100,13 @@ 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) -{ - Line(batch, p1, c1, p2, c2); - Line(batch, p2, c2, p3, c3); - Line(batch, p3, c3, p1, c1); - 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) -{ - 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(); @@ -13772,20 +16119,8 @@ 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) -{ - 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::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); @@ -13801,42 +16136,24 @@ 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) -{ - 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::Draw2D::Polygon(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) -{ - 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(); @@ -13849,12 +16166,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(); @@ -13867,7 +16184,8 @@ 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); @@ -13883,7 +16201,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(); @@ -13906,7 +16224,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; } } @@ -13914,7 +16232,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(); @@ -13936,7 +16254,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; } } @@ -13944,7 +16262,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 }; @@ -13977,70 +16295,61 @@ 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; -} - -const GPUTask& olc::Draw2D::Batch(olc::ImageBatch& batch, const olc::Pixel tint) -{ - batch.task.tint = tint; - return vecGPUTasks.data.emplace_back(batch.task); + return vecGPUTasks.data.emplace_back(std::move( + TaskWireMesh( + olc::Structure::Line, + { vStart, vEnd }, + { col, col }, + tint + ))); } -FilledBatch olc::Draw2D::CreateFilledBatch() +GPUTask& olc::Draw::Mesh(const olc::Structure structure, const std::vector& vPoints, const std::vector& vColours, const olc::Pixel tint) { - FilledBatch b; - b.task.structure = olc::Structure::List; - return b; -} + PrepareTargetForHW(); -const GPUTask& olc::Draw2D::Batch(olc::FilledBatch& batch, const olc::Pixel tint) -{ - batch.task.tint = tint; - return vecGPUTasks.data.emplace_back(batch.task); + return vecGPUTasks.data.emplace_back(std::move( + TaskWireMesh( + structure, + vPoints, + vColours, + tint + ))); } -LineBatch olc::Draw2D::CreateLineBatch() +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) { - LineBatch b; - b.task.structure = olc::Structure::LineList; - return b; -} + PrepareImageForHW(texture); + PrepareTargetForHW(); -const GPUTask& olc::Draw2D::Batch(olc::LineBatch& batch, const olc::Pixel tint) -{ - batch.task.tint = tint; - return vecGPUTasks.data.emplace_back(batch.task); + return vecGPUTasks.data.emplace_back(std::move( + TaskTexturedMesh( + structure, + vPoints, + vColours, + vUVs, + &texture, + tint + ))); } -const ImageBatch& olc::Draw2D::Image(ImageBatch& batch, olc::ImageRegion image, const olc::vf2d& pos, const olc::vf2d& scale, const olc::Pixel tint) +ImageBatch olc::Draw::CreateImageBatch(olc::Image &image) { - // 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 }); + PrepareImageForHW(image); + PrepareTargetForHW(); - //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; + ImageBatch b; + b.task.structure = olc::Structure::List; + b.task.pImage = ℑ + return b; } -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); @@ -14071,7 +16380,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); @@ -14080,10 +16389,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++) @@ -14094,42 +16403,13 @@ const GPUTask& olc::Draw2D::ImageRotated(olc::ImageRegion image, const olc::vf2d olc::Structure::Fan, transformAffine.forward(vPoints), { tint, tint, tint, tint}, - { image.coords[0], image.coords[1], image.coords[2], image.coords[3] }, - &image.image.get() - ))); -} - -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) -{ - // 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; + { image.coords[0], image.coords[1], image.coords[2], image.coords[3] }, + &image.image.get() + ))); } -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); @@ -14184,64 +16464,15 @@ 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) -{ - 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 Draw2D::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) -{ - 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); @@ -14259,554 +16490,523 @@ 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) + +void olc::Draw::SetCullMode(const olc::GPUTask::CullMode mode) { - olc_IgnoreUnused(image, pos, size, tint); - // TODO: Implement this function - return batch; + cullMode = mode; } +void olc::Draw::EnableDepth(const bool bEnable) +{ + bDepth = bEnable; +} -// This is all essentially the olc::PixelGameEngine 2 rasteriser code -using namespace olc; - +void olc::Draw::SetViewport(const olc::vi2d& pos, const olc::vi2d& size) +{ + pRenderer->SetViewport(pos, size); +} -void Draw2D::swLine(const olc::vf2d& p1, const olc::vf2d& p2, const olc::Pixel col) +void olc::Draw::MatrixReset() { - swLine(p1, p2, col, col); + matMVP.identity(); + matModel.identity(); + matView.identity(); + matProjection.identity(); } -void Draw2D::swLine(const olc::vf2d& p1, const olc::vf2d& p2, const olc::Pixel c1, const olc::Pixel c2) +void olc::Draw::SetModelMatrix(const olc::mf4d& mat) { - const auto vTransformedPoints = transformAffine.forward({ p1, p2 }); - swRasterShadedLine( - vTransformedPoints[0], - vTransformedPoints[1], - c1, c2); + matModel = mat; + matMVP = matVP * matModel; } -void olc::Draw2D::swRect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) +const olc::mf4d& olc::Draw::GetModelMatrix() const { - swRect(pos, size, col, col, col, col); + return matModel; } -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) +void olc::Draw::SetViewMatrix(const olc::mf4d& mat) { - 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); + matView = mat; + matVP = matProjection * matView; + matMVP = matVP * matModel; } -void olc::Draw2D::swFilledRect(const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col) +const olc::mf4d& olc::Draw::GetViewMatrix() const { - swFilledRect(pos, size, col, col, col, col); + return matView; } -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) +void olc::Draw::SetProjectionMatrix(const olc::mf4d& mat) { - 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 + matProjection = mat; + matVP = matProjection * matView; + matMVP = matVP * matModel; +} - // 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(); +const olc::mf4d& olc::Draw::GetProjectionMatrix() const +{ + return matProjection; +} - // 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::Draw::SetMVPMatrix(const olc::mf4d& mat) +{ + matMVP = mat; } -void olc::Draw2D::swTriangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col) +const olc::mf4d& olc::Draw::GetMVPMatrix() const { - swTriangle(p1, p2, p3, col, col, col); + return matMVP; } -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) +using namespace olc; + +const GPUTask& olc::Draw::Batch(olc::ImageBatch& batch, const olc::Pixel tint) { - swLine(p1, p2, c1, c2); - swLine(p2, p3, c2, c3); - swLine(p3, p1, c3, c1); + batch.task.tint = tint; + return vecGPUTasks.data.emplace_back(batch.task); } -void olc::Draw2D::swFilledTriangle(const olc::vf2d& p1, const olc::vf2d& p2, const olc::vf2d& p3, const olc::Pixel col) +FilledBatch olc::Draw::CreateFilledBatch() { - swFilledTriangle(p1, p2, p3, col, col, col); + FilledBatch b; + b.task.structure = olc::Structure::List; + return b; } -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 GPUTask& olc::Draw::Batch(olc::FilledBatch& batch, const olc::Pixel tint) { - const auto vTransformedPoints = transformAffine.forward({ p1, p2, p3 }); - swRasterShadedTriangle( - vTransformedPoints[0], - vTransformedPoints[1], - vTransformedPoints[2], - c1, c2, c3); + batch.task.tint = tint; + return vecGPUTasks.data.emplace_back(batch.task); } -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) +LineBatch olc::Draw::CreateLineBatch() { - const auto vTransformedPoints = transformAffine.forward({ p1, p2, p3 }); - swRasterTexturedTriangle( - vTransformedPoints[0], - vTransformedPoints[1], - vTransformedPoints[2], - c1, c2, c3, - t1, t2, t3, - texture); + LineBatch b; + b.task.structure = olc::Structure::LineList; + return b; } -bool olc::Draw2D::swClipLine(olc::vf2d& p1, olc::vf2d& p2, const olc::vf2d& vMin, const olc::vf2d& vMax) +const GPUTask& olc::Draw::Batch(olc::LineBatch& batch, const olc::Pixel tint) { - // 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; - }; + batch.task.tint = tint; + return vecGPUTasks.data.emplace_back(batch.task); +} - int s1 = Segment(p1), s2 = Segment(p2); +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, tint); +} - 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; +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.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; } -bool olc::Draw2D::swClipWeightedLine(olc::vf2d& v0, olc::vf2d& v1, const olc::vf2d& vMin, const olc::vf2d& vMax, float& w0, float& w1) +const LineBatch& olc::Draw::Rect(olc::LineBatch& batch, const olc::vf2d& pos, const olc::vf2d& size, const olc::Pixel col, const olc::Pixel tint) { - // Liang-Barsky line clipping algorithm adapted for weighted lines - // https://en.wikipedia.org/wiki/Liang%E2%80%93Barsky_algorithm - - olc::vf2d diff = v1 - v0; + return Rect(batch, pos, size, col, col, col, col, tint); +} - float p[4] = { -diff.x, diff.x, -diff.y, diff.y }; +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); - 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; + 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); - 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; - } - } - } + return batch; +} - if (w1 < w0) - return false; // Line is outside the clipping boundary +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, 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; +} - // Return new line segment ends - olc::vf2d v = v0; - v0 = v + (diff * w0); - v1 = v + (diff * w1); +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, 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; +} - // Line has visible pixels inside clipping boundary - return true; +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, tint, nFacets); } -std::pair olc::Draw2D::swBaryFillTriangle(const olc::vi2d& v1, const olc::vi2d& v2, const olc::vi2d& v3) +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) { - // 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; + return FilledEllipse(batch, pos, radius, radius, col, tint, nFacets); +} - if (nHeight <= 0) - return { 0, 0 }; // Degenerate triangle +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, tint, nFacets); +} - // 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. +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 - // Get visible height of triangle - int32_t y_min = std::max(0, nMinY); - int32_t y_max = std::min(nMaxY, pTarget->Size().y); + if (nFacets != int32_t(buffUnitCirclePoints.data.size() - 1)) + RedefineUnitCircleBuffer(nFacets); - // Zero out scanline buffer (by resetting min and max values) - for (int32_t y = y_min; y < y_max; y++) + for (int32_t i = 0; i <= nFacets; i++) { - vScanlines[y].nMin = std::numeric_limits::max(); - vScanlines[y].nMax = std::numeric_limits::min(); + 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, tint); } - // 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; + return batch; +} - // Ensure p0.y < p1.y - bool swapped = false; - if (p0.y > p1.y) - { - std::swap(p0, p1); - swapped = true; - } +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, tint, nFacets); +} - // 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); +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 - // 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)); + if (nFacets != int32_t(buffUnitCirclePoints.data.size() - 1)) + RedefineUnitCircleBuffer(nFacets); - // interpolation along edge - // Note: We may need to do this differently when clipping - float t = (y - p0.y) * dy_step; + 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 }; - std::array bary = { 0.0f, 0.0f, 0.0f }; + 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 }; - // 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; - } + FilledTriangle(batch, pos, p1, p2, colInner, colOuter, colOuter, tint); + } - // Update scanline extents and barycentric coords - if (ix < vScanlines[y].nMin) - { - vScanlines[y].nMin = ix; - vScanlines[y].fBaryMin = bary; - } + return batch; +} - if (ix > vScanlines[y].nMax) - { - vScanlines[y].nMax = ix; - vScanlines[y].fBaryMax = bary; - } - nScanline++; - } +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(); - x += dx_step; - } + olc::vf2d adjustedPos = pos + olc::vf2d(radius, radius); + olc::vf2d adjustedSize = size - olc::vf2d(2.0f * radius, 2.0f * radius); - return nScanline; - }; + // 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) }); + } - // Rasterise triangle edges into scanline buffer - scanEdge(v1, v2, 0, 1); - scanEdge(v1, v3, 0, 2); - scanEdge(v2, v3, 1, 2); + // 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) }); + } - return { y_min, y_max }; -} + // 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) }); + } -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(); + // 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) }); + } - auto [y_min, y_max] = swBaryFillTriangle(v1, v2, v3); + buffPoints.data.push_back({ adjustedPos.x - radius, adjustedPos.y }); - // Now draw the scanlines - for (int32_t y = y_min; y < y_max; y++) + for (size_t i = 0; i < buffPoints.data.size() - 1; i++) { - const auto& scanline = vScanlines[y]; - - int32_t xStart = scanline.nMin; - int32_t xEnd = scanline.nMax; + Line(batch, buffPoints.data[i], col, buffPoints.data[i + 1], col, tint); + } - int32_t x_min = std::max(0, xStart); - int32_t x_max = std::min(xEnd, pTarget->Size().x); + return batch; +} - 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]); +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, tint); +} - float b0 = scanline.fBaryMin[0]; - float b1 = scanline.fBaryMin[1]; - float b2 = scanline.fBaryMin[2]; +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, tint); + Line(batch, p2, c2, p3, c3, tint); + Line(batch, p3, c3, p1, c1, tint); + return batch; +} - 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; - } - } +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, tint); +} - return; +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.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; } -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) + +const LineBatch& olc::Draw::Polygon(olc::LineBatch& batch, const std::vector& vecPoints, const olc::Pixel col, const olc::Pixel tint) { - // We are writing to the target image, so make sure its memory resident (and up to date) - PrepareTargetForSW(); - PrepareImageForSW(texture); + for (size_t i = 0; i < vecPoints.size(); i++) + { + Line(batch, vecPoints[i], col, vecPoints[(i + 1) % vecPoints.size()], col, tint); + } - auto [y_min, y_max] = swBaryFillTriangle(v1, v2, v3); + return batch; +} - // Now draw the scanlines - for (int32_t y = y_min; y < y_max; y++) +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++) { - const auto& scanline = vScanlines[y]; + Line(batch, vecPoints[i], vecColours[i], vecPoints[(i + 1) % vecPoints.size()], vecColours[(i + 1) % vecColours.size()], tint); + } - int32_t xStart = scanline.nMin; - int32_t xEnd = scanline.nMax; + return batch; +} - int32_t x_min = std::max(0, xStart); - int32_t x_max = std::min(xEnd, pTarget->Size().x); +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} }; + }; - 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]); + // 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); + } - float b0 = scanline.fBaryMin[0]; - float b1 = scanline.fBaryMin[1]; - float b2 = scanline.fBaryMin[2]; + 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); - 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 (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; - 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)); + case olc::Structure::Strip: + { + size_t idx = batch.task.vertexBuffer.size(); + batch.task.vertexBuffer.resize(batch.task.vertexBuffer.size() + (vecPoints.size() - 2) * 3); - olc::vf2d uv = olc::vf2d( - b0 * t1.x + b1 * t2.x + b2 * t3.x, - b0 * t1.y + b1 * t2.y + b2 * t3.y); + 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]); + } - - // 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; - } + default: + break; } - return; + return batch; } -void olc::Draw2D::swRasterShadedLine(const olc::vi2d& v1, const olc::vi2d& v2, const olc::Pixel c1, const olc::Pixel c2) +const ImageBatch& olc::Draw::Image(ImageBatch& batch, olc::ImageRegion image, const olc::vf2d& pos, const olc::vf2d& scale, const olc::Pixel tint) { - 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; - }; + // Add quad to existing task + olc::vf2d size = image.regionsize * scale; - // 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; - }; + 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 }); - // Clip line to draw target - olc::vf2d clipped_p1 = v1; - olc::vf2d clipped_p2 = v2; + //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} }); + 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; +} - // If line is completely outside bounds, exit - //if (!swClipLine(clipped_p1, clipped_p2, { 0,0 }, pTarget->Size())) - //return; +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; - float w0=0, w1=1; - if (!swClipWeightedLine(clipped_p1, clipped_p2, { 0,0 }, pTarget->Size(), w0, w1)) - return; + 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; - // Move to integer space - olc::vi2d ip1 = clipped_p1; - olc::vi2d ip2 = clipped_p2; - olc::vi2d pixel; + 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); - // Calculate deltas - int dx = ip2.x - ip1.x; - int dy = ip2.y - ip1.y; - int absDx = std::abs(dx); - int absDy = std::abs(dy); + 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]); - // Determine dominant axis - bool xMajor = absDx >= absDy; - int steps = xMajor ? absDx : absDy; + //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; +} - // Handle degenerate case (single pixel) - if (steps == 0) +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) { - 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); + 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::Pixel cStart = olc::PixelLerp(c1, c2, w0); - olc::Pixel cEnd = olc::PixelLerp(c1, c2, w1); + olc::vf2d center; + if (!(rn < 0.f || rn > 1.f || sn < 0.f || sn > 1.f)) + center = vTL + rn * (vBR - vTL); - // Starting position and color interpolation parameter - float x = float(ip1.x); - float y = float(ip1.y); - float t = 0.0f; + std::array d = { { + (vTL - center).mag(), + (vTR - center).mag(), + (vBR - center).mag(), + (vBL - center).mag(), + } }; - // 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); + 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], + } }; - // Plot pixel - if(rol()) - Plot((int)std::round(x), (int)std::round(y), col); + olc::vf2d p0 = transformAffine.forward(vTL); + olc::vf2d p1 = transformAffine.forward(vTR); + olc::vf2d p2 = transformAffine.forward(vBR); + olc::vf2d p3 = transformAffine.forward(vBL); - // Step to next pixel - x += xStep; - y += yStep; - t += colorStep; + //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); +} - return; +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_DRAW2D_IMPLEMENTED 1 + +#define PGE_DRAW_IMPLEMENTED 1 #endif #if defined(OLC_PGE3_APPLICATION) && !defined(PGE_CORE_IMPLEMENTED) namespace olc { + PGEWindow::PGEWindow() : Window(), draw() + { + } + bool PGEWindow::Create(const olc::vi2d& vScreenSize, const olc::vi2d& vPixelSize) { //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 @@ -14833,49 +17033,72 @@ namespace olc bool PGEWindow::olc_WindowUpdate(const float fElapsedTime, const float fTotalElapsedTime) { + float fDT = fElapsedTime; + // Input Changes mouse.UpdateState(); keyboard.UpdateState(); draw.SetGPU(pRenderer); - draw.SetTarget(GetDefaultImage()); + 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 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 + } + + // Finialise any outstanding tasks + draw.ProcessGPUTasks(); + + if (GetScreen().GetConfig().MSAA) { - // User has confirmed window destruction by returning true - bShouldRemove = true; + pRenderer->ResolveMSAA(uint32_t(GetScreen().GetGPUID())); } - else - bRequestToClose = false; // User vetoed closure + + draw.ResetShader(); + draw.WorldReset(); } + for (const auto& pgex : vecWindowExtensions) + { + if (pgex->OnAfterUserUpdate(this, fDT)) + { + // Finialise any outstanding tasks + draw.ProcessGPUTasks(); - // Finialise any outstanding tasks - draw.ProcessGPUTasks(); + if (GetScreen().GetConfig().MSAA) + { + pRenderer->ResolveMSAA(uint32_t(GetScreen().GetGPUID())); + } - if (GetDefaultImage().GetConfig().MSAA) - { - pRenderer->ResolveMSAA(uint32_t(GetDefaultImage().GetGPUID())); + draw.ResetShader(); + draw.WorldReset(); + } } - 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 === @@ -14883,7 +17106,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); @@ -14906,10 +17129,13 @@ 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); - 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 @@ -14919,6 +17145,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 @@ -14995,12 +17227,12 @@ namespace olc pImageLoader = imload; } - olc::Image& PGEWindow::GetDefaultImage() + olc::Image& PGEWindow::GetScreen() { return imgPrimary; } - olc::Draw2D& PGEWindow::GetDraw() + olc::Draw& PGEWindow::GetDraw() { return draw; } @@ -15017,7 +17249,14 @@ namespace olc const olc::vi2d& PGEWindow::ScreenSize() { - return GetDefaultImage().Size(); + 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) @@ -15031,8 +17270,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; } @@ -15061,6 +17300,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. @@ -15071,31 +17329,7 @@ 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( - olc::host::Host_Android::androidApp->activity->assetManager - ); -#endif + imageloader = std::make_unique(); // Allow host to prepare itself return host->OnApplicationStart(this); @@ -15137,7 +17371,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) @@ -15157,7 +17391,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); @@ -15165,7 +17399,24 @@ namespace olc // Prepare Draw2D system draw.SetGPU(gpu.get()); gpu->ApplyDefaultShader(); - draw.SetTarget(GetDefaultImage()); + 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()) @@ -15178,7 +17429,24 @@ namespace olc draw.ProcessGPUTasks(); // Set to known default state - draw.SetTarget(GetDefaultImage()); + draw.SetTarget(GetScreen()); + 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(); @@ -15231,7 +17499,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; @@ -15244,9 +17512,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 @@ -15308,6 +17594,12 @@ namespace olc #endif } + bool PixelGameEngine::InstallSystemExtension(olc::PGESystemExtension* pgex) + { + vecSystemExtensions.push_back(pgex); + return pgex->OnInstall(this); + } + } #define PGE_CORE_IMPLEMENTED 1 #endif @@ -15875,9 +18167,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; } @@ -15953,6 +18245,26 @@ 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); + } + + void Window::ShowFullScreen(const bool bFullScreen) + { + pHost->SetFullScreen(this, bFullScreen); + } + + bool Window::IsFocused() const + { + return bWindowIsFocused; + } + }; #define PGE_WINDOW_IMPLEMENTED 1 #endif @@ -16229,7 +18541,7 @@ namespace olc::imload } #endif -#if OLC_HOST == OLC_HOST_ANDROID +#if OLC_IMAGELOADER == OLC_IMAGELOADER_NDK_IMAGEDECODER #include #include #include @@ -16239,7 +18551,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 ); @@ -16375,6 +18687,94 @@ 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) + { + 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) + { + 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) + { + 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 diff --git a/utilities/olcUTIL3_Camera2D.h b/utilities/olcUTIL3_Camera2D.h index 1ff9ca9e..b6839154 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,109 @@ namespace olc::utils m_vViewPos = m_vViewPos.max(m_vWorldBoundaryPos).min(m_vWorldBoundaryPos + m_vWorldBoundarySize - m_vViewSize); } + if (bUpdateTransform) + { + transform.identity(); + transform.scale(m_vViewScale); + transform.translate(-m_vViewPos); + } + + 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; + } + + // 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 posWorldBefore = vMouseWorldPos; + + // If right mouse button held, we are rotating + //if (mouse.GetButton(1).bHeld) + //{ + // // Adjust rotation depending on wheel direction + //float fRotateDelta = (mouse.GetWheel() > 0) ? 0.1f : -0.1f; + //transform.rotate(fRotateDelta, posWorldBeforeRotate); + //} + //else + //{ + // Adjust scale depending on wheel direction + 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 posWorldAfter = transform.inverse(mouse.GetPosition()); + + // Adjust translation to keep mouse position stable + auto posScreenDisplacement = posWorldBefore - posWorldAfter; + + // Apply adjustment + 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; + } + + protected: // Position of camera focus point in the world olc::vf2d m_vPosition; @@ -251,6 +354,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 +372,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