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