From dabb85f936efa40de1267f08db8cf6ecc54e25a6 Mon Sep 17 00:00:00 2001 From: ApfelTeeSaft <91074565+ApfelTeeSaft@users.noreply.github.com> Date: Fri, 28 Nov 2025 14:13:34 +0100 Subject: [PATCH] Implement Portable Multi Platform layer Implement Various Platforms --- .gitattributes | 2 + CMakeLists.txt | 23 +- include/wv/app/App.h | 19 +- include/wv/app/PlatformEntryPoint.h | 196 ++++++++ include/wv/platform/IGraphicsContext.h | 77 ++++ include/wv/platform/IPlatform.h | 126 +++++ include/wv/platform/InputState.h | 178 ++++++++ include/wv/platform/PlatformConfig.h | 241 ++++++++++ src/app/App.cpp | 98 +++- .../Android/AndroidGraphicsContext.cpp | 278 +++++++++++ src/platform/Android/AndroidGraphicsContext.h | 78 ++++ src/platform/Android/AndroidPlatform.cpp | 432 ++++++++++++++++++ src/platform/Android/AndroidPlatform.h | 111 +++++ .../Desktop/DesktopGraphicsContext.cpp | 200 ++++++++ src/platform/Desktop/DesktopGraphicsContext.h | 61 +++ src/platform/Desktop/DesktopPlatform.cpp | 296 ++++++++++++ src/platform/Desktop/DesktopPlatform.h | 71 +++ .../Nintendo/GameCubeGraphicsContext.h | 43 ++ src/platform/Nintendo/GameCubePlatform.h | 48 ++ src/platform/Nintendo/SwitchGraphicsContext.h | 45 ++ src/platform/Nintendo/SwitchPlatform.cpp | 184 ++++++++ src/platform/Nintendo/SwitchPlatform.h | 68 +++ src/platform/Nintendo/WiiGraphicsContext.h | 43 ++ src/platform/Nintendo/WiiPlatform.h | 48 ++ src/platform/Nintendo/WiiUGraphicsContext.h | 43 ++ src/platform/Nintendo/WiiUPlatform.h | 40 ++ src/platform/PS3/PS3GraphicsContext.cpp | 229 ++++++++++ src/platform/PS3/PS3GraphicsContext.h | 60 +++ src/platform/PS3/PS3Platform.cpp | 223 +++++++++ src/platform/PS3/PS3Platform.h | 65 +++ src/platform/PS4/PS4GraphicsContext.h | 43 ++ src/platform/PS4/PS4Platform.h | 41 ++ src/platform/PlatformFactory.cpp | 95 ++++ src/platform/Xbox/XboxSeriesGraphicsContext.h | 43 ++ src/platform/Xbox/XboxSeriesPlatform.h | 49 ++ src/platform/iOS/iOSGraphicsContext.h | 43 ++ src/platform/iOS/iOSPlatform.h | 45 ++ src/threading/ThreadPool.cpp | 2 - 38 files changed, 3961 insertions(+), 26 deletions(-) create mode 100644 .gitattributes create mode 100644 include/wv/app/PlatformEntryPoint.h create mode 100644 include/wv/platform/IGraphicsContext.h create mode 100644 include/wv/platform/IPlatform.h create mode 100644 include/wv/platform/InputState.h create mode 100644 include/wv/platform/PlatformConfig.h create mode 100644 src/platform/Android/AndroidGraphicsContext.cpp create mode 100644 src/platform/Android/AndroidGraphicsContext.h create mode 100644 src/platform/Android/AndroidPlatform.cpp create mode 100644 src/platform/Android/AndroidPlatform.h create mode 100644 src/platform/Desktop/DesktopGraphicsContext.cpp create mode 100644 src/platform/Desktop/DesktopGraphicsContext.h create mode 100644 src/platform/Desktop/DesktopPlatform.cpp create mode 100644 src/platform/Desktop/DesktopPlatform.h create mode 100644 src/platform/Nintendo/GameCubeGraphicsContext.h create mode 100644 src/platform/Nintendo/GameCubePlatform.h create mode 100644 src/platform/Nintendo/SwitchGraphicsContext.h create mode 100644 src/platform/Nintendo/SwitchPlatform.cpp create mode 100644 src/platform/Nintendo/SwitchPlatform.h create mode 100644 src/platform/Nintendo/WiiGraphicsContext.h create mode 100644 src/platform/Nintendo/WiiPlatform.h create mode 100644 src/platform/Nintendo/WiiUGraphicsContext.h create mode 100644 src/platform/Nintendo/WiiUPlatform.h create mode 100644 src/platform/PS3/PS3GraphicsContext.cpp create mode 100644 src/platform/PS3/PS3GraphicsContext.h create mode 100644 src/platform/PS3/PS3Platform.cpp create mode 100644 src/platform/PS3/PS3Platform.h create mode 100644 src/platform/PS4/PS4GraphicsContext.h create mode 100644 src/platform/PS4/PS4Platform.h create mode 100644 src/platform/PlatformFactory.cpp create mode 100644 src/platform/Xbox/XboxSeriesGraphicsContext.h create mode 100644 src/platform/Xbox/XboxSeriesPlatform.h create mode 100644 src/platform/iOS/iOSGraphicsContext.h create mode 100644 src/platform/iOS/iOSPlatform.h diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/CMakeLists.txt b/CMakeLists.txt index 6961518..e15e42b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,26 @@ endmacro() # Add glad _add_engine_git_module(glad https://github.com/Dav1dde/glad.git 5bf3eda) +# Platform-specific sources +set(PLATFORM_SOURCES "") + +if(PLATFORM_WINDOWS OR PLATFORM_LINUX OR PLATFORM_MACOS) + list(APPEND PLATFORM_SOURCES + src/platform/Desktop/DesktopPlatform.cpp + src/platform/Desktop/DesktopGraphicsContext.cpp + ) + # Desktop needs GLAD + list(APPEND PLATFORM_SOURCES ${glad_SOURCE_DIR}/src/glad.c) +endif() + +# NOTE: Console and mobile platforms need their respective SDKs and toolchains +# These are provided as templates and will compile when the appropriate SDK is available + +# Platform factory (always included) +list(APPEND PLATFORM_SOURCES + src/platform/PlatformFactory.cpp +) + # Create library add_library(WVCore STATIC src/Logger.cpp @@ -43,7 +63,8 @@ add_library(WVCore STATIC src/threading/ThreadPool.cpp - ${glad_SOURCE_DIR}/src/glad.c + # Platform abstraction sources + ${PLATFORM_SOURCES} ) #target_precompile_headers(WVCore PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include/wv/wvpch.h) diff --git a/include/wv/app/App.h b/include/wv/app/App.h index 508fd54..0af3c97 100644 --- a/include/wv/app/App.h +++ b/include/wv/app/App.h @@ -1,7 +1,13 @@ #pragma once +#include + namespace WillowVox { + // Forward declarations + class IPlatform; + class IGraphicsContext; + extern const char* appWindowName; extern int appDefaultWindowX; extern int appDefaultWindowY; @@ -12,13 +18,24 @@ namespace WillowVox void Run(); virtual void Start() {} - virtual void Update() {} + virtual void Update(const InputState& input) {} virtual void Render() {} static float m_deltaTime; + // Access to platform and graphics context + IPlatform* GetPlatform() const { return m_platform; } + IGraphicsContext* GetGraphicsContext() const { return m_graphicsContext; } + + protected: + // Derived apps can access input state + InputState m_inputState; + private: static float m_lastFrame; + + IPlatform* m_platform = nullptr; + IGraphicsContext* m_graphicsContext = nullptr; }; App* CreateApp(); diff --git a/include/wv/app/PlatformEntryPoint.h b/include/wv/app/PlatformEntryPoint.h new file mode 100644 index 0000000..6748da6 --- /dev/null +++ b/include/wv/app/PlatformEntryPoint.h @@ -0,0 +1,196 @@ +#pragma once + +/** + * Platform-Agnostic Entry Point + * + * This header provides the correct entry point for each platform. + * Game code should include this instead of defining main() manually. + * + * Usage: + * #include + * + * WillowVox::App* WillowVox::CreateApp() + * { + * return new MyGameApp(); + * } + */ + +#include +#include + +// Forward declare CreateApp +extern WillowVox::App* WillowVox::CreateApp(); + +// ============================================================================ +// Desktop Platforms (Windows, Linux, macOS) +// ============================================================================ + +#if defined(PLATFORM_DESKTOP) + +int main(int argc, char** argv) +{ + auto app = WillowVox::CreateApp(); + app->Run(); + delete app; + return 0; +} + +// ============================================================================ +// Android +// ============================================================================ + +#elif defined(PLATFORM_ANDROID) + +#include + +void android_main(struct android_app* state) +{ + // Set up the android app state + // The platform implementation will handle this + + auto app = WillowVox::CreateApp(); + + // Android apps need to be tied to the android_app state + // This is handled internally by AndroidPlatform + + app->Run(); + delete app; +} + +// ============================================================================ +// iOS +// ============================================================================ + +#elif defined(PLATFORM_IOS) + +// iOS uses UIApplicationMain, but we can provide a standard main() +// The actual setup happens in the platform layer + +int main(int argc, char** argv) +{ + // iOS-specific initialization happens in the platform layer + auto app = WillowVox::CreateApp(); + app->Run(); + delete app; + return 0; +} + +// ============================================================================ +// PlayStation 3 +// ============================================================================ + +#elif defined(PLATFORM_PS3) + +// PS3 homebrew standard main entry point +int main(int argc, char** argv) +{ + auto app = WillowVox::CreateApp(); + app->Run(); + delete app; + return 0; +} + +// ============================================================================ +// PlayStation 4 +// ============================================================================ + +#elif defined(PLATFORM_PS4) + +// PS4 homebrew main entry point +int main(int argc, char** argv) +{ + auto app = WillowVox::CreateApp(); + app->Run(); + delete app; + return 0; +} + +// ============================================================================ +// Nintendo Wii +// ============================================================================ + +#elif defined(PLATFORM_WII) + +// Wii homebrew main entry point (devkitPPC) +int main(int argc, char** argv) +{ + auto app = WillowVox::CreateApp(); + app->Run(); + delete app; + return 0; +} + +// ============================================================================ +// Nintendo GameCube +// ============================================================================ + +#elif defined(PLATFORM_GAMECUBE) + +// GameCube homebrew main entry point (devkitPPC) +int main(int argc, char** argv) +{ + auto app = WillowVox::CreateApp(); + app->Run(); + delete app; + return 0; +} + +// ============================================================================ +// Nintendo Wii U +// ============================================================================ + +#elif defined(PLATFORM_WIIU) + +// Wii U homebrew main entry point (devkitPPC + wut) +int main(int argc, char** argv) +{ + auto app = WillowVox::CreateApp(); + app->Run(); + delete app; + return 0; +} + +// ============================================================================ +// Nintendo Switch +// ============================================================================ + +#elif defined(PLATFORM_SWITCH) + +// Switch homebrew main entry point (devkitA64 + libnx) +int main(int argc, char** argv) +{ + auto app = WillowVox::CreateApp(); + app->Run(); + delete app; + return 0; +} + +// ============================================================================ +// Xbox Series X/S (Dev Mode / GDK) +// ============================================================================ + +#elif defined(PLATFORM_XBOX_SERIES_DEV) + +// Xbox uses a different entry point for UWP/GDK +// This may need adjustment based on the specific Xbox SDK being used + +int main(int argc, char** argv) +{ + auto app = WillowVox::CreateApp(); + app->Run(); + delete app; + return 0; +} + +// For full GDK, might need: +// int __cdecl main(Platform::Array^ args) +// { +// auto app = WillowVox::CreateApp(); +// app->Run(); +// delete app; +// return 0; +// } + +#else + #error "Unsupported platform for entry point!" +#endif diff --git a/include/wv/platform/IGraphicsContext.h b/include/wv/platform/IGraphicsContext.h new file mode 100644 index 0000000..02f2d88 --- /dev/null +++ b/include/wv/platform/IGraphicsContext.h @@ -0,0 +1,77 @@ +#pragma once + +#include +#include + +namespace WillowVox +{ + /** + * Abstract Graphics Context Interface + * + * This interface abstracts the platform-specific graphics initialization, + * window/framebuffer management, and basic rendering operations. + * + * Each platform implements this interface using its native graphics API: + * - Desktop: OpenGL + GLFW + * - Android: OpenGL ES + EGL/ANativeWindow + * - iOS: Metal or OpenGL ES + UIKit + * - PS3: PSGL or GCM + * - PS4: GNM + * - Wii/GC: GX + * - Wii U: GX2 + * - Switch: OpenGL or NVN + * - Xbox: DirectX 12 + */ + class IGraphicsContext + { + public: + virtual ~IGraphicsContext() = default; + + // Initialization and shutdown + virtual bool Initialize(int width, int height, const char* title) = 0; + virtual void Shutdown() = 0; + + // Frame management + virtual void BeginFrame() = 0; + virtual void EndFrame() = 0; + virtual void SwapBuffers() = 0; + + // Clear operations + virtual void Clear(float r, float g, float b, float a) = 0; + virtual void ClearDepth() = 0; + + // Window management + virtual bool ShouldClose() const = 0; + virtual void SetShouldClose(bool shouldClose) = 0; + virtual void GetFramebufferSize(int& width, int& height) const = 0; + virtual void GetWindowSize(int& width, int& height) const = 0; + + // VSync + virtual void SetVSync(bool enabled) = 0; + virtual bool IsVSyncEnabled() const = 0; + + // Time + virtual float GetTime() const = 0; + + // Background color + virtual void SetBackgroundColor(float r, float g, float b, float a) = 0; + + // Platform-specific native handles (optional, for advanced use) + virtual void* GetNativeWindowHandle() const { return nullptr; } + virtual void* GetNativeGraphicsHandle() const { return nullptr; } + + // Viewport + virtual void SetViewport(int x, int y, int width, int height) = 0; + }; + + /** + * Graphics Context Factory + * + * Creates the appropriate graphics context for the current platform. + */ + class GraphicsContextFactory + { + public: + static IGraphicsContext* CreateGraphicsContext(); + }; +} diff --git a/include/wv/platform/IPlatform.h b/include/wv/platform/IPlatform.h new file mode 100644 index 0000000..768f411 --- /dev/null +++ b/include/wv/platform/IPlatform.h @@ -0,0 +1,126 @@ +#pragma once + +#include +#include +#include + +namespace WillowVox +{ + /** + * Abstract Platform Interface + * + * This interface defines the contract that each platform must implement. + * It handles platform-specific initialization, input polling, events, + * and any OS-level operations. + * + * The platform layer is responsible for: + * - Creating and managing the graphics context + * - Polling and translating input to InputState + * - File system access + * - Platform-specific main loop integration + */ + class IPlatform + { + public: + virtual ~IPlatform() = default; + + // Platform lifecycle + virtual bool Initialize() = 0; + virtual void Shutdown() = 0; + + // Input + virtual void PollInput(InputState& outInputState) = 0; + virtual void ResetInputFrameState(InputState& inputState) = 0; + + // Events (platform may have specific event handling) + virtual void ProcessEvents() = 0; + + // Graphics context access + virtual IGraphicsContext* GetGraphicsContext() = 0; + + // File system + virtual const char* GetUserDataPath() const = 0; + virtual const char* GetAssetsPath() const = 0; + + // Platform info + virtual const char* GetPlatformName() const = 0; + virtual InputDeviceType GetPrimaryInputDevice() const = 0; + + // Special platform features + virtual bool HasFeature(const char* featureName) const = 0; + + // Optional: Some platforms may need special per-frame updates + virtual void Update(float deltaTime) {} + }; + + /** + * Platform Factory + * + * Creates the appropriate platform implementation for the current platform. + */ + class PlatformFactory + { + public: + static IPlatform* CreatePlatform(); + }; + + /** + * Platform Entry Point Helpers + * + * Different platforms have different entry points and main loop requirements. + * These macros help define the correct entry point for each platform. + */ + + // Forward declare App + class App; + App* CreateApp(); + + // Desktop platforms (Windows, Linux, macOS) + #if defined(PLATFORM_DESKTOP) + #define WV_PLATFORM_MAIN() \ + int main(int argc, char** argv) + + // Android + #elif defined(PLATFORM_ANDROID) + #define WV_PLATFORM_MAIN() \ + void android_main(struct android_app* state) + + // iOS + #elif defined(PLATFORM_IOS) + #define WV_PLATFORM_MAIN() \ + int main(int argc, char** argv) + + // PS3 + #elif defined(PLATFORM_PS3) + #define WV_PLATFORM_MAIN() \ + int main(int argc, char** argv) + + // PS4 + #elif defined(PLATFORM_PS4) + #define WV_PLATFORM_MAIN() \ + int main(int argc, char** argv) + + // Wii / GameCube + #elif defined(PLATFORM_WII) || defined(PLATFORM_GAMECUBE) + #define WV_PLATFORM_MAIN() \ + int main(int argc, char** argv) + + // Wii U + #elif defined(PLATFORM_WIIU) + #define WV_PLATFORM_MAIN() \ + int main(int argc, char** argv) + + // Nintendo Switch + #elif defined(PLATFORM_SWITCH) + #define WV_PLATFORM_MAIN() \ + int main(int argc, char** argv) + + // Xbox Series X/S Dev Mode + #elif defined(PLATFORM_XBOX_SERIES_DEV) + #define WV_PLATFORM_MAIN() \ + int __cdecl main(Platform::Array^ args) + + #else + #error "Unsupported platform for entry point!" + #endif +} diff --git a/include/wv/platform/InputState.h b/include/wv/platform/InputState.h new file mode 100644 index 0000000..f3e7c51 --- /dev/null +++ b/include/wv/platform/InputState.h @@ -0,0 +1,178 @@ +#pragma once + +#include + +namespace WillowVox +{ + /** + * Platform-Agnostic Input State + * + * This structure represents the abstract input state that the game layer sees. + * All platform-specific inputs (keyboard, mouse, touch, gamepads) are mapped + * to these abstract actions by the platform layer. + */ + + // Abstract input actions that game understands + enum class InputAction + { + // Movement + MoveForward, + MoveBackward, + MoveLeft, + MoveRight, + MoveUp, + MoveDown, + + // Camera/Look + LookUp, + LookDown, + LookLeft, + LookRight, + + // Primary actions + Action1, // Primary action (e.g., attack, break block, confirm) + Action2, // Secondary action (e.g., use item, place block, cancel) + Action3, // Tertiary action (e.g., pick block, special) + Action4, // Quaternary action + + // Menu/UI + MenuOpen, // Open main menu / pause + MenuBack, // Back / escape + MenuConfirm, // Confirm selection + MenuCancel, // Cancel selection + + // Other + Jump, + Crouch, + Sprint, + Interact, + + // Special + CycleLeft, // Cycle items/options left + CycleRight, // Cycle items/options right + + Count + }; + + // Input device types + enum class InputDeviceType + { + Unknown, + KeyboardMouse, + Touchscreen, + Gamepad, + WiiRemote, + PSController, + XboxController, + SwitchController + }; + + // Abstract input state + struct InputState + { + // Digital buttons (pressed this frame) + bool actions[static_cast(InputAction::Count)] = {}; + + // Digital buttons (held down) + bool actionsHeld[static_cast(InputAction::Count)] = {}; + + // Digital buttons (released this frame) + bool actionsReleased[static_cast(InputAction::Count)] = {}; + + // Analog inputs (-1.0 to 1.0) + float moveAxisX = 0.0f; // Left/Right movement + float moveAxisY = 0.0f; // Forward/Backward movement + float lookAxisX = 0.0f; // Horizontal look + float lookAxisY = 0.0f; // Vertical look + + // Mouse/Touch position (normalized 0-1 or pixels depending on platform) + float pointerX = 0.0f; + float pointerY = 0.0f; + float pointerDeltaX = 0.0f; + float pointerDeltaY = 0.0f; + bool pointerDown = false; + + // Scroll/Zoom + float scrollDelta = 0.0f; + + // Device info + InputDeviceType deviceType = InputDeviceType::Unknown; + + // Helper to check if action is pressed this frame + bool IsActionPressed(InputAction action) const + { + return actions[static_cast(action)]; + } + + // Helper to check if action is held + bool IsActionHeld(InputAction action) const + { + return actionsHeld[static_cast(action)]; + } + + // Helper to check if action was released this frame + bool IsActionReleased(InputAction action) const + { + return actionsReleased[static_cast(action)]; + } + + // Reset per-frame input states + void ResetFrameStates() + { + for (int i = 0; i < static_cast(InputAction::Count); ++i) + { + actions[i] = false; + actionsReleased[i] = false; + } + pointerDeltaX = 0.0f; + pointerDeltaY = 0.0f; + scrollDelta = 0.0f; + } + }; + + /** + * Touch Control Layout (for mobile platforms) + * + * Defines virtual buttons and joystick regions for touchscreen controls. + */ + struct TouchControlRegion + { + float x, y, width, height; // Normalized screen coordinates (0-1) + InputAction mappedAction; + bool isJoystick; // If true, this is a virtual joystick + }; + + /** + * Gamepad Button Mapping + * + * Maps physical gamepad buttons to abstract input actions. + * Each platform implements this mapping for their specific controller. + */ + struct GamepadMapping + { + // Button indices vary by platform, but we define standard mappings + // Platform layer translates native button IDs to InputActions + + // Example for standard gamepad: + // Button 0 (A/Cross) -> Action1 + // Button 1 (B/Circle) -> Action2 + // Button 2 (X/Square) -> Action3 + // Button 3 (Y/Triangle) -> Action4 + // Button 4 (LB/L1) -> CycleLeft + // Button 5 (RB/R1) -> CycleRight + // Button 6 (Back/Select) -> MenuBack + // Button 7 (Start) -> MenuOpen + // Button 8 (L3) -> Crouch + // Button 9 (R3) -> Sprint + + static constexpr int MAX_BUTTONS = 32; + InputAction buttonMapping[MAX_BUTTONS]; + + GamepadMapping() + { + // Initialize all to Count (invalid) + for (int i = 0; i < MAX_BUTTONS; ++i) + buttonMapping[i] = InputAction::Count; + } + }; +} diff --git a/include/wv/platform/PlatformConfig.h b/include/wv/platform/PlatformConfig.h new file mode 100644 index 0000000..adfb2ac --- /dev/null +++ b/include/wv/platform/PlatformConfig.h @@ -0,0 +1,241 @@ +#pragma once + +/** + * Platform Detection and Configuration + * + * This header centralizes all platform detection logic and defines + * platform-specific macros used throughout the engine. + * + * Platform Macros Defined: + * - Desktop: PLATFORM_WINDOWS, PLATFORM_LINUX, PLATFORM_MACOS + * - Mobile: PLATFORM_ANDROID, PLATFORM_IOS + * - Consoles: PLATFORM_PS3, PLATFORM_PS4, PLATFORM_WII, PLATFORM_GAMECUBE, + * PLATFORM_WIIU, PLATFORM_SWITCH, PLATFORM_XBOX_SERIES_DEV + * + * Platform Categories: + * - PLATFORM_DESKTOP: Windows, Linux, macOS + * - PLATFORM_MOBILE: Android, iOS + * - PLATFORM_CONSOLE: All console platforms + * - PLATFORM_NINTENDO: Wii, GameCube, Wii U, Switch + * - PLATFORM_PLAYSTATION: PS3, PS4 + */ + +// ============================================================================ +// Desktop Platforms +// ============================================================================ + +#if defined(_WIN32) || defined(_WIN64) || defined(__WIN32__) || defined(__WINDOWS__) + #ifndef PLATFORM_WINDOWS + #define PLATFORM_WINDOWS 1 + #endif + #define PLATFORM_DESKTOP 1 + #define PLATFORM_NAME "Windows" + +#elif defined(__linux__) && !defined(__ANDROID__) + #ifndef PLATFORM_LINUX + #define PLATFORM_LINUX 1 + #endif + #define PLATFORM_DESKTOP 1 + #define PLATFORM_NAME "Linux" + +#elif defined(__APPLE__) && defined(__MACH__) + #include + #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR + #ifndef PLATFORM_IOS + #define PLATFORM_IOS 1 + #endif + #define PLATFORM_MOBILE 1 + #define PLATFORM_NAME "iOS" + #else + #ifndef PLATFORM_MACOS + #define PLATFORM_MACOS 1 + #endif + #define PLATFORM_DESKTOP 1 + #define PLATFORM_NAME "macOS" + #endif + +// ============================================================================ +// Mobile Platforms +// ============================================================================ + +#elif defined(__ANDROID__) + #ifndef PLATFORM_ANDROID + #define PLATFORM_ANDROID 1 + #endif + #define PLATFORM_MOBILE 1 + #define PLATFORM_NAME "Android" + +// ============================================================================ +// PlayStation Consoles (Homebrew) +// ============================================================================ + +#elif defined(__PPU__) || defined(__CELLOS_LV2__) + // PS3 (PSL1GHT toolchain) + #ifndef PLATFORM_PS3 + #define PLATFORM_PS3 1 + #endif + #define PLATFORM_CONSOLE 1 + #define PLATFORM_PLAYSTATION 1 + #define PLATFORM_NAME "PlayStation 3" + +#elif defined(__ORBIS__) || defined(__PS4__) + // PS4 (OpenOrbis toolchain) + #ifndef PLATFORM_PS4 + #define PLATFORM_PS4 1 + #endif + #define PLATFORM_CONSOLE 1 + #define PLATFORM_PLAYSTATION 1 + #define PLATFORM_NAME "PlayStation 4" + +// ============================================================================ +// Nintendo Consoles (devkitPro Homebrew) +// ============================================================================ + +#elif defined(__wii__) || defined(HW_RVL) + // Wii (devkitPPC) + #ifndef PLATFORM_WII + #define PLATFORM_WII 1 + #endif + #define PLATFORM_CONSOLE 1 + #define PLATFORM_NINTENDO 1 + #define PLATFORM_NAME "Nintendo Wii" + +#elif defined(__gamecube__) || defined(HW_DOL) + // GameCube (devkitPPC) + #ifndef PLATFORM_GAMECUBE + #define PLATFORM_GAMECUBE 1 + #endif + #define PLATFORM_CONSOLE 1 + #define PLATFORM_NINTENDO 1 + #define PLATFORM_NAME "Nintendo GameCube" + +#elif defined(__WIIU__) || defined(__wiiu__) + // Wii U (devkitPPC + wut) + #ifndef PLATFORM_WIIU + #define PLATFORM_WIIU 1 + #endif + #define PLATFORM_CONSOLE 1 + #define PLATFORM_NINTENDO 1 + #define PLATFORM_NAME "Nintendo Wii U" + +#elif defined(__SWITCH__) || defined(__switch__) + // Nintendo Switch (devkitA64 + libnx) + #ifndef PLATFORM_SWITCH + #define PLATFORM_SWITCH 1 + #endif + #define PLATFORM_CONSOLE 1 + #define PLATFORM_NINTENDO 1 + #define PLATFORM_NAME "Nintendo Switch" + +// ============================================================================ +// Xbox (Dev Mode / GDK) +// ============================================================================ + +#elif defined(_GAMING_XBOX) || defined(_GAMING_XBOX_SCARLETT) + // Xbox Series X/S in Dev Mode or GDK + #ifndef PLATFORM_XBOX_SERIES_DEV + #define PLATFORM_XBOX_SERIES_DEV 1 + #endif + #define PLATFORM_CONSOLE 1 + #define PLATFORM_XBOX 1 + #define PLATFORM_NAME "Xbox Series X/S (Dev Mode)" + +#else + #error "Unknown or unsupported platform!" +#endif + +// ============================================================================ +// Platform Feature Detection +// ============================================================================ + +// Has mouse & keyboard +#if defined(PLATFORM_DESKTOP) + #define PLATFORM_HAS_MOUSE 1 + #define PLATFORM_HAS_KEYBOARD 1 +#endif + +// Has touchscreen +#if defined(PLATFORM_MOBILE) + #define PLATFORM_HAS_TOUCHSCREEN 1 +#endif + +// Has gamepad/controller +#if defined(PLATFORM_CONSOLE) || defined(PLATFORM_DESKTOP) + #define PLATFORM_HAS_GAMEPAD 1 +#endif + +// Has filesystem access +#if !defined(PLATFORM_WEB) + #define PLATFORM_HAS_FILESYSTEM 1 +#endif + +// ============================================================================ +// Graphics API Hints +// ============================================================================ + +#if defined(PLATFORM_DESKTOP) + // Desktop typically uses OpenGL or Vulkan + #define PLATFORM_GRAPHICS_OPENGL 1 +#elif defined(PLATFORM_ANDROID) + // Android uses OpenGL ES or Vulkan + #define PLATFORM_GRAPHICS_OPENGL_ES 1 +#elif defined(PLATFORM_IOS) + // iOS uses Metal (but we can also use OpenGL ES compatibility) + #define PLATFORM_GRAPHICS_METAL 1 + #define PLATFORM_GRAPHICS_OPENGL_ES 1 +#elif defined(PLATFORM_SWITCH) + // Switch uses OpenGL or native NVN + #define PLATFORM_GRAPHICS_OPENGL 1 +#elif defined(PLATFORM_PS3) + // PS3 uses GCM or PSGL (OpenGL subset) + #define PLATFORM_GRAPHICS_GCM 1 +#elif defined(PLATFORM_PS4) + // PS4 uses GNM + #define PLATFORM_GRAPHICS_GNM 1 +#elif defined(PLATFORM_WII) || defined(PLATFORM_GAMECUBE) + // Wii/GC use GX + #define PLATFORM_GRAPHICS_GX 1 +#elif defined(PLATFORM_WIIU) + // Wii U uses GX2 + #define PLATFORM_GRAPHICS_GX2 1 +#elif defined(PLATFORM_XBOX_SERIES_DEV) + // Xbox uses DirectX 12 + #define PLATFORM_GRAPHICS_DX12 1 +#endif + +// ============================================================================ +// Compiler Attributes +// ============================================================================ + +#if defined(__GNUC__) || defined(__clang__) + #define WV_FORCE_INLINE __attribute__((always_inline)) inline + #define WV_NO_INLINE __attribute__((noinline)) +#elif defined(_MSC_VER) + #define WV_FORCE_INLINE __forceinline + #define WV_NO_INLINE __declspec(noinline) +#else + #define WV_FORCE_INLINE inline + #define WV_NO_INLINE +#endif + +// ============================================================================ +// Debug/Release Configuration +// ============================================================================ + +#if defined(DEBUG) || defined(_DEBUG) || !defined(NDEBUG) + #define WV_DEBUG 1 + #define WV_RELEASE 0 +#else + #define WV_DEBUG 0 + #define WV_RELEASE 1 +#endif + +// ============================================================================ +// Platform-Specific Includes +// ============================================================================ + +// Standard library availability varies by platform +#if defined(PLATFORM_PS3) || defined(PLATFORM_WII) || defined(PLATFORM_GAMECUBE) + // Some older consoles have limited standard library support + #define WV_LIMITED_STL 1 +#endif diff --git a/src/app/App.cpp b/src/app/App.cpp index bb3be27..444862e 100644 --- a/src/app/App.cpp +++ b/src/app/App.cpp @@ -1,9 +1,8 @@ #include #include -#include -#include -#include +#include +#include #include namespace WillowVox @@ -13,39 +12,94 @@ namespace WillowVox void App::Run() { - Logger::EngineLog("Using WillowVox Engine"); - - Renderer::Init(); - Window::InitWindow(appDefaultWindowX, appDefaultWindowY, appWindowName); - auto& window = Window::GetInstance(); - window.SetBackgroundColor(0.1f, 0.1f, 0.1f, 1.0f); + Logger::EngineLog("Using WillowVox Engine"); - Input::Init(); - + // Create platform abstraction + m_platform = PlatformFactory::CreatePlatform(); + if (!m_platform) + { + Logger::EngineError("Failed to create platform abstraction!"); + return; + } + + // Initialize platform + if (!m_platform->Initialize()) + { + Logger::EngineError("Failed to initialize platform!"); + delete m_platform; + return; + } + + Logger::EngineLog("Running on platform: %s", m_platform->GetPlatformName()); + + // Get graphics context + m_graphicsContext = m_platform->GetGraphicsContext(); + if (!m_graphicsContext) + { + Logger::EngineError("Failed to get graphics context!"); + m_platform->Shutdown(); + delete m_platform; + return; + } + + // Initialize graphics + if (!m_graphicsContext->Initialize(appDefaultWindowX, appDefaultWindowY, appWindowName)) + { + Logger::EngineError("Failed to initialize graphics context!"); + m_platform->Shutdown(); + delete m_platform; + return; + } + + m_graphicsContext->SetBackgroundColor(0.1f, 0.1f, 0.1f, 1.0f); + + // Call user's Start function Start(); - while(!window.ShouldClose()) + // Main loop + while(!m_graphicsContext->ShouldClose()) { // Calculate deltaTime - float frameStartTime = Renderer::GetTime(); - float currentFrame = frameStartTime; + float currentFrame = m_graphicsContext->GetTime(); m_deltaTime = currentFrame - m_lastFrame; m_lastFrame = currentFrame; - // Clear window - window.Clear(); + // Process platform events + m_platform->ProcessEvents(); + + // Poll input + m_platform->PollInput(m_inputState); + + // Begin frame + m_graphicsContext->BeginFrame(); + + // Clear + m_graphicsContext->Clear(0.1f, 0.1f, 0.1f, 1.0f); // Client app logic - Update(); + Update(m_inputState); + // Client rendering Render(); - // End-of-frame steps - Input::ResetStates(); - window.SwapBuffers(); - window.PollEvents(); + // End frame + m_graphicsContext->EndFrame(); + + // Swap buffers + m_graphicsContext->SwapBuffers(); + + // Reset per-frame input states + m_platform->ResetInputFrameState(m_inputState); + + // Platform-specific per-frame update + m_platform->Update(m_deltaTime); } - Renderer::Shutdown(); + // Shutdown + m_graphicsContext->Shutdown(); + m_platform->Shutdown(); + delete m_platform; + + Logger::EngineLog("WillowVox Engine shutdown"); } } \ No newline at end of file diff --git a/src/platform/Android/AndroidGraphicsContext.cpp b/src/platform/Android/AndroidGraphicsContext.cpp new file mode 100644 index 0000000..3fa532f --- /dev/null +++ b/src/platform/Android/AndroidGraphicsContext.cpp @@ -0,0 +1,278 @@ +#include "AndroidGraphicsContext.h" +#include + +#ifdef PLATFORM_ANDROID + +#include +#include +#include +#include +#include + +namespace WillowVox +{ + AndroidGraphicsContext::AndroidGraphicsContext() + { + // Get start time + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + m_startTime = ts.tv_sec + ts.tv_nsec / 1000000000.0f; + } + + AndroidGraphicsContext::~AndroidGraphicsContext() + { + Shutdown(); + } + + void AndroidGraphicsContext::SetNativeWindow(ANativeWindow* window) + { + m_nativeWindow = window; + } + + void AndroidGraphicsContext::SetAssetManager(AAssetManager* assetManager) + { + m_assetManager = assetManager; + } + + bool AndroidGraphicsContext::Initialize(int width, int height, const char* title) + { + m_width = width; + m_height = height; + + if (!m_nativeWindow) + { + Logger::EngineError("Android: No native window set!"); + return false; + } + + if (!InitializeEGL()) + { + Logger::EngineError("Failed to initialize EGL"); + return false; + } + + // Enable OpenGL ES features + glEnable(GL_DEPTH_TEST); + glEnable(GL_CULL_FACE); + glCullFace(GL_BACK); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + Logger::EngineLog("Android Graphics Context initialized (OpenGL ES %s)", + glGetString(GL_VERSION)); + + m_initialized = true; + return true; + } + + bool AndroidGraphicsContext::InitializeEGL() + { + // Get default display + EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (display == EGL_NO_DISPLAY) + { + Logger::EngineError("eglGetDisplay failed"); + return false; + } + + // Initialize EGL + if (!eglInitialize(display, nullptr, nullptr)) + { + Logger::EngineError("eglInitialize failed"); + return false; + } + + // Choose config + const EGLint attribs[] = { + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT, + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_BLUE_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_RED_SIZE, 8, + EGL_ALPHA_SIZE, 8, + EGL_DEPTH_SIZE, 24, + EGL_STENCIL_SIZE, 8, + EGL_NONE + }; + + EGLConfig config; + EGLint numConfigs; + if (!eglChooseConfig(display, attribs, &config, 1, &numConfigs)) + { + Logger::EngineError("eglChooseConfig failed"); + return false; + } + + // Create surface + EGLSurface surface = eglCreateWindowSurface(display, config, m_nativeWindow, nullptr); + if (surface == EGL_NO_SURFACE) + { + Logger::EngineError("eglCreateWindowSurface failed"); + return false; + } + + // Create context + const EGLint contextAttribs[] = { + EGL_CONTEXT_CLIENT_VERSION, 3, + EGL_NONE + }; + + EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs); + if (context == EGL_NO_CONTEXT) + { + Logger::EngineError("eglCreateContext failed"); + return false; + } + + // Make current + if (!eglMakeCurrent(display, surface, surface, context)) + { + Logger::EngineError("eglMakeCurrent failed"); + return false; + } + + // Store handles + m_eglDisplay = display; + m_eglSurface = surface; + m_eglContext = context; + + // Get actual surface size + eglQuerySurface(display, surface, EGL_WIDTH, &m_width); + eglQuerySurface(display, surface, EGL_HEIGHT, &m_height); + + // Set VSync + eglSwapInterval(display, m_vsyncEnabled ? 1 : 0); + + return true; + } + + void AndroidGraphicsContext::Shutdown() + { + if (m_initialized) + { + ShutdownEGL(); + m_initialized = false; + } + } + + void AndroidGraphicsContext::ShutdownEGL() + { + if (m_eglDisplay != EGL_NO_DISPLAY) + { + eglMakeCurrent((EGLDisplay)m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + + if (m_eglContext != EGL_NO_CONTEXT) + { + eglDestroyContext((EGLDisplay)m_eglDisplay, (EGLContext)m_eglContext); + m_eglContext = EGL_NO_CONTEXT; + } + + if (m_eglSurface != EGL_NO_SURFACE) + { + eglDestroySurface((EGLDisplay)m_eglDisplay, (EGLSurface)m_eglSurface); + m_eglSurface = EGL_NO_SURFACE; + } + + eglTerminate((EGLDisplay)m_eglDisplay); + m_eglDisplay = EGL_NO_DISPLAY; + } + } + + void AndroidGraphicsContext::BeginFrame() + { + // Nothing specific needed + } + + void AndroidGraphicsContext::EndFrame() + { + // Nothing specific needed + } + + void AndroidGraphicsContext::SwapBuffers() + { + if (m_eglDisplay && m_eglSurface) + { + eglSwapBuffers((EGLDisplay)m_eglDisplay, (EGLSurface)m_eglSurface); + } + } + + void AndroidGraphicsContext::Clear(float r, float g, float b, float a) + { + glClearColor(r, g, b, a); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + } + + void AndroidGraphicsContext::ClearDepth() + { + glClear(GL_DEPTH_BUFFER_BIT); + } + + bool AndroidGraphicsContext::ShouldClose() const + { + return m_shouldClose; + } + + void AndroidGraphicsContext::SetShouldClose(bool shouldClose) + { + m_shouldClose = shouldClose; + } + + void AndroidGraphicsContext::GetFramebufferSize(int& width, int& height) const + { + width = m_width; + height = m_height; + } + + void AndroidGraphicsContext::GetWindowSize(int& width, int& height) const + { + width = m_width; + height = m_height; + } + + void AndroidGraphicsContext::SetVSync(bool enabled) + { + m_vsyncEnabled = enabled; + if (m_eglDisplay) + { + eglSwapInterval((EGLDisplay)m_eglDisplay, enabled ? 1 : 0); + } + } + + bool AndroidGraphicsContext::IsVSyncEnabled() const + { + return m_vsyncEnabled; + } + + float AndroidGraphicsContext::GetTime() const + { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + float currentTime = ts.tv_sec + ts.tv_nsec / 1000000000.0f; + return currentTime - m_startTime; + } + + void AndroidGraphicsContext::SetBackgroundColor(float r, float g, float b, float a) + { + m_clearColor[0] = r; + m_clearColor[1] = g; + m_clearColor[2] = b; + m_clearColor[3] = a; + } + + void* AndroidGraphicsContext::GetNativeWindowHandle() const + { + return m_nativeWindow; + } + + void* AndroidGraphicsContext::GetNativeGraphicsHandle() const + { + return m_eglContext; + } + + void AndroidGraphicsContext::SetViewport(int x, int y, int width, int height) + { + glViewport(x, y, width, height); + } +} + +#endif // PLATFORM_ANDROID diff --git a/src/platform/Android/AndroidGraphicsContext.h b/src/platform/Android/AndroidGraphicsContext.h new file mode 100644 index 0000000..3702e25 --- /dev/null +++ b/src/platform/Android/AndroidGraphicsContext.h @@ -0,0 +1,78 @@ +#pragma once + +#include + +// Forward declarations (avoid including Android headers in header file) +struct ANativeWindow; +struct AAssetManager; + +namespace WillowVox +{ + /** + * Android Graphics Context (OpenGL ES + EGL) + * + * Implements IGraphicsContext for Android using OpenGL ES 3.0+ and EGL + * for context creation. Uses ANativeWindow for the rendering surface. + */ + class AndroidGraphicsContext : public IGraphicsContext + { + public: + AndroidGraphicsContext(); + ~AndroidGraphicsContext() override; + + // IGraphicsContext implementation + bool Initialize(int width, int height, const char* title) override; + void Shutdown() override; + + void BeginFrame() override; + void EndFrame() override; + void SwapBuffers() override; + + void Clear(float r, float g, float b, float a) override; + void ClearDepth() override; + + bool ShouldClose() const override; + void SetShouldClose(bool shouldClose) override; + + void GetFramebufferSize(int& width, int& height) const override; + void GetWindowSize(int& width, int& height) const override; + + void SetVSync(bool enabled) override; + bool IsVSyncEnabled() const override; + + float GetTime() const override; + + void SetBackgroundColor(float r, float g, float b, float a) override; + + void* GetNativeWindowHandle() const override; + void* GetNativeGraphicsHandle() const override; + + void SetViewport(int x, int y, int width, int height) override; + + // Android-specific + void SetNativeWindow(ANativeWindow* window); + void SetAssetManager(AAssetManager* assetManager); + + private: + bool InitializeEGL(); + void ShutdownEGL(); + + ANativeWindow* m_nativeWindow = nullptr; + AAssetManager* m_assetManager = nullptr; + + // EGL handles (using void* to avoid including EGL headers) + void* m_eglDisplay = nullptr; + void* m_eglSurface = nullptr; + void* m_eglContext = nullptr; + + bool m_initialized = false; + bool m_shouldClose = false; + bool m_vsyncEnabled = true; + + float m_clearColor[4] = {0.1f, 0.1f, 0.1f, 1.0f}; + int m_width = 0; + int m_height = 0; + + float m_startTime = 0.0f; + }; +} diff --git a/src/platform/Android/AndroidPlatform.cpp b/src/platform/Android/AndroidPlatform.cpp new file mode 100644 index 0000000..1e5e36f --- /dev/null +++ b/src/platform/Android/AndroidPlatform.cpp @@ -0,0 +1,432 @@ +#include "AndroidPlatform.h" +#include + +#ifdef PLATFORM_ANDROID + +#include +#include +#include +#include +#include + +namespace WillowVox +{ + AndroidPlatform::AndroidPlatform() + { + } + + AndroidPlatform::~AndroidPlatform() + { + Shutdown(); + } + + void AndroidPlatform::SetAndroidApp(android_app* app) + { + m_androidApp = app; + } + + bool AndroidPlatform::Initialize() + { + Logger::EngineLog("Initializing Android Platform"); + + if (!m_androidApp) + { + Logger::EngineError("Android app not set!"); + return false; + } + + m_graphicsContext = std::make_unique(); + m_graphicsContext->SetNativeWindow(m_androidApp->window); + m_graphicsContext->SetAssetManager(m_androidApp->activity->assetManager); + + // Get screen dimensions + if (m_androidApp->window) + { + m_screenWidth = ANativeWindow_getWidth(m_androidApp->window); + m_screenHeight = ANativeWindow_getHeight(m_androidApp->window); + Logger::EngineLog("Screen size: %dx%d", m_screenWidth, m_screenHeight); + } + + // Setup virtual controls + SetupVirtualControls(); + + return true; + } + + void AndroidPlatform::SetupVirtualControls() + { + m_virtualControls.clear(); + + // Left virtual joystick (bottom left corner) + // Position at (0.15, 0.85) with radius 0.12 + m_virtualControls.push_back({0.15f, 0.85f, 0.12f, InputAction::Count, true}); + + // Action buttons (bottom right) + // A button (primary action) + m_virtualControls.push_back({0.85f, 0.85f, 0.06f, InputAction::Action1, false}); + + // B button (secondary action) + m_virtualControls.push_back({0.75f, 0.80f, 0.06f, InputAction::Action2, false}); + + // Jump button (X) + m_virtualControls.push_back({0.85f, 0.75f, 0.06f, InputAction::Jump, false}); + + // Menu button (top left) + m_virtualControls.push_back({0.10f, 0.10f, 0.05f, InputAction::MenuOpen, false}); + + Logger::EngineLog("Virtual controls setup complete"); + } + + void AndroidPlatform::Shutdown() + { + m_graphicsContext.reset(); + Logger::EngineLog("Android Platform shutdown"); + } + + void AndroidPlatform::ProcessEvents() + { + if (!m_androidApp) + return; + + // Process pending events + int events; + struct android_poll_source* source; + + // Non-blocking poll + while (ALooper_pollAll(0, nullptr, &events, (void**)&source) >= 0) + { + if (source != nullptr) + { + source->process(m_androidApp, source); + } + + // Check if we're exiting + if (m_androidApp->destroyRequested != 0) + { + m_graphicsContext->SetShouldClose(true); + } + } + } + + int32_t AndroidPlatform::HandleInputEvent(AInputEvent* event) + { + int32_t eventType = AInputEvent_getType(event); + + if (eventType == AINPUT_EVENT_TYPE_MOTION) + { + int32_t action = AMotionEvent_getAction(event); + int32_t actionMasked = action & AMOTION_EVENT_ACTION_MASK; + size_t pointerIndex = (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) + >> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; + int32_t pointerId = AMotionEvent_getPointerId(event, pointerIndex); + + float x = AMotionEvent_getX(event, pointerIndex); + float y = AMotionEvent_getY(event, pointerIndex); + + // Normalize to 0-1 + float normX = x / m_screenWidth; + float normY = y / m_screenHeight; + + switch (actionMasked) + { + case AMOTION_EVENT_ACTION_DOWN: + case AMOTION_EVENT_ACTION_POINTER_DOWN: + { + TouchPoint* touch = AllocateTouchPoint(pointerId); + if (touch) + { + touch->x = normX; + touch->y = normY; + touch->startX = normX; + touch->startY = normY; + } + break; + } + + case AMOTION_EVENT_ACTION_MOVE: + { + // Update all active touch points + size_t pointerCount = AMotionEvent_getPointerCount(event); + for (size_t i = 0; i < pointerCount; ++i) + { + int32_t id = AMotionEvent_getPointerId(event, i); + TouchPoint* touch = GetTouchPoint(id); + if (touch) + { + touch->x = AMotionEvent_getX(event, i) / m_screenWidth; + touch->y = AMotionEvent_getY(event, i) / m_screenHeight; + } + } + break; + } + + case AMOTION_EVENT_ACTION_UP: + case AMOTION_EVENT_ACTION_POINTER_UP: + case AMOTION_EVENT_ACTION_CANCEL: + { + ReleaseTouchPoint(pointerId); + break; + } + } + + return 1; // Event handled + } + + return 0; // Event not handled + } + + void AndroidPlatform::PollInput(InputState& outInputState) + { + ProcessTouchInput(outInputState); + + // Copy button states + std::memcpy(m_buttonPrevStates, m_buttonStates, sizeof(m_buttonStates)); + + outInputState.deviceType = InputDeviceType::Touchscreen; + } + + void AndroidPlatform::ProcessTouchInput(InputState& outInputState) + { + // Reset per-frame state + m_lookDeltaX = 0.0f; + m_lookDeltaY = 0.0f; + m_joystickX = 0.0f; + m_joystickY = 0.0f; + + // Reset button states + for (int i = 0; i < static_cast(InputAction::Count); ++i) + { + m_buttonStates[i] = false; + } + + // Process each active touch point + for (int i = 0; i < MAX_TOUCH_POINTS; ++i) + { + TouchPoint& touch = m_touchPoints[i]; + if (!touch.active) + continue; + + // Check virtual controls + bool handled = false; + + // Virtual joystick (left side) + if (touch.startX < 0.5f && touch.id == m_joystickTouchId) + { + // Calculate joystick displacement from start position + float dx = touch.x - touch.startX; + float dy = touch.y - touch.startY; + + // Normalize joystick input (clamp to radius) + float dist = std::sqrt(dx * dx + dy * dy); + float maxDist = 0.12f; // Same as virtual control radius + + if (dist > maxDist) + { + dx = (dx / dist) * maxDist; + dy = (dy / dist) * maxDist; + } + + m_joystickX = dx / maxDist; + m_joystickY = dy / maxDist; + + handled = true; + } + else if (touch.startX < 0.5f && m_joystickTouchId == -1) + { + // New touch on left side - assign to joystick + m_joystickTouchId = touch.id; + handled = true; + } + + // Look control (right side) + if (touch.startX >= 0.5f && touch.id == m_lookTouchId) + { + // Calculate look delta + static float prevLookX = touch.startX; + static float prevLookY = touch.startY; + + m_lookDeltaX = (touch.x - prevLookX) * m_screenWidth; + m_lookDeltaY = (touch.y - prevLookY) * m_screenHeight; + + prevLookX = touch.x; + prevLookY = touch.y; + + handled = true; + } + else if (touch.startX >= 0.5f && m_lookTouchId == -1) + { + // Check if touching a button first + bool isTouchingButton = false; + for (const auto& control : m_virtualControls) + { + if (!control.isJoystick && + IsInsideCircle(touch.x, touch.y, control.x, control.y, control.radius)) + { + m_buttonStates[static_cast(control.action)] = true; + isTouchingButton = true; + handled = true; + break; + } + } + + // If not touching a button, use for look control + if (!isTouchingButton) + { + m_lookTouchId = touch.id; + handled = true; + } + } + + // Check button touches + if (!handled) + { + for (const auto& control : m_virtualControls) + { + if (!control.isJoystick && + IsInsideCircle(touch.x, touch.y, control.x, control.y, control.radius)) + { + m_buttonStates[static_cast(control.action)] = true; + break; + } + } + } + } + + // Check for released touch IDs + bool joystickActive = false; + bool lookActive = false; + for (int i = 0; i < MAX_TOUCH_POINTS; ++i) + { + if (m_touchPoints[i].active) + { + if (m_touchPoints[i].id == m_joystickTouchId) + joystickActive = true; + if (m_touchPoints[i].id == m_lookTouchId) + lookActive = true; + } + } + + if (!joystickActive) + m_joystickTouchId = -1; + if (!lookActive) + m_lookTouchId = -1; + + // Map to InputState + outInputState.moveAxisX = m_joystickX; + outInputState.moveAxisY = -m_joystickY; // Invert Y + + outInputState.lookAxisX = m_lookDeltaX; + outInputState.lookAxisY = m_lookDeltaY; + + outInputState.pointerDeltaX = m_lookDeltaX; + outInputState.pointerDeltaY = m_lookDeltaY; + + // Map analog to digital actions + if (std::abs(m_joystickX) > 0.5f || std::abs(m_joystickY) > 0.5f) + { + if (m_joystickY < -0.5f) + outInputState.actionsHeld[static_cast(InputAction::MoveForward)] = true; + if (m_joystickY > 0.5f) + outInputState.actionsHeld[static_cast(InputAction::MoveBackward)] = true; + if (m_joystickX < -0.5f) + outInputState.actionsHeld[static_cast(InputAction::MoveLeft)] = true; + if (m_joystickX > 0.5f) + outInputState.actionsHeld[static_cast(InputAction::MoveRight)] = true; + } + + // Copy button states + for (int i = 0; i < static_cast(InputAction::Count); ++i) + { + outInputState.actionsHeld[i] = m_buttonStates[i]; + outInputState.actions[i] = m_buttonStates[i] && !m_buttonPrevStates[i]; + outInputState.actionsReleased[i] = !m_buttonStates[i] && m_buttonPrevStates[i]; + } + } + + void AndroidPlatform::ResetInputFrameState(InputState& inputState) + { + inputState.ResetFrameStates(); + } + + TouchPoint* AndroidPlatform::GetTouchPoint(int32_t id) + { + for (int i = 0; i < MAX_TOUCH_POINTS; ++i) + { + if (m_touchPoints[i].active && m_touchPoints[i].id == id) + return &m_touchPoints[i]; + } + return nullptr; + } + + TouchPoint* AndroidPlatform::AllocateTouchPoint(int32_t id) + { + for (int i = 0; i < MAX_TOUCH_POINTS; ++i) + { + if (!m_touchPoints[i].active) + { + m_touchPoints[i].active = true; + m_touchPoints[i].id = id; + return &m_touchPoints[i]; + } + } + return nullptr; + } + + void AndroidPlatform::ReleaseTouchPoint(int32_t id) + { + for (int i = 0; i < MAX_TOUCH_POINTS; ++i) + { + if (m_touchPoints[i].active && m_touchPoints[i].id == id) + { + m_touchPoints[i].active = false; + m_touchPoints[i].id = -1; + return; + } + } + } + + bool AndroidPlatform::IsInsideCircle(float x, float y, float cx, float cy, float radius) const + { + float dx = x - cx; + float dy = y - cy; + return (dx * dx + dy * dy) <= (radius * radius); + } + + IGraphicsContext* AndroidPlatform::GetGraphicsContext() + { + return m_graphicsContext.get(); + } + + const char* AndroidPlatform::GetUserDataPath() const + { + if (m_androidApp && m_androidApp->activity) + { + return m_androidApp->activity->internalDataPath; + } + return "/sdcard/WillowVox"; + } + + const char* AndroidPlatform::GetAssetsPath() const + { + return ""; // Android uses AssetManager, not file paths + } + + const char* AndroidPlatform::GetPlatformName() const + { + return "Android"; + } + + InputDeviceType AndroidPlatform::GetPrimaryInputDevice() const + { + return InputDeviceType::Touchscreen; + } + + bool AndroidPlatform::HasFeature(const char* featureName) const + { + if (strcmp(featureName, "touchscreen") == 0) return true; + if (strcmp(featureName, "filesystem") == 0) return true; + return false; + } +} + +#endif // PLATFORM_ANDROID diff --git a/src/platform/Android/AndroidPlatform.h b/src/platform/Android/AndroidPlatform.h new file mode 100644 index 0000000..df450b5 --- /dev/null +++ b/src/platform/Android/AndroidPlatform.h @@ -0,0 +1,111 @@ +#pragma once + +#include +#include "AndroidGraphicsContext.h" +#include +#include + +// Forward declarations +struct android_app; +struct AInputEvent; + +namespace WillowVox +{ + /** + * Android Platform Implementation + * + * Implements IPlatform for Android using android_native_app_glue. + * + * Virtual Touch Controls Layout: + * - Left side: Virtual joystick (movement) + * - Right side: Look control (drag to look around) + * - Bottom right buttons: + * - A button: Action1 (break block / primary) + * - B button: Action2 (place block / secondary) + * - X button: Jump + * - Top left: Menu button (pause/settings) + * + * Touch regions are defined in normalized screen space (0-1). + */ + class AndroidPlatform : public IPlatform + { + public: + AndroidPlatform(); + ~AndroidPlatform() override; + + // IPlatform implementation + bool Initialize() override; + void Shutdown() override; + + void PollInput(InputState& outInputState) override; + void ResetInputFrameState(InputState& inputState) override; + + void ProcessEvents() override; + + IGraphicsContext* GetGraphicsContext() override; + + const char* GetUserDataPath() const override; + const char* GetAssetsPath() const override; + + const char* GetPlatformName() const override; + InputDeviceType GetPrimaryInputDevice() const override; + + bool HasFeature(const char* featureName) const override; + + // Android-specific + void SetAndroidApp(android_app* app); + android_app* GetAndroidApp() const { return m_androidApp; } + + // Input event handling + int32_t HandleInputEvent(AInputEvent* event); + + private: + struct TouchPoint + { + int32_t id; + float x, y; + float startX, startY; + bool active; + }; + + std::unique_ptr m_graphicsContext; + android_app* m_androidApp = nullptr; + + // Touch tracking + static constexpr int MAX_TOUCH_POINTS = 10; + TouchPoint m_touchPoints[MAX_TOUCH_POINTS] = {}; + + // Virtual control regions (normalized 0-1) + struct VirtualControl + { + float x, y, radius; + InputAction action; + bool isJoystick; + }; + + std::vector m_virtualControls; + + // Input state + float m_joystickX = 0.0f; + float m_joystickY = 0.0f; + int m_joystickTouchId = -1; + + float m_lookDeltaX = 0.0f; + float m_lookDeltaY = 0.0f; + int m_lookTouchId = -1; + + bool m_buttonStates[static_cast(InputAction::Count)] = {}; + bool m_buttonPrevStates[static_cast(InputAction::Count)] = {}; + + int m_screenWidth = 1920; + int m_screenHeight = 1080; + + // Helper methods + void SetupVirtualControls(); + void ProcessTouchInput(InputState& outInputState); + TouchPoint* GetTouchPoint(int32_t id); + TouchPoint* AllocateTouchPoint(int32_t id); + void ReleaseTouchPoint(int32_t id); + bool IsInsideCircle(float x, float y, float cx, float cy, float radius) const; + }; +} diff --git a/src/platform/Desktop/DesktopGraphicsContext.cpp b/src/platform/Desktop/DesktopGraphicsContext.cpp new file mode 100644 index 0000000..bc10b23 --- /dev/null +++ b/src/platform/Desktop/DesktopGraphicsContext.cpp @@ -0,0 +1,200 @@ +#include "DesktopGraphicsContext.h" +#include + +#include +#include + +namespace WillowVox +{ + static void GLFWErrorCallback(int error, const char* description) + { + Logger::EngineError("GLFW Error (%d): %s", error, description); + } + + DesktopGraphicsContext::DesktopGraphicsContext() + { + } + + DesktopGraphicsContext::~DesktopGraphicsContext() + { + Shutdown(); + } + + bool DesktopGraphicsContext::Initialize(int width, int height, const char* title) + { + m_width = width; + m_height = height; + + // Initialize GLFW + glfwSetErrorCallback(GLFWErrorCallback); + + if (!glfwInit()) + { + Logger::EngineError("Failed to initialize GLFW"); + return false; + } + + // Configure GLFW + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + +#ifdef __APPLE__ + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); +#endif + + // Create window + m_window = glfwCreateWindow(width, height, title, nullptr, nullptr); + if (!m_window) + { + Logger::EngineError("Failed to create GLFW window"); + glfwTerminate(); + return false; + } + + glfwMakeContextCurrent(m_window); + + // Load OpenGL functions + if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) + { + Logger::EngineError("Failed to initialize GLAD"); + glfwDestroyWindow(m_window); + glfwTerminate(); + return false; + } + + // Set VSync + glfwSwapInterval(m_vsyncEnabled ? 1 : 0); + + // Enable depth testing + glEnable(GL_DEPTH_TEST); + glEnable(GL_CULL_FACE); + glCullFace(GL_BACK); + + // Enable blending + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + Logger::EngineLog("Desktop Graphics Context initialized (OpenGL %s)", glGetString(GL_VERSION)); + + return true; + } + + void DesktopGraphicsContext::Shutdown() + { + if (m_window) + { + glfwDestroyWindow(m_window); + m_window = nullptr; + } + glfwTerminate(); + } + + void DesktopGraphicsContext::BeginFrame() + { + // Nothing special needed for GLFW + } + + void DesktopGraphicsContext::EndFrame() + { + // Nothing special needed for GLFW + } + + void DesktopGraphicsContext::SwapBuffers() + { + if (m_window) + { + glfwSwapBuffers(m_window); + } + } + + void DesktopGraphicsContext::Clear(float r, float g, float b, float a) + { + glClearColor(r, g, b, a); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + } + + void DesktopGraphicsContext::ClearDepth() + { + glClear(GL_DEPTH_BUFFER_BIT); + } + + bool DesktopGraphicsContext::ShouldClose() const + { + return m_window ? glfwWindowShouldClose(m_window) : true; + } + + void DesktopGraphicsContext::SetShouldClose(bool shouldClose) + { + if (m_window) + { + glfwSetWindowShouldClose(m_window, shouldClose ? GLFW_TRUE : GLFW_FALSE); + } + } + + void DesktopGraphicsContext::GetFramebufferSize(int& width, int& height) const + { + if (m_window) + { + glfwGetFramebufferSize(m_window, &width, &height); + } + else + { + width = m_width; + height = m_height; + } + } + + void DesktopGraphicsContext::GetWindowSize(int& width, int& height) const + { + if (m_window) + { + glfwGetWindowSize(m_window, &width, &height); + } + else + { + width = m_width; + height = m_height; + } + } + + void DesktopGraphicsContext::SetVSync(bool enabled) + { + m_vsyncEnabled = enabled; + glfwSwapInterval(enabled ? 1 : 0); + } + + bool DesktopGraphicsContext::IsVSyncEnabled() const + { + return m_vsyncEnabled; + } + + float DesktopGraphicsContext::GetTime() const + { + return static_cast(glfwGetTime()); + } + + void DesktopGraphicsContext::SetBackgroundColor(float r, float g, float b, float a) + { + m_clearColor[0] = r; + m_clearColor[1] = g; + m_clearColor[2] = b; + m_clearColor[3] = a; + } + + void* DesktopGraphicsContext::GetNativeWindowHandle() const + { + return m_window; + } + + void* DesktopGraphicsContext::GetNativeGraphicsHandle() const + { + // For OpenGL, this could return the GL context + return nullptr; + } + + void DesktopGraphicsContext::SetViewport(int x, int y, int width, int height) + { + glViewport(x, y, width, height); + } +} diff --git a/src/platform/Desktop/DesktopGraphicsContext.h b/src/platform/Desktop/DesktopGraphicsContext.h new file mode 100644 index 0000000..92c3fde --- /dev/null +++ b/src/platform/Desktop/DesktopGraphicsContext.h @@ -0,0 +1,61 @@ +#pragma once + +#include + +// Forward declare GLFW types to avoid including GLFW in header +struct GLFWwindow; + +namespace WillowVox +{ + /** + * Desktop Graphics Context (OpenGL + GLFW) + * + * Implements IGraphicsContext for desktop platforms (Windows, Linux, macOS) + * using GLFW for windowing and OpenGL for rendering. + */ + class DesktopGraphicsContext : public IGraphicsContext + { + public: + DesktopGraphicsContext(); + ~DesktopGraphicsContext() override; + + // IGraphicsContext implementation + bool Initialize(int width, int height, const char* title) override; + void Shutdown() override; + + void BeginFrame() override; + void EndFrame() override; + void SwapBuffers() override; + + void Clear(float r, float g, float b, float a) override; + void ClearDepth() override; + + bool ShouldClose() const override; + void SetShouldClose(bool shouldClose) override; + + void GetFramebufferSize(int& width, int& height) const override; + void GetWindowSize(int& width, int& height) const override; + + void SetVSync(bool enabled) override; + bool IsVSyncEnabled() const override; + + float GetTime() const override; + + void SetBackgroundColor(float r, float g, float b, float a) override; + + void* GetNativeWindowHandle() const override; + void* GetNativeGraphicsHandle() const override; + + void SetViewport(int x, int y, int width, int height) override; + + // Desktop-specific + GLFWwindow* GetGLFWWindow() const { return m_window; } + + private: + GLFWwindow* m_window = nullptr; + bool m_vsyncEnabled = true; + float m_clearColor[4] = {0.1f, 0.1f, 0.1f, 1.0f}; + int m_width = 0; + int m_height = 0; + }; +} diff --git a/src/platform/Desktop/DesktopPlatform.cpp b/src/platform/Desktop/DesktopPlatform.cpp new file mode 100644 index 0000000..bb9e1f6 --- /dev/null +++ b/src/platform/Desktop/DesktopPlatform.cpp @@ -0,0 +1,296 @@ +#include "DesktopPlatform.h" +#include +#include +#include + +namespace WillowVox +{ + // GLFW scroll callback + static double g_scrollX = 0.0; + static double g_scrollY = 0.0; + + static void GLFWScrollCallback(GLFWwindow* window, double xoffset, double yoffset) + { + g_scrollX += xoffset; + g_scrollY += yoffset; + } + + DesktopPlatform::DesktopPlatform() + { + } + + DesktopPlatform::~DesktopPlatform() + { + Shutdown(); + } + + bool DesktopPlatform::Initialize() + { + Logger::EngineLog("Initializing Desktop Platform (%s)", PLATFORM_NAME); + + m_graphicsContext = std::make_unique(); + + // Graphics context will initialize GLFW + return true; + } + + void DesktopPlatform::Shutdown() + { + m_graphicsContext.reset(); + Logger::EngineLog("Desktop Platform shutdown"); + } + + void DesktopPlatform::ProcessEvents() + { + glfwPollEvents(); + + // Store current scroll values + m_scrollX = g_scrollX; + m_scrollY = g_scrollY; + + // Reset scroll accumulator for next frame + g_scrollX = 0.0; + g_scrollY = 0.0; + } + + void DesktopPlatform::PollInput(InputState& outInputState) + { + GLFWwindow* window = m_graphicsContext->GetGLFWWindow(); + if (!window) + return; + + // Setup scroll callback if not already set + static bool scrollCallbackSet = false; + if (!scrollCallbackSet) + { + glfwSetScrollCallback(window, GLFWScrollCallback); + scrollCallbackSet = true; + } + + // Update keyboard + UpdateKeyboardInput(outInputState); + + // Update mouse + UpdateMouseInput(outInputState); + + // Update gamepad (if connected) + UpdateGamepadInput(outInputState); + + // Store previous states + std::memcpy(m_keyPrevStates, m_keyStates, sizeof(m_keyStates)); + std::memcpy(m_mouseButtonPrevStates, m_mouseButtonStates, sizeof(m_mouseButtonStates)); + } + + void DesktopPlatform::ResetInputFrameState(InputState& inputState) + { + inputState.ResetFrameStates(); + m_scrollX = 0.0; + m_scrollY = 0.0; + } + + void DesktopPlatform::UpdateKeyboardInput(InputState& outInputState) + { + GLFWwindow* window = m_graphicsContext->GetGLFWWindow(); + + // Read key states + m_keyStates[GLFW_KEY_W] = glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS; + m_keyStates[GLFW_KEY_S] = glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS; + m_keyStates[GLFW_KEY_A] = glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS; + m_keyStates[GLFW_KEY_D] = glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS; + m_keyStates[GLFW_KEY_E] = glfwGetKey(window, GLFW_KEY_E) == GLFW_PRESS; + m_keyStates[GLFW_KEY_Q] = glfwGetKey(window, GLFW_KEY_Q) == GLFW_PRESS; + m_keyStates[GLFW_KEY_SPACE] = glfwGetKey(window, GLFW_KEY_SPACE) == GLFW_PRESS; + m_keyStates[GLFW_KEY_LEFT_SHIFT] = glfwGetKey(window, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS; + m_keyStates[GLFW_KEY_LEFT_CONTROL] = glfwGetKey(window, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS; + m_keyStates[GLFW_KEY_ESCAPE] = glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS; + m_keyStates[GLFW_KEY_ENTER] = glfwGetKey(window, GLFW_KEY_ENTER) == GLFW_PRESS; + m_keyStates[GLFW_KEY_TAB] = glfwGetKey(window, GLFW_KEY_TAB) == GLFW_PRESS; + + // Map to InputState actions + + // Movement (digital) + outInputState.actionsHeld[static_cast(InputAction::MoveForward)] = m_keyStates[GLFW_KEY_W]; + outInputState.actionsHeld[static_cast(InputAction::MoveBackward)] = m_keyStates[GLFW_KEY_S]; + outInputState.actionsHeld[static_cast(InputAction::MoveLeft)] = m_keyStates[GLFW_KEY_A]; + outInputState.actionsHeld[static_cast(InputAction::MoveRight)] = m_keyStates[GLFW_KEY_D]; + outInputState.actionsHeld[static_cast(InputAction::MoveUp)] = m_keyStates[GLFW_KEY_E]; + outInputState.actionsHeld[static_cast(InputAction::MoveDown)] = m_keyStates[GLFW_KEY_Q]; + + // Pressed this frame + outInputState.actions[static_cast(InputAction::MoveForward)] = + m_keyStates[GLFW_KEY_W] && !m_keyPrevStates[GLFW_KEY_W]; + outInputState.actions[static_cast(InputAction::MoveBackward)] = + m_keyStates[GLFW_KEY_S] && !m_keyPrevStates[GLFW_KEY_S]; + outInputState.actions[static_cast(InputAction::MoveLeft)] = + m_keyStates[GLFW_KEY_A] && !m_keyPrevStates[GLFW_KEY_A]; + outInputState.actions[static_cast(InputAction::MoveRight)] = + m_keyStates[GLFW_KEY_D] && !m_keyPrevStates[GLFW_KEY_D]; + + // Menu + outInputState.actions[static_cast(InputAction::MenuOpen)] = + m_keyStates[GLFW_KEY_ESCAPE] && !m_keyPrevStates[GLFW_KEY_ESCAPE]; + outInputState.actionsHeld[static_cast(InputAction::MenuOpen)] = m_keyStates[GLFW_KEY_ESCAPE]; + + outInputState.actions[static_cast(InputAction::MenuConfirm)] = + m_keyStates[GLFW_KEY_ENTER] && !m_keyPrevStates[GLFW_KEY_ENTER]; + + // Other actions + outInputState.actionsHeld[static_cast(InputAction::Jump)] = m_keyStates[GLFW_KEY_SPACE]; + outInputState.actionsHeld[static_cast(InputAction::Crouch)] = m_keyStates[GLFW_KEY_LEFT_SHIFT]; + outInputState.actionsHeld[static_cast(InputAction::Sprint)] = m_keyStates[GLFW_KEY_LEFT_CONTROL]; + + // Analog axes from digital input + outInputState.moveAxisX = 0.0f; + outInputState.moveAxisY = 0.0f; + if (m_keyStates[GLFW_KEY_W]) outInputState.moveAxisY += 1.0f; + if (m_keyStates[GLFW_KEY_S]) outInputState.moveAxisY -= 1.0f; + if (m_keyStates[GLFW_KEY_D]) outInputState.moveAxisX += 1.0f; + if (m_keyStates[GLFW_KEY_A]) outInputState.moveAxisX -= 1.0f; + + // Device type + outInputState.deviceType = InputDeviceType::KeyboardMouse; + } + + void DesktopPlatform::UpdateMouseInput(InputState& outInputState) + { + GLFWwindow* window = m_graphicsContext->GetGLFWWindow(); + + // Get mouse position + glfwGetCursorPos(window, &m_mouseX, &m_mouseY); + + // Calculate delta + outInputState.pointerDeltaX = static_cast(m_mouseX - m_prevMouseX); + outInputState.pointerDeltaY = static_cast(m_mouseY - m_prevMouseY); + m_prevMouseX = m_mouseX; + m_prevMouseY = m_mouseY; + + outInputState.pointerX = static_cast(m_mouseX); + outInputState.pointerY = static_cast(m_mouseY); + + // Look axes (from mouse delta) + outInputState.lookAxisX = outInputState.pointerDeltaX; + outInputState.lookAxisY = outInputState.pointerDeltaY; + + // Mouse buttons + m_mouseButtonStates[0] = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS; + m_mouseButtonStates[1] = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_RIGHT) == GLFW_PRESS; + m_mouseButtonStates[2] = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_MIDDLE) == GLFW_PRESS; + + // Map to actions + outInputState.actionsHeld[static_cast(InputAction::Action1)] = m_mouseButtonStates[0]; + outInputState.actionsHeld[static_cast(InputAction::Action2)] = m_mouseButtonStates[1]; + outInputState.actionsHeld[static_cast(InputAction::Action3)] = m_mouseButtonStates[2]; + + // Pressed this frame + outInputState.actions[static_cast(InputAction::Action1)] = + m_mouseButtonStates[0] && !m_mouseButtonPrevStates[0]; + outInputState.actions[static_cast(InputAction::Action2)] = + m_mouseButtonStates[1] && !m_mouseButtonPrevStates[1]; + outInputState.actions[static_cast(InputAction::Action3)] = + m_mouseButtonStates[2] && !m_mouseButtonPrevStates[2]; + + // Released this frame + outInputState.actionsReleased[static_cast(InputAction::Action1)] = + !m_mouseButtonStates[0] && m_mouseButtonPrevStates[0]; + outInputState.actionsReleased[static_cast(InputAction::Action2)] = + !m_mouseButtonStates[1] && m_mouseButtonPrevStates[1]; + + // Scroll + outInputState.scrollDelta = static_cast(m_scrollY); + + // Scroll for cycling + if (m_scrollY > 0.1) + outInputState.actions[static_cast(InputAction::CycleRight)] = true; + else if (m_scrollY < -0.1) + outInputState.actions[static_cast(InputAction::CycleLeft)] = true; + + // Pointer down + outInputState.pointerDown = m_mouseButtonStates[0]; + } + + void DesktopPlatform::UpdateGamepadInput(InputState& outInputState) + { + // Check if gamepad is connected + if (glfwJoystickPresent(GLFW_JOYSTICK_1)) + { + GLFWgamepadstate state; + if (glfwGetGamepadState(GLFW_JOYSTICK_1, &state)) + { + // Override device type + outInputState.deviceType = InputDeviceType::Gamepad; + + // Axes + outInputState.moveAxisX = state.axes[GLFW_GAMEPAD_AXIS_LEFT_X]; + outInputState.moveAxisY = state.axes[GLFW_GAMEPAD_AXIS_LEFT_Y]; + outInputState.lookAxisX = state.axes[GLFW_GAMEPAD_AXIS_RIGHT_X]; + outInputState.lookAxisY = state.axes[GLFW_GAMEPAD_AXIS_RIGHT_Y]; + + // Buttons + outInputState.actionsHeld[static_cast(InputAction::Action1)] = + state.buttons[GLFW_GAMEPAD_BUTTON_A] == GLFW_PRESS; + outInputState.actionsHeld[static_cast(InputAction::Action2)] = + state.buttons[GLFW_GAMEPAD_BUTTON_B] == GLFW_PRESS; + outInputState.actionsHeld[static_cast(InputAction::Action3)] = + state.buttons[GLFW_GAMEPAD_BUTTON_X] == GLFW_PRESS; + outInputState.actionsHeld[static_cast(InputAction::Action4)] = + state.buttons[GLFW_GAMEPAD_BUTTON_Y] == GLFW_PRESS; + + outInputState.actionsHeld[static_cast(InputAction::Jump)] = + state.buttons[GLFW_GAMEPAD_BUTTON_A] == GLFW_PRESS; + + outInputState.actionsHeld[static_cast(InputAction::MenuOpen)] = + state.buttons[GLFW_GAMEPAD_BUTTON_START] == GLFW_PRESS; + outInputState.actionsHeld[static_cast(InputAction::MenuBack)] = + state.buttons[GLFW_GAMEPAD_BUTTON_BACK] == GLFW_PRESS; + + outInputState.actionsHeld[static_cast(InputAction::CycleLeft)] = + state.buttons[GLFW_GAMEPAD_BUTTON_LEFT_BUMPER] == GLFW_PRESS; + outInputState.actionsHeld[static_cast(InputAction::CycleRight)] = + state.buttons[GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER] == GLFW_PRESS; + } + } + } + + IGraphicsContext* DesktopPlatform::GetGraphicsContext() + { + return m_graphicsContext.get(); + } + + const char* DesktopPlatform::GetUserDataPath() const + { + // Platform-specific user data paths +#if defined(PLATFORM_WINDOWS) + static const char* path = "./userdata"; // TODO: Use AppData +#elif defined(PLATFORM_MACOS) + static const char* path = "./userdata"; // TODO: Use ~/Library/Application Support +#else + static const char* path = "./userdata"; // TODO: Use ~/.local/share +#endif + return path; + } + + const char* DesktopPlatform::GetAssetsPath() const + { + return "./assets"; + } + + const char* DesktopPlatform::GetPlatformName() const + { + return PLATFORM_NAME; + } + + InputDeviceType DesktopPlatform::GetPrimaryInputDevice() const + { + return InputDeviceType::KeyboardMouse; + } + + bool DesktopPlatform::HasFeature(const char* featureName) const + { + // Desktop has mouse, keyboard, filesystem, etc. + if (strcmp(featureName, "mouse") == 0) return true; + if (strcmp(featureName, "keyboard") == 0) return true; + if (strcmp(featureName, "gamepad") == 0) return true; + if (strcmp(featureName, "filesystem") == 0) return true; + return false; + } +} diff --git a/src/platform/Desktop/DesktopPlatform.h b/src/platform/Desktop/DesktopPlatform.h new file mode 100644 index 0000000..b2c81fc --- /dev/null +++ b/src/platform/Desktop/DesktopPlatform.h @@ -0,0 +1,71 @@ +#pragma once + +#include +#include "DesktopGraphicsContext.h" +#include + +namespace WillowVox +{ + /** + * Desktop Platform Implementation + * + * Implements IPlatform for Windows, Linux, and macOS. + * Uses GLFW for windowing and input. + * + * Input Mapping (Keyboard & Mouse): + * - WASD: Movement (Forward/Back/Left/Right) + * - Space/Shift: Up/Down + * - Mouse: Look + * - Left Click: Action1 (break block) + * - Right Click: Action2 (place block) + * - Middle Click: Action3 (pick block) + * - ESC: MenuOpen + * - E/Q: MoveUp/MoveDown + * - Mouse Scroll: CycleLeft/CycleRight + */ + class DesktopPlatform : public IPlatform + { + public: + DesktopPlatform(); + ~DesktopPlatform() override; + + // IPlatform implementation + bool Initialize() override; + void Shutdown() override; + + void PollInput(InputState& outInputState) override; + void ResetInputFrameState(InputState& inputState) override; + + void ProcessEvents() override; + + IGraphicsContext* GetGraphicsContext() override; + + const char* GetUserDataPath() const override; + const char* GetAssetsPath() const override; + + const char* GetPlatformName() const override; + InputDeviceType GetPrimaryInputDevice() const override; + + bool HasFeature(const char* featureName) const override; + + private: + std::unique_ptr m_graphicsContext; + + // Input tracking + bool m_keyStates[512] = {}; + bool m_keyPrevStates[512] = {}; + bool m_mouseButtonStates[8] = {}; + bool m_mouseButtonPrevStates[8] = {}; + double m_mouseX = 0.0; + double m_mouseY = 0.0; + double m_prevMouseX = 0.0; + double m_prevMouseY = 0.0; + double m_scrollX = 0.0; + double m_scrollY = 0.0; + + // Helper methods + void UpdateKeyboardInput(InputState& outInputState); + void UpdateMouseInput(InputState& outInputState); + void UpdateGamepadInput(InputState& outInputState); + }; +} diff --git a/src/platform/Nintendo/GameCubeGraphicsContext.h b/src/platform/Nintendo/GameCubeGraphicsContext.h new file mode 100644 index 0000000..2c016d0 --- /dev/null +++ b/src/platform/Nintendo/GameCubeGraphicsContext.h @@ -0,0 +1,43 @@ +#pragma once + +#include + +namespace WillowVox +{ + /** + * Nintendo GameCube Graphics Context (GX) + * + * GameCube uses the GX graphics API (libogc). + * This is a template implementation. + */ + class GameCubeGraphicsContext : public IGraphicsContext + { + public: + GameCubeGraphicsContext(); + ~GameCubeGraphicsContext() override; + + bool Initialize(int width, int height, const char* title) override; + void Shutdown() override; + void BeginFrame() override; + void EndFrame() override; + void SwapBuffers() override; + void Clear(float r, float g, float b, float a) override; + void ClearDepth() override; + bool ShouldClose() const override; + void SetShouldClose(bool shouldClose) override; + void GetFramebufferSize(int& width, int& height) const override; + void GetWindowSize(int& width, int& height) const override; + void SetVSync(bool enabled) override; + bool IsVSyncEnabled() const override; + float GetTime() const override; + void SetBackgroundColor(float r, float g, float b, float a) override; + void SetViewport(int x, int y, int width, int height) override; + + private: + bool m_initialized = false; + bool m_shouldClose = false; + int m_width = 640; + int m_height = 480; + float m_clearColor[4] = {0.1f, 0.1f, 0.1f, 1.0f}; + }; +} diff --git a/src/platform/Nintendo/GameCubePlatform.h b/src/platform/Nintendo/GameCubePlatform.h new file mode 100644 index 0000000..3e31167 --- /dev/null +++ b/src/platform/Nintendo/GameCubePlatform.h @@ -0,0 +1,48 @@ +#pragma once + +#include +#include + +namespace WillowVox +{ + class GameCubeGraphicsContext; + + /** + * Nintendo GameCube Platform (devkitPPC Homebrew) + * + * Controller Mapping (GameCube Controller): + * - Control Stick: Movement + * - C-Stick: Look + * - A: Action1 / Jump + * - B: Action2 + * - X: Action3 + * - Y: Action4 + * - L/R: Cycle items + * - Z: Crouch + * - Start: MenuOpen + * + * Graphics: GX (libogc) + * Input: PAD (libogc) + */ + class GameCubePlatform : public IPlatform + { + public: + GameCubePlatform(); + ~GameCubePlatform() override; + + bool Initialize() override; + void Shutdown() override; + void PollInput(InputState& outInputState) override; + void ResetInputFrameState(InputState& inputState) override; + void ProcessEvents() override; + IGraphicsContext* GetGraphicsContext() override; + const char* GetUserDataPath() const override; + const char* GetAssetsPath() const override; + const char* GetPlatformName() const override; + InputDeviceType GetPrimaryInputDevice() const override; + bool HasFeature(const char* featureName) const override; + + private: + std::unique_ptr m_graphicsContext; + }; +} diff --git a/src/platform/Nintendo/SwitchGraphicsContext.h b/src/platform/Nintendo/SwitchGraphicsContext.h new file mode 100644 index 0000000..1d69fa2 --- /dev/null +++ b/src/platform/Nintendo/SwitchGraphicsContext.h @@ -0,0 +1,45 @@ +#pragma once + +#include + +namespace WillowVox +{ + /** + * Nintendo Switch Graphics Context (OpenGL) + * + * Switch homebrew supports OpenGL via mesa/deko3d or native NVN. + * This template uses OpenGL for simplicity. + */ + class SwitchGraphicsContext : public IGraphicsContext + { + public: + SwitchGraphicsContext(); + ~SwitchGraphicsContext() override; + + bool Initialize(int width, int height, const char* title) override; + void Shutdown() override; + void BeginFrame() override; + void EndFrame() override; + void SwapBuffers() override; + void Clear(float r, float g, float b, float a) override; + void ClearDepth() override; + bool ShouldClose() const override; + void SetShouldClose(bool shouldClose) override; + void GetFramebufferSize(int& width, int& height) const override; + void GetWindowSize(int& width, int& height) const override; + void SetVSync(bool enabled) override; + bool IsVSyncEnabled() const override; + float GetTime() const override; + void SetBackgroundColor(float r, float g, float b, float a) override; + void SetViewport(int x, int y, int width, int height) override; + + private: + bool m_initialized = false; + bool m_shouldClose = false; + bool m_vsyncEnabled = true; + int m_width = 1280; + int m_height = 720; + float m_clearColor[4] = {0.1f, 0.1f, 0.1f, 1.0f}; + float m_startTime = 0.0f; + }; +} diff --git a/src/platform/Nintendo/SwitchPlatform.cpp b/src/platform/Nintendo/SwitchPlatform.cpp new file mode 100644 index 0000000..032dbe9 --- /dev/null +++ b/src/platform/Nintendo/SwitchPlatform.cpp @@ -0,0 +1,184 @@ +#include "SwitchPlatform.h" +#include "SwitchGraphicsContext.h" +#include + +#ifdef PLATFORM_SWITCH + +// libnx includes (devkitA64 homebrew SDK) +#include + +namespace WillowVox +{ + SwitchPlatform::SwitchPlatform() + { + } + + SwitchPlatform::~SwitchPlatform() + { + Shutdown(); + } + + bool SwitchPlatform::Initialize() + { + Logger::EngineLog("Initializing Nintendo Switch Platform (libnx v4.0+)"); + + // Configure input for single player with standard controller styles + // HidNpadStyleSet_NpadStandard supports handheld, Joy-Con, and Pro Controller + padConfigureInput(1, HidNpadStyleSet_NpadStandard); + + // Initialize the default gamepad (reads handheld mode and first connected controller) + padInitializeDefault(&m_pad); + + // Initialize graphics + m_graphicsContext = std::make_unique(); + + Logger::EngineLog("Switch Platform initialized"); + return true; + } + + void SwitchPlatform::Shutdown() + { + m_graphicsContext.reset(); + Logger::EngineLog("Switch Platform shutdown"); + } + + void SwitchPlatform::ProcessEvents() + { + // Check if we should exit (e.g., user pressed home button) + if (!appletMainLoop()) + { + m_graphicsContext->SetShouldClose(true); + } + } + + void SwitchPlatform::PollInput(InputState& outInputState) + { + UpdateGamepadInput(outInputState); + outInputState.deviceType = InputDeviceType::SwitchController; + } + + void SwitchPlatform::UpdateGamepadInput(InputState& outInputState) + { + m_prevButtons = m_buttons; + + // Update pad state (must be called once per frame) + padUpdate(&m_pad); + + // Get button states + m_buttons = padGetButtons(&m_pad); + u64 buttonsDown = padGetButtonsDown(&m_pad); + + // Get analog stick positions + HidAnalogStickState lstick = padGetStickPos(&m_pad, 0); // Left stick + HidAnalogStickState rstick = padGetStickPos(&m_pad, 1); // Right stick + + m_lstickX = lstick.x; + m_lstickY = lstick.y; + m_rstickX = rstick.x; + m_rstickY = rstick.y; + + // Analog axes + outInputState.moveAxisX = NormalizeAxis(m_lstickX); + outInputState.moveAxisY = -NormalizeAxis(m_lstickY); + outInputState.lookAxisX = NormalizeAxis(m_rstickX) * 100.0f; + outInputState.lookAxisY = -NormalizeAxis(m_rstickY) * 100.0f; + + // Button mapping (using libnx HidNpadButton constants) + bool btnA = (m_buttons & HidNpadButton_A) != 0; + bool btnB = (m_buttons & HidNpadButton_B) != 0; + bool btnX = (m_buttons & HidNpadButton_X) != 0; + bool btnY = (m_buttons & HidNpadButton_Y) != 0; + bool btnL = (m_buttons & HidNpadButton_L) != 0; + bool btnR = (m_buttons & HidNpadButton_R) != 0; + bool btnZL = (m_buttons & HidNpadButton_ZL) != 0; + bool btnZR = (m_buttons & HidNpadButton_ZR) != 0; + bool btnPlus = (m_buttons & HidNpadButton_Plus) != 0; + bool btnMinus = (m_buttons & HidNpadButton_Minus) != 0; + + outInputState.actionsHeld[static_cast(InputAction::Action1)] = btnA; + outInputState.actionsHeld[static_cast(InputAction::Action2)] = btnB; + outInputState.actionsHeld[static_cast(InputAction::Action3)] = btnX; + outInputState.actionsHeld[static_cast(InputAction::Action4)] = btnY; + outInputState.actionsHeld[static_cast(InputAction::Jump)] = btnA; + outInputState.actionsHeld[static_cast(InputAction::Crouch)] = btnZL; + outInputState.actionsHeld[static_cast(InputAction::Sprint)] = btnZR; + outInputState.actionsHeld[static_cast(InputAction::CycleLeft)] = btnL; + outInputState.actionsHeld[static_cast(InputAction::CycleRight)] = btnR; + outInputState.actionsHeld[static_cast(InputAction::MenuOpen)] = btnPlus; + outInputState.actionsHeld[static_cast(InputAction::MenuBack)] = btnMinus || btnB; + + // Pressed this frame (using libnx HidNpadButton constants) + uint64_t pressed = m_buttons & ~m_prevButtons; + outInputState.actions[static_cast(InputAction::Action1)] = (pressed & HidNpadButton_A) != 0; + outInputState.actions[static_cast(InputAction::Action2)] = (pressed & HidNpadButton_B) != 0; + outInputState.actions[static_cast(InputAction::MenuOpen)] = (pressed & HidNpadButton_Plus) != 0; + + // Analog to digital + if (std::abs(outInputState.moveAxisY) > 0.3f) + { + if (outInputState.moveAxisY > 0.3f) + outInputState.actionsHeld[static_cast(InputAction::MoveForward)] = true; + else + outInputState.actionsHeld[static_cast(InputAction::MoveBackward)] = true; + } + if (std::abs(outInputState.moveAxisX) > 0.3f) + { + if (outInputState.moveAxisX > 0.3f) + outInputState.actionsHeld[static_cast(InputAction::MoveRight)] = true; + else + outInputState.actionsHeld[static_cast(InputAction::MoveLeft)] = true; + } + } + + float SwitchPlatform::NormalizeAxis(int32_t value) const + { + // Switch analog sticks return values from -32768 to 32767 + float normalized = static_cast(value) / 32768.0f; + + // Apply deadzone + const float deadzone = 0.15f; + if (std::abs(normalized) < deadzone) + return 0.0f; + + return normalized; + } + + void SwitchPlatform::ResetInputFrameState(InputState& inputState) + { + inputState.ResetFrameStates(); + } + + IGraphicsContext* SwitchPlatform::GetGraphicsContext() + { + return m_graphicsContext.get(); + } + + const char* SwitchPlatform::GetUserDataPath() const + { + return "/switch/WillowVox/userdata"; + } + + const char* SwitchPlatform::GetAssetsPath() const + { + return "/switch/WillowVox/assets"; + } + + const char* SwitchPlatform::GetPlatformName() const + { + return "Nintendo Switch"; + } + + InputDeviceType SwitchPlatform::GetPrimaryInputDevice() const + { + return InputDeviceType::SwitchController; + } + + bool SwitchPlatform::HasFeature(const char* featureName) const + { + if (strcmp(featureName, "gamepad") == 0) return true; + if (strcmp(featureName, "filesystem") == 0) return true; + return false; + } +} + +#endif // PLATFORM_SWITCH diff --git a/src/platform/Nintendo/SwitchPlatform.h b/src/platform/Nintendo/SwitchPlatform.h new file mode 100644 index 0000000..f508805 --- /dev/null +++ b/src/platform/Nintendo/SwitchPlatform.h @@ -0,0 +1,68 @@ +#pragma once + +#include +#include + +namespace WillowVox +{ + class SwitchGraphicsContext; + + /** + * Nintendo Switch Platform Implementation (libnx Homebrew) + * + * Controller Mapping (Joy-Con / Pro Controller): + * - Left Stick: Movement (MoveAxisX/Y) + * - Right Stick: Look (LookAxisX/Y) + * - A: Action1 + * - B: Action2 / MenuBack + * - X: Action3 + * - Y: Action4 + * - L: CycleLeft + * - R: CycleRight + * - ZL: Crouch + * - ZR: Sprint + * - Plus (+): MenuOpen + * - Minus (-): MenuBack + * + * Uses: libnx (devkitA64), OpenGL for graphics, hidpad for input + */ + class SwitchPlatform : public IPlatform + { + public: + SwitchPlatform(); + ~SwitchPlatform() override; + + bool Initialize() override; + void Shutdown() override; + + void PollInput(InputState& outInputState) override; + void ResetInputFrameState(InputState& inputState) override; + void ProcessEvents() override; + + IGraphicsContext* GetGraphicsContext() override; + + const char* GetUserDataPath() const override; + const char* GetAssetsPath() const override; + const char* GetPlatformName() const override; + InputDeviceType GetPrimaryInputDevice() const override; + bool HasFeature(const char* featureName) const override; + + private: + std::unique_ptr m_graphicsContext; + + // Pad state - using char array to avoid including switch.h in header + // Real type is PadState from libnx + alignas(8) char m_padStorage[0x400]; // Storage for PadState + + // HID pad state (libnx) + uint64_t m_buttons = 0; + uint64_t m_prevButtons = 0; + int32_t m_lstickX = 0; + int32_t m_lstickY = 0; + int32_t m_rstickX = 0; + int32_t m_rstickY = 0; + + void UpdateGamepadInput(InputState& outInputState); + float NormalizeAxis(int32_t value) const; + }; +} diff --git a/src/platform/Nintendo/WiiGraphicsContext.h b/src/platform/Nintendo/WiiGraphicsContext.h new file mode 100644 index 0000000..d6075ec --- /dev/null +++ b/src/platform/Nintendo/WiiGraphicsContext.h @@ -0,0 +1,43 @@ +#pragma once + +#include + +namespace WillowVox +{ + /** + * Nintendo Wii Graphics Context (GX) + * + * Wii uses the GX graphics API (libogc). + * This is a template implementation. + */ + class WiiGraphicsContext : public IGraphicsContext + { + public: + WiiGraphicsContext(); + ~WiiGraphicsContext() override; + + bool Initialize(int width, int height, const char* title) override; + void Shutdown() override; + void BeginFrame() override; + void EndFrame() override; + void SwapBuffers() override; + void Clear(float r, float g, float b, float a) override; + void ClearDepth() override; + bool ShouldClose() const override; + void SetShouldClose(bool shouldClose) override; + void GetFramebufferSize(int& width, int& height) const override; + void GetWindowSize(int& width, int& height) const override; + void SetVSync(bool enabled) override; + bool IsVSyncEnabled() const override; + float GetTime() const override; + void SetBackgroundColor(float r, float g, float b, float a) override; + void SetViewport(int x, int y, int width, int height) override; + + private: + bool m_initialized = false; + bool m_shouldClose = false; + int m_width = 640; + int m_height = 480; + float m_clearColor[4] = {0.1f, 0.1f, 0.1f, 1.0f}; + }; +} diff --git a/src/platform/Nintendo/WiiPlatform.h b/src/platform/Nintendo/WiiPlatform.h new file mode 100644 index 0000000..c560ed8 --- /dev/null +++ b/src/platform/Nintendo/WiiPlatform.h @@ -0,0 +1,48 @@ +#pragma once + +#include +#include + +namespace WillowVox +{ + class WiiGraphicsContext; + + /** + * Nintendo Wii Platform (devkitPPC Homebrew) + * + * Controller Mapping (Wiimote + Nunchuk or Classic Controller): + * - Nunchuk Stick / Classic Left Stick: Movement + * - Wiimote IR / Classic Right Stick: Look + * - A / Classic A: Action1 + * - B / Classic B: Action2 + * - 1 / Classic X: Action3 + * - 2 / Classic Y: Action4 + * - Home: MenuOpen + * - Plus/Minus: Cycle items + * + * Graphics: GX (libogc) + * Input: WPAD (Wiimote) / PAD (GameCube controller) + */ + class WiiPlatform : public IPlatform + { + public: + WiiPlatform(); + ~WiiPlatform() override; + + bool Initialize() override; + void Shutdown() override; + void PollInput(InputState& outInputState) override; + void ResetInputFrameState(InputState& inputState) override; + void ProcessEvents() override; + IGraphicsContext* GetGraphicsContext() override; + const char* GetUserDataPath() const override; + const char* GetAssetsPath() const override; + const char* GetPlatformName() const override; + InputDeviceType GetPrimaryInputDevice() const override; + bool HasFeature(const char* featureName) const override; + + private: + std::unique_ptr m_graphicsContext; + // Wii-specific input handling + }; +} diff --git a/src/platform/Nintendo/WiiUGraphicsContext.h b/src/platform/Nintendo/WiiUGraphicsContext.h new file mode 100644 index 0000000..6f33f30 --- /dev/null +++ b/src/platform/Nintendo/WiiUGraphicsContext.h @@ -0,0 +1,43 @@ +#pragma once + +#include + +namespace WillowVox +{ + /** + * Nintendo Wii U Graphics Context (GX2) + * + * Wii U uses the GX2 graphics API (wut library). + * This is a template implementation. + */ + class WiiUGraphicsContext : public IGraphicsContext + { + public: + WiiUGraphicsContext(); + ~WiiUGraphicsContext() override; + + bool Initialize(int width, int height, const char* title) override; + void Shutdown() override; + void BeginFrame() override; + void EndFrame() override; + void SwapBuffers() override; + void Clear(float r, float g, float b, float a) override; + void ClearDepth() override; + bool ShouldClose() const override; + void SetShouldClose(bool shouldClose) override; + void GetFramebufferSize(int& width, int& height) const override; + void GetWindowSize(int& width, int& height) const override; + void SetVSync(bool enabled) override; + bool IsVSyncEnabled() const override; + float GetTime() const override; + void SetBackgroundColor(float r, float g, float b, float a) override; + void SetViewport(int x, int y, int width, int height) override; + + private: + bool m_initialized = false; + bool m_shouldClose = false; + int m_width = 1920; + int m_height = 1080; + float m_clearColor[4] = {0.1f, 0.1f, 0.1f, 1.0f}; + }; +} diff --git a/src/platform/Nintendo/WiiUPlatform.h b/src/platform/Nintendo/WiiUPlatform.h new file mode 100644 index 0000000..a54a0e9 --- /dev/null +++ b/src/platform/Nintendo/WiiUPlatform.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include + +namespace WillowVox +{ + class WiiUGraphicsContext; + + /** + * Nintendo Wii U Platform (devkitPPC + wut Homebrew) + * + * Controller: Wii U Pro Controller / GamePad (similar to modern gamepad) + * Graphics: GX2 + * Input: VPAD (GamePad) / WPAD (Wiimote) / KPAD (Pro Controller) + * + * Similar button mapping to Switch/modern controllers + */ + class WiiUPlatform : public IPlatform + { + public: + WiiUPlatform(); + ~WiiUPlatform() override; + + bool Initialize() override; + void Shutdown() override; + void PollInput(InputState& outInputState) override; + void ResetInputFrameState(InputState& inputState) override; + void ProcessEvents() override; + IGraphicsContext* GetGraphicsContext() override; + const char* GetUserDataPath() const override; + const char* GetAssetsPath() const override; + const char* GetPlatformName() const override; + InputDeviceType GetPrimaryInputDevice() const override; + bool HasFeature(const char* featureName) const override; + + private: + std::unique_ptr m_graphicsContext; + }; +} diff --git a/src/platform/PS3/PS3GraphicsContext.cpp b/src/platform/PS3/PS3GraphicsContext.cpp new file mode 100644 index 0000000..6a933e0 --- /dev/null +++ b/src/platform/PS3/PS3GraphicsContext.cpp @@ -0,0 +1,229 @@ +#include "PS3GraphicsContext.h" +#include + +#ifdef PLATFORM_PS3 + +#include +#include +#include +#include +#include + +namespace WillowVox +{ + PS3GraphicsContext::PS3GraphicsContext() + { + struct timeval tv; + gettimeofday(&tv, nullptr); + m_startTime = tv.tv_sec + tv.tv_usec / 1000000.0f; + } + + PS3GraphicsContext::~PS3GraphicsContext() + { + Shutdown(); + } + + bool PS3GraphicsContext::Initialize(int width, int height, const char* title) + { + m_width = width; + m_height = height; + + Logger::EngineLog("Initializing PS3 Graphics (PSGL)"); + + // Initialize PSGL + PSGLinitOptions options = { + enable: PSGL_INIT_MAX_SPUS | PSGL_INIT_INITIALIZE_SPUS, + maxSPUs: 1, + initializeSPUs: false, + persistentMemorySize: 0, + transientMemorySize: 0, + errorConsole: 0, + fifoSize: 0, + hostMemorySize: 0 + }; + + psglInit(&options); + + // Get available video modes + videoState state; + videoConfiguration vconfig; + videoResolution resolution; + + if (videoGetState(0, 0, &state) == 0 && + videoGetResolution(state.displayMode.resolution, &resolution) == 0) + { + m_width = resolution.width; + m_height = resolution.height; + } + + // Set video configuration + memset(&vconfig, 0, sizeof(videoConfiguration)); + vconfig.resolution = VIDEORESOLUTION_1920x1080; + vconfig.format = VIDEO_BUFFER_FORMAT_XRGB; + vconfig.pitch = m_width * 4; + vconfig.aspect = VIDEO_ASPECT_16_9; + + // Create PSGL device + PSGLdeviceParameters params; + params.enable = PSGL_DEVICE_PARAMETERS_COLOR_FORMAT | + PSGL_DEVICE_PARAMETERS_DEPTH_FORMAT | + PSGL_DEVICE_PARAMETERS_MULTISAMPLING_MODE; + params.colorFormat = GL_ARGB_SCE; + params.depthFormat = GL_DEPTH_COMPONENT24; + params.multisamplingMode = GL_MULTISAMPLING_NONE_SCE; + + m_psglDevice = psglCreateDeviceExtended(¶ms); + if (!m_psglDevice) + { + Logger::EngineError("Failed to create PSGL device!"); + return false; + } + + // Create PSGL context + m_psglContext = psglCreateContext(); + if (!m_psglContext) + { + Logger::EngineError("Failed to create PSGL context!"); + psglDestroyDevice(m_psglDevice); + return false; + } + + // Make context current + psglMakeCurrent(m_psglContext, m_psglDevice); + + // Reset PSGL state + psglResetCurrentContext(); + + // Get actual framebuffer dimensions + GLuint fbWidth, fbHeight; + psglGetDeviceDimensions(m_psglDevice, &fbWidth, &fbHeight); + m_width = fbWidth; + m_height = fbHeight; + + // Set viewport + glViewport(0, 0, m_width, m_height); + + // Enable depth testing + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LEQUAL); + + // Enable culling + glEnable(GL_CULL_FACE); + glCullFace(GL_BACK); + + // Enable blending + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + Logger::EngineLog("PS3 Graphics initialized (%dx%d)", m_width, m_height); + m_initialized = true; + + return true; + } + + void PS3GraphicsContext::Shutdown() + { + if (m_initialized) + { + if (m_psglContext) + { + psglDestroyContext(m_psglContext); + m_psglContext = nullptr; + } + + if (m_psglDevice) + { + psglDestroyDevice(m_psglDevice); + m_psglDevice = nullptr; + } + + psglExit(); + m_initialized = false; + } + } + + void PS3GraphicsContext::BeginFrame() + { + // Nothing specific for PSGL + } + + void PS3GraphicsContext::EndFrame() + { + // Nothing specific for PSGL + } + + void PS3GraphicsContext::SwapBuffers() + { + if (m_psglDevice) + { + psglSwap(); + } + } + + void PS3GraphicsContext::Clear(float r, float g, float b, float a) + { + glClearColor(r, g, b, a); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + } + + void PS3GraphicsContext::ClearDepth() + { + glClear(GL_DEPTH_BUFFER_BIT); + } + + bool PS3GraphicsContext::ShouldClose() const + { + return m_shouldClose; + } + + void PS3GraphicsContext::SetShouldClose(bool shouldClose) + { + m_shouldClose = shouldClose; + } + + void PS3GraphicsContext::GetFramebufferSize(int& width, int& height) const + { + width = m_width; + height = m_height; + } + + void PS3GraphicsContext::GetWindowSize(int& width, int& height) const + { + width = m_width; + height = m_height; + } + + void PS3GraphicsContext::SetVSync(bool enabled) + { + m_vsyncEnabled = enabled; + // PSGL VSync is typically controlled by psglSwap behavior + } + + bool PS3GraphicsContext::IsVSyncEnabled() const + { + return m_vsyncEnabled; + } + + float PS3GraphicsContext::GetTime() const + { + struct timeval tv; + gettimeofday(&tv, nullptr); + float currentTime = tv.tv_sec + tv.tv_usec / 1000000.0f; + return currentTime - m_startTime; + } + + void PS3GraphicsContext::SetBackgroundColor(float r, float g, float b, float a) + { + m_clearColor[0] = r; + m_clearColor[1] = g; + m_clearColor[2] = b; + m_clearColor[3] = a; + } + + void PS3GraphicsContext::SetViewport(int x, int y, int width, int height) + { + glViewport(x, y, width, height); + } +} + +#endif // PLATFORM_PS3 diff --git a/src/platform/PS3/PS3GraphicsContext.h b/src/platform/PS3/PS3GraphicsContext.h new file mode 100644 index 0000000..0128012 --- /dev/null +++ b/src/platform/PS3/PS3GraphicsContext.h @@ -0,0 +1,60 @@ +#pragma once + +#include + +namespace WillowVox +{ + /** + * PS3 Graphics Context (PSGL or libgcm) + * + * PS3 homebrew can use: + * - PSGL: OpenGL-like API (easier, but less performant) + * - libgcm: Low-level RSX graphics API (more complex, better performance) + * + * This implementation provides a template for either approach. + */ + class PS3GraphicsContext : public IGraphicsContext + { + public: + PS3GraphicsContext(); + ~PS3GraphicsContext() override; + + bool Initialize(int width, int height, const char* title) override; + void Shutdown() override; + + void BeginFrame() override; + void EndFrame() override; + void SwapBuffers() override; + + void Clear(float r, float g, float b, float a) override; + void ClearDepth() override; + + bool ShouldClose() const override; + void SetShouldClose(bool shouldClose) override; + + void GetFramebufferSize(int& width, int& height) const override; + void GetWindowSize(int& width, int& height) const override; + + void SetVSync(bool enabled) override; + bool IsVSyncEnabled() const override; + + float GetTime() const override; + + void SetBackgroundColor(float r, float g, float b, float a) override; + void SetViewport(int x, int y, int width, int height) override; + + private: + bool m_initialized = false; + bool m_shouldClose = false; + bool m_vsyncEnabled = true; + float m_clearColor[4] = {0.1f, 0.1f, 0.1f, 1.0f}; + int m_width = 1920; + int m_height = 1080; + float m_startTime = 0.0f; + + // PS3-specific handles (void* for template) + void* m_psglDevice = nullptr; + void* m_psglContext = nullptr; + void* m_gcmContext = nullptr; + }; +} diff --git a/src/platform/PS3/PS3Platform.cpp b/src/platform/PS3/PS3Platform.cpp new file mode 100644 index 0000000..5e9b73d --- /dev/null +++ b/src/platform/PS3/PS3Platform.cpp @@ -0,0 +1,223 @@ +#include "PS3Platform.h" +#include "PS3GraphicsContext.h" +#include + +#ifdef PLATFORM_PS3 + +// PSL1GHT includes (homebrew SDK) +#include +#include +#include + +// PSL1GHT pad button constants (from io/pad.h) +#define PAD_BTN_SELECT 0x0001 +#define PAD_BTN_L3 0x0002 +#define PAD_BTN_R3 0x0004 +#define PAD_BTN_START 0x0008 +#define PAD_BTN_UP 0x0010 +#define PAD_BTN_RIGHT 0x0020 +#define PAD_BTN_DOWN 0x0040 +#define PAD_BTN_LEFT 0x0080 +#define PAD_BTN_L2 0x0100 +#define PAD_BTN_R2 0x0200 +#define PAD_BTN_L1 0x0400 +#define PAD_BTN_R1 0x0800 +#define PAD_BTN_TRIANGLE 0x1000 +#define PAD_BTN_CIRCLE 0x2000 +#define PAD_BTN_CROSS 0x4000 +#define PAD_BTN_SQUARE 0x8000 + +namespace WillowVox +{ + PS3Platform::PS3Platform() + { + m_padState = {}; + } + + PS3Platform::~PS3Platform() + { + Shutdown(); + } + + bool PS3Platform::Initialize() + { + Logger::EngineLog("Initializing PS3 Platform (PSL1GHT Homebrew)"); + + // Initialize pad library (max 7 pads) + if (ioPadInit(7) != 0) + { + Logger::EngineError("Failed to initialize PS3 pad library!"); + return false; + } + + // Initialize graphics + m_graphicsContext = std::make_unique(); + + Logger::EngineLog("PS3 Platform initialized"); + return true; + } + + void PS3Platform::Shutdown() + { + m_graphicsContext.reset(); + + // Shutdown pad + ioPadEnd(); + + Logger::EngineLog("PS3 Platform shutdown"); + } + + void PS3Platform::ProcessEvents() + { + // PS3 homebrew doesn't have a traditional event loop + // Events are polled directly via ioPad, sysutil, etc. + + // Handle system utility callbacks (important for XMB interaction) + sysUtilCheckCallback(); + } + + void PS3Platform::PollInput(InputState& outInputState) + { + UpdateGamepadInput(outInputState); + outInputState.deviceType = InputDeviceType::PSController; + } + + void PS3Platform::UpdateGamepadInput(InputState& outInputState) + { + // Store previous button state + m_padState.prevButtons = m_padState.buttons; + + // Read pad data from PSL1GHT + padInfo padinfo; + ioPadGetInfo(&padinfo); + + // Reset to defaults + m_padState.buttons = 0; + m_padState.lstickX = 128; + m_padState.lstickY = 128; + m_padState.rstickX = 128; + m_padState.rstickY = 128; + + // Check if pad 0 is connected + if (padinfo.status[0]) + { + padData paddata; + if (ioPadGetData(0, &paddata) == 0 && paddata.len > 0) + { + // Button states (combine all buttons into one field) + m_padState.buttons = paddata.button[2] | (paddata.button[3] << 8); + + // Analog stick values (0-255, center at 128) + m_padState.lstickX = paddata.ANA_L_H; + m_padState.lstickY = paddata.ANA_L_V; + m_padState.rstickX = paddata.ANA_R_H; + m_padState.rstickY = paddata.ANA_R_V; + } + } + + // Analog sticks + outInputState.moveAxisX = NormalizeAxis(m_padState.lstickX); + outInputState.moveAxisY = -NormalizeAxis(m_padState.lstickY); + outInputState.lookAxisX = NormalizeAxis(m_padState.rstickX) * 100.0f; + outInputState.lookAxisY = -NormalizeAxis(m_padState.rstickY) * 100.0f; + + // Button mapping + bool btnCross = (m_padState.buttons & PAD_BTN_CROSS) != 0; + bool btnCircle = (m_padState.buttons & PAD_BTN_CIRCLE) != 0; + bool btnSquare = (m_padState.buttons & PAD_BTN_SQUARE) != 0; + bool btnTriangle = (m_padState.buttons & PAD_BTN_TRIANGLE) != 0; + bool btnL1 = (m_padState.buttons & PAD_BTN_L1) != 0; + bool btnR1 = (m_padState.buttons & PAD_BTN_R1) != 0; + bool btnL2 = (m_padState.buttons & PAD_BTN_L2) != 0; + bool btnR2 = (m_padState.buttons & PAD_BTN_R2) != 0; + bool btnStart = (m_padState.buttons & PAD_BTN_START) != 0; + bool btnSelect = (m_padState.buttons & PAD_BTN_SELECT) != 0; + + // Map to abstract actions + outInputState.actionsHeld[static_cast(InputAction::Action1)] = btnCross; + outInputState.actionsHeld[static_cast(InputAction::Action2)] = btnCircle; + outInputState.actionsHeld[static_cast(InputAction::Action3)] = btnSquare; + outInputState.actionsHeld[static_cast(InputAction::Action4)] = btnTriangle; + outInputState.actionsHeld[static_cast(InputAction::Jump)] = btnCross; + outInputState.actionsHeld[static_cast(InputAction::Crouch)] = btnL2; + outInputState.actionsHeld[static_cast(InputAction::Sprint)] = btnR2; + outInputState.actionsHeld[static_cast(InputAction::CycleLeft)] = btnL1; + outInputState.actionsHeld[static_cast(InputAction::CycleRight)] = btnR1; + outInputState.actionsHeld[static_cast(InputAction::MenuOpen)] = btnStart; + outInputState.actionsHeld[static_cast(InputAction::MenuBack)] = btnSelect || btnCircle; + + // Pressed this frame + uint16_t pressed = m_padState.buttons & ~m_padState.prevButtons; + outInputState.actions[static_cast(InputAction::Action1)] = (pressed & PAD_BTN_CROSS) != 0; + outInputState.actions[static_cast(InputAction::Action2)] = (pressed & PAD_BTN_CIRCLE) != 0; + outInputState.actions[static_cast(InputAction::MenuOpen)] = (pressed & PAD_BTN_START) != 0; + + // Analog to digital movement + if (std::abs(outInputState.moveAxisY) > 0.3f) + { + if (outInputState.moveAxisY > 0.3f) + outInputState.actionsHeld[static_cast(InputAction::MoveForward)] = true; + else if (outInputState.moveAxisY < -0.3f) + outInputState.actionsHeld[static_cast(InputAction::MoveBackward)] = true; + } + if (std::abs(outInputState.moveAxisX) > 0.3f) + { + if (outInputState.moveAxisX > 0.3f) + outInputState.actionsHeld[static_cast(InputAction::MoveRight)] = true; + else if (outInputState.moveAxisX < -0.3f) + outInputState.actionsHeld[static_cast(InputAction::MoveLeft)] = true; + } + } + + float PS3Platform::NormalizeAxis(uint8_t value) const + { + // PS3 pad axes are 0-255, center at 128 + float normalized = (static_cast(value) - 128.0f) / 128.0f; + + // Apply deadzone + const float deadzone = 0.15f; + if (std::abs(normalized) < deadzone) + return 0.0f; + + return normalized; + } + + void PS3Platform::ResetInputFrameState(InputState& inputState) + { + inputState.ResetFrameStates(); + } + + IGraphicsContext* PS3Platform::GetGraphicsContext() + { + return m_graphicsContext.get(); + } + + const char* PS3Platform::GetUserDataPath() const + { + return "/dev_hdd0/game/WVOX00001/USRDIR/userdata"; + } + + const char* PS3Platform::GetAssetsPath() const + { + return "/dev_hdd0/game/WVOX00001/USRDIR/assets"; + } + + const char* PS3Platform::GetPlatformName() const + { + return "PlayStation 3"; + } + + InputDeviceType PS3Platform::GetPrimaryInputDevice() const + { + return InputDeviceType::PSController; + } + + bool PS3Platform::HasFeature(const char* featureName) const + { + if (strcmp(featureName, "gamepad") == 0) return true; + if (strcmp(featureName, "filesystem") == 0) return true; + return false; + } +} + +#endif // PLATFORM_PS3 diff --git a/src/platform/PS3/PS3Platform.h b/src/platform/PS3/PS3Platform.h new file mode 100644 index 0000000..528e6d3 --- /dev/null +++ b/src/platform/PS3/PS3Platform.h @@ -0,0 +1,65 @@ +#pragma once + +#include +#include + +namespace WillowVox +{ + class PS3GraphicsContext; + + /** + * PlayStation 3 Platform Implementation (PSL1GHT Homebrew) + * + * Controller Mapping (DualShock 3): + * - Left Stick: Movement (MoveAxisX/Y) + * - Right Stick: Look (LookAxisX/Y) + * - Cross (X): Action1 / Jump + * - Circle (O): Action2 / MenuBack + * - Square: Action3 + * - Triangle: Action4 + * - L1: CycleLeft + * - R1: CycleRight + * - L2: Crouch + * - R2: Sprint + * - Start: MenuOpen + * - Select: MenuBack + * + * Uses: libgcm or PSGL for graphics, libpad for input + */ + class PS3Platform : public IPlatform + { + public: + PS3Platform(); + ~PS3Platform() override; + + bool Initialize() override; + void Shutdown() override; + + void PollInput(InputState& outInputState) override; + void ResetInputFrameState(InputState& inputState) override; + void ProcessEvents() override; + + IGraphicsContext* GetGraphicsContext() override; + + const char* GetUserDataPath() const override; + const char* GetAssetsPath() const override; + const char* GetPlatformName() const override; + InputDeviceType GetPrimaryInputDevice() const override; + bool HasFeature(const char* featureName) const override; + + private: + std::unique_ptr m_graphicsContext; + + // PS3 pad state + struct PadState + { + uint16_t buttons; + uint16_t prevButtons; + uint8_t lstickX, lstickY; + uint8_t rstickX, rstickY; + } m_padState; + + void UpdateGamepadInput(InputState& outInputState); + float NormalizeAxis(uint8_t value) const; + }; +} diff --git a/src/platform/PS4/PS4GraphicsContext.h b/src/platform/PS4/PS4GraphicsContext.h new file mode 100644 index 0000000..36c3d72 --- /dev/null +++ b/src/platform/PS4/PS4GraphicsContext.h @@ -0,0 +1,43 @@ +#pragma once + +#include + +namespace WillowVox +{ + /** + * PS4 Graphics Context (GNM) + * + * PS4 uses GNM (low-level graphics API) via OpenOrbis SDK. + * This is a template implementation. + */ + class PS4GraphicsContext : public IGraphicsContext + { + public: + PS4GraphicsContext(); + ~PS4GraphicsContext() override; + + bool Initialize(int width, int height, const char* title) override; + void Shutdown() override; + void BeginFrame() override; + void EndFrame() override; + void SwapBuffers() override; + void Clear(float r, float g, float b, float a) override; + void ClearDepth() override; + bool ShouldClose() const override; + void SetShouldClose(bool shouldClose) override; + void GetFramebufferSize(int& width, int& height) const override; + void GetWindowSize(int& width, int& height) const override; + void SetVSync(bool enabled) override; + bool IsVSyncEnabled() const override; + float GetTime() const override; + void SetBackgroundColor(float r, float g, float b, float a) override; + void SetViewport(int x, int y, int width, int height) override; + + private: + bool m_initialized = false; + bool m_shouldClose = false; + int m_width = 1920; + int m_height = 1080; + float m_clearColor[4] = {0.1f, 0.1f, 0.1f, 1.0f}; + }; +} diff --git a/src/platform/PS4/PS4Platform.h b/src/platform/PS4/PS4Platform.h new file mode 100644 index 0000000..a0a9127 --- /dev/null +++ b/src/platform/PS4/PS4Platform.h @@ -0,0 +1,41 @@ +#pragma once + +#include +#include + +namespace WillowVox +{ + class PS4GraphicsContext; + + /** + * PlayStation 4 Platform (OpenOrbis Homebrew) + * + * Controller: DualShock 4 (same mapping as PS3) + * Graphics: GNM (low-level) or Gnm wrapper + * Input: libSceUserService + libScePad + * + * See PS3Platform for controller mapping (similar button layout) + */ + class PS4Platform : public IPlatform + { + public: + PS4Platform(); + ~PS4Platform() override; + + bool Initialize() override; + void Shutdown() override; + void PollInput(InputState& outInputState) override; + void ResetInputFrameState(InputState& inputState) override; + void ProcessEvents() override; + IGraphicsContext* GetGraphicsContext() override; + const char* GetUserDataPath() const override; + const char* GetAssetsPath() const override; + const char* GetPlatformName() const override; + InputDeviceType GetPrimaryInputDevice() const override; + bool HasFeature(const char* featureName) const override; + + private: + std::unique_ptr m_graphicsContext; + // PS4-specific pad handling (similar to PS3) + }; +} diff --git a/src/platform/PlatformFactory.cpp b/src/platform/PlatformFactory.cpp new file mode 100644 index 0000000..1434b20 --- /dev/null +++ b/src/platform/PlatformFactory.cpp @@ -0,0 +1,95 @@ +#include +#include +#include + +// Include platform-specific implementations +#if defined(PLATFORM_DESKTOP) + #include "Desktop/DesktopPlatform.h" + #include "Desktop/DesktopGraphicsContext.h" +#elif defined(PLATFORM_ANDROID) + #include "Android/AndroidPlatform.h" + #include "Android/AndroidGraphicsContext.h" +#elif defined(PLATFORM_IOS) + #include "iOS/iOSPlatform.h" + #include "iOS/iOSGraphicsContext.h" +#elif defined(PLATFORM_PS3) + #include "PS3/PS3Platform.h" + #include "PS3/PS3GraphicsContext.h" +#elif defined(PLATFORM_PS4) + #include "PS4/PS4Platform.h" + #include "PS4/PS4GraphicsContext.h" +#elif defined(PLATFORM_WII) + #include "Nintendo/WiiPlatform.h" + #include "Nintendo/WiiGraphicsContext.h" +#elif defined(PLATFORM_GAMECUBE) + #include "Nintendo/GameCubePlatform.h" + #include "Nintendo/GameCubeGraphicsContext.h" +#elif defined(PLATFORM_WIIU) + #include "Nintendo/WiiUPlatform.h" + #include "Nintendo/WiiUGraphicsContext.h" +#elif defined(PLATFORM_SWITCH) + #include "Nintendo/SwitchPlatform.h" + #include "Nintendo/SwitchGraphicsContext.h" +#elif defined(PLATFORM_XBOX_SERIES_DEV) + #include "Xbox/XboxSeriesPlatform.h" + #include "Xbox/XboxSeriesGraphicsContext.h" +#endif + +namespace WillowVox +{ + IPlatform* PlatformFactory::CreatePlatform() + { +#if defined(PLATFORM_DESKTOP) + return new DesktopPlatform(); +#elif defined(PLATFORM_ANDROID) + return new AndroidPlatform(); +#elif defined(PLATFORM_IOS) + return new iOSPlatform(); +#elif defined(PLATFORM_PS3) + return new PS3Platform(); +#elif defined(PLATFORM_PS4) + return new PS4Platform(); +#elif defined(PLATFORM_WII) + return new WiiPlatform(); +#elif defined(PLATFORM_GAMECUBE) + return new GameCubePlatform(); +#elif defined(PLATFORM_WIIU) + return new WiiUPlatform(); +#elif defined(PLATFORM_SWITCH) + return new SwitchPlatform(); +#elif defined(PLATFORM_XBOX_SERIES_DEV) + return new XboxSeriesPlatform(); +#else + #error "Unsupported platform!" + return nullptr; +#endif + } + + IGraphicsContext* GraphicsContextFactory::CreateGraphicsContext() + { +#if defined(PLATFORM_DESKTOP) + return new DesktopGraphicsContext(); +#elif defined(PLATFORM_ANDROID) + return new AndroidGraphicsContext(); +#elif defined(PLATFORM_IOS) + return new iOSGraphicsContext(); +#elif defined(PLATFORM_PS3) + return new PS3GraphicsContext(); +#elif defined(PLATFORM_PS4) + return new PS4GraphicsContext(); +#elif defined(PLATFORM_WII) + return new WiiGraphicsContext(); +#elif defined(PLATFORM_GAMECUBE) + return new GameCubeGraphicsContext(); +#elif defined(PLATFORM_WIIU) + return new WiiUGraphicsContext(); +#elif defined(PLATFORM_SWITCH) + return new SwitchGraphicsContext(); +#elif defined(PLATFORM_XBOX_SERIES_DEV) + return new XboxSeriesGraphicsContext(); +#else + #error "Unsupported platform!" + return nullptr; +#endif + } +} diff --git a/src/platform/Xbox/XboxSeriesGraphicsContext.h b/src/platform/Xbox/XboxSeriesGraphicsContext.h new file mode 100644 index 0000000..4f03ad9 --- /dev/null +++ b/src/platform/Xbox/XboxSeriesGraphicsContext.h @@ -0,0 +1,43 @@ +#pragma once + +#include + +namespace WillowVox +{ + /** + * Xbox Series X/S Graphics Context (DirectX 12) + * + * Xbox uses DirectX 12 via the GDK (Game Development Kit). + * This is a template implementation. + */ + class XboxSeriesGraphicsContext : public IGraphicsContext + { + public: + XboxSeriesGraphicsContext(); + ~XboxSeriesGraphicsContext() override; + + bool Initialize(int width, int height, const char* title) override; + void Shutdown() override; + void BeginFrame() override; + void EndFrame() override; + void SwapBuffers() override; + void Clear(float r, float g, float b, float a) override; + void ClearDepth() override; + bool ShouldClose() const override; + void SetShouldClose(bool shouldClose) override; + void GetFramebufferSize(int& width, int& height) const override; + void GetWindowSize(int& width, int& height) const override; + void SetVSync(bool enabled) override; + bool IsVSyncEnabled() const override; + float GetTime() const override; + void SetBackgroundColor(float r, float g, float b, float a) override; + void SetViewport(int x, int y, int width, int height) override; + + private: + bool m_initialized = false; + bool m_shouldClose = false; + int m_width = 1920; + int m_height = 1080; + float m_clearColor[4] = {0.1f, 0.1f, 0.1f, 1.0f}; + }; +} diff --git a/src/platform/Xbox/XboxSeriesPlatform.h b/src/platform/Xbox/XboxSeriesPlatform.h new file mode 100644 index 0000000..6bccf6d --- /dev/null +++ b/src/platform/Xbox/XboxSeriesPlatform.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include + +namespace WillowVox +{ + class XboxSeriesGraphicsContext; + + /** + * Xbox Series X/S Platform (Dev Mode / GDK) + * + * Controller: Xbox Series Controller (standard Xbox layout) + * - Left Stick: Movement + * - Right Stick: Look + * - A: Action1 / Jump + * - B: Action2 / MenuBack + * - X: Action3 + * - Y: Action4 + * - LB/RB: Cycle items + * - LT/RT: Crouch/Sprint + * - Menu (≡): MenuOpen + * - View (::): MenuBack + * + * Graphics: DirectX 12 (via GDK) + * Input: GameInput API or XInput + */ + class XboxSeriesPlatform : public IPlatform + { + public: + XboxSeriesPlatform(); + ~XboxSeriesPlatform() override; + + bool Initialize() override; + void Shutdown() override; + void PollInput(InputState& outInputState) override; + void ResetInputFrameState(InputState& inputState) override; + void ProcessEvents() override; + IGraphicsContext* GetGraphicsContext() override; + const char* GetUserDataPath() const override; + const char* GetAssetsPath() const override; + const char* GetPlatformName() const override; + InputDeviceType GetPrimaryInputDevice() const override; + bool HasFeature(const char* featureName) const override; + + private: + std::unique_ptr m_graphicsContext; + }; +} diff --git a/src/platform/iOS/iOSGraphicsContext.h b/src/platform/iOS/iOSGraphicsContext.h new file mode 100644 index 0000000..9f380ed --- /dev/null +++ b/src/platform/iOS/iOSGraphicsContext.h @@ -0,0 +1,43 @@ +#pragma once + +#include + +namespace WillowVox +{ + /** + * iOS Graphics Context (Metal or OpenGL ES) + * + * iOS can use Metal (preferred) or OpenGL ES for compatibility. + * This is a template implementation. + */ + class iOSGraphicsContext : public IGraphicsContext + { + public: + iOSGraphicsContext(); + ~iOSGraphicsContext() override; + + bool Initialize(int width, int height, const char* title) override; + void Shutdown() override; + void BeginFrame() override; + void EndFrame() override; + void SwapBuffers() override; + void Clear(float r, float g, float b, float a) override; + void ClearDepth() override; + bool ShouldClose() const override; + void SetShouldClose(bool shouldClose) override; + void GetFramebufferSize(int& width, int& height) const override; + void GetWindowSize(int& width, int& height) const override; + void SetVSync(bool enabled) override; + bool IsVSyncEnabled() const override; + float GetTime() const override; + void SetBackgroundColor(float r, float g, float b, float a) override; + void SetViewport(int x, int y, int width, int height) override; + + private: + bool m_initialized = false; + bool m_shouldClose = false; + int m_width = 0; + int m_height = 0; + float m_clearColor[4] = {0.1f, 0.1f, 0.1f, 1.0f}; + }; +} diff --git a/src/platform/iOS/iOSPlatform.h b/src/platform/iOS/iOSPlatform.h new file mode 100644 index 0000000..a8d68ca --- /dev/null +++ b/src/platform/iOS/iOSPlatform.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include + +namespace WillowVox +{ + class iOSGraphicsContext; + + /** + * iOS Platform Implementation + * + * Virtual Touch Controls (similar to Android): + * - Left virtual joystick: Movement + * - Right side: Look control (drag) + * - Bottom right buttons: A (Action1), B (Action2), Jump + * - Top left: Menu button + */ + class iOSPlatform : public IPlatform + { + public: + iOSPlatform(); + ~iOSPlatform() override; + + bool Initialize() override; + void Shutdown() override; + + void PollInput(InputState& outInputState) override; + void ResetInputFrameState(InputState& inputState) override; + void ProcessEvents() override; + + IGraphicsContext* GetGraphicsContext() override; + + const char* GetUserDataPath() const override; + const char* GetAssetsPath() const override; + const char* GetPlatformName() const override; + InputDeviceType GetPrimaryInputDevice() const override; + bool HasFeature(const char* featureName) const override; + + private: + std::unique_ptr m_graphicsContext; + // Touch input tracking (similar to Android) + // Implementation details omitted for brevity + }; +} diff --git a/src/threading/ThreadPool.cpp b/src/threading/ThreadPool.cpp index 2beb757..b8ed38d 100644 --- a/src/threading/ThreadPool.cpp +++ b/src/threading/ThreadPool.cpp @@ -39,8 +39,6 @@ namespace WillowVox { while (true) { - std::this_thread::sleep_for(std::chrono::milliseconds(5)); - std::function job; { /*m_mutexCondition.wait(lock, [this] {