mirror of
https://github.com/ApfelTeeSaft/WV-Core.git
synced 2026-08-26 19:43:26 +00:00
Implement Portable Multi Platform layer
Implement Various Platforms
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
+22
-1
@@ -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)
|
||||
|
||||
+18
-1
@@ -1,7 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <wv/platform/InputState.h>
|
||||
|
||||
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();
|
||||
|
||||
@@ -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 <wv/app/PlatformEntryPoint.h>
|
||||
*
|
||||
* WillowVox::App* WillowVox::CreateApp()
|
||||
* {
|
||||
* return new MyGameApp();
|
||||
* }
|
||||
*/
|
||||
|
||||
#include <wv/app/App.h>
|
||||
#include <wv/platform/PlatformConfig.h>
|
||||
|
||||
// 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 <android_native_app_glue.h>
|
||||
|
||||
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<Platform::String^>^ args)
|
||||
// {
|
||||
// auto app = WillowVox::CreateApp();
|
||||
// app->Run();
|
||||
// delete app;
|
||||
// return 0;
|
||||
// }
|
||||
|
||||
#else
|
||||
#error "Unsupported platform for entry point!"
|
||||
#endif
|
||||
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
#include <wv/platform/PlatformConfig.h>
|
||||
#include <wv/wvpch.h>
|
||||
|
||||
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();
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
#pragma once
|
||||
|
||||
#include <wv/platform/PlatformConfig.h>
|
||||
#include <wv/platform/InputState.h>
|
||||
#include <wv/platform/IGraphicsContext.h>
|
||||
|
||||
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<Platform::String^>^ args)
|
||||
|
||||
#else
|
||||
#error "Unsupported platform for entry point!"
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
#pragma once
|
||||
|
||||
#include <wv/wvpch.h>
|
||||
|
||||
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<int>(InputAction::Count)] = {};
|
||||
|
||||
// Digital buttons (held down)
|
||||
bool actionsHeld[static_cast<int>(InputAction::Count)] = {};
|
||||
|
||||
// Digital buttons (released this frame)
|
||||
bool actionsReleased[static_cast<int>(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<int>(action)];
|
||||
}
|
||||
|
||||
// Helper to check if action is held
|
||||
bool IsActionHeld(InputAction action) const
|
||||
{
|
||||
return actionsHeld[static_cast<int>(action)];
|
||||
}
|
||||
|
||||
// Helper to check if action was released this frame
|
||||
bool IsActionReleased(InputAction action) const
|
||||
{
|
||||
return actionsReleased[static_cast<int>(action)];
|
||||
}
|
||||
|
||||
// Reset per-frame input states
|
||||
void ResetFrameStates()
|
||||
{
|
||||
for (int i = 0; i < static_cast<int>(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;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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 <TargetConditionals.h>
|
||||
#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
|
||||
+73
-19
@@ -1,9 +1,8 @@
|
||||
#include <wv/app/App.h>
|
||||
|
||||
#include <wv/Logger.h>
|
||||
#include <wv/rendering/Renderer.h>
|
||||
#include <wv/rendering/Window.h>
|
||||
#include <wv/input/Input.h>
|
||||
#include <wv/platform/IPlatform.h>
|
||||
#include <wv/platform/IGraphicsContext.h>
|
||||
#include <iostream>
|
||||
|
||||
namespace WillowVox
|
||||
@@ -15,37 +14,92 @@ namespace WillowVox
|
||||
{
|
||||
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);
|
||||
// Create platform abstraction
|
||||
m_platform = PlatformFactory::CreatePlatform();
|
||||
if (!m_platform)
|
||||
{
|
||||
Logger::EngineError("Failed to create platform abstraction!");
|
||||
return;
|
||||
}
|
||||
|
||||
Input::Init();
|
||||
// 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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
#include "AndroidGraphicsContext.h"
|
||||
#include <wv/Logger.h>
|
||||
|
||||
#ifdef PLATFORM_ANDROID
|
||||
|
||||
#include <EGL/egl.h>
|
||||
#include <GLES3/gl3.h>
|
||||
#include <android/native_window.h>
|
||||
#include <android/asset_manager.h>
|
||||
#include <time.h>
|
||||
|
||||
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
|
||||
@@ -0,0 +1,78 @@
|
||||
#pragma once
|
||||
|
||||
#include <wv/platform/IGraphicsContext.h>
|
||||
|
||||
// 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;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,432 @@
|
||||
#include "AndroidPlatform.h"
|
||||
#include <wv/Logger.h>
|
||||
|
||||
#ifdef PLATFORM_ANDROID
|
||||
|
||||
#include <android/input.h>
|
||||
#include <android_native_app_glue.h>
|
||||
#include <android/native_activity.h>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
|
||||
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<AndroidGraphicsContext>();
|
||||
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<int>(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<int>(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<int>(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<int>(InputAction::MoveForward)] = true;
|
||||
if (m_joystickY > 0.5f)
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::MoveBackward)] = true;
|
||||
if (m_joystickX < -0.5f)
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::MoveLeft)] = true;
|
||||
if (m_joystickX > 0.5f)
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::MoveRight)] = true;
|
||||
}
|
||||
|
||||
// Copy button states
|
||||
for (int i = 0; i < static_cast<int>(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
|
||||
@@ -0,0 +1,111 @@
|
||||
#pragma once
|
||||
|
||||
#include <wv/platform/IPlatform.h>
|
||||
#include "AndroidGraphicsContext.h"
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
// 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<AndroidGraphicsContext> 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<VirtualControl> 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<int>(InputAction::Count)] = {};
|
||||
bool m_buttonPrevStates[static_cast<int>(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;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
#include "DesktopGraphicsContext.h"
|
||||
#include <wv/Logger.h>
|
||||
|
||||
#include <glad/glad.h>
|
||||
#include <GLFW/glfw3.h>
|
||||
|
||||
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<float>(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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
|
||||
#include <wv/platform/IGraphicsContext.h>
|
||||
|
||||
// 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;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
#include "DesktopPlatform.h"
|
||||
#include <wv/Logger.h>
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <cstring>
|
||||
|
||||
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<DesktopGraphicsContext>();
|
||||
|
||||
// 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<int>(InputAction::MoveForward)] = m_keyStates[GLFW_KEY_W];
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::MoveBackward)] = m_keyStates[GLFW_KEY_S];
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::MoveLeft)] = m_keyStates[GLFW_KEY_A];
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::MoveRight)] = m_keyStates[GLFW_KEY_D];
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::MoveUp)] = m_keyStates[GLFW_KEY_E];
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::MoveDown)] = m_keyStates[GLFW_KEY_Q];
|
||||
|
||||
// Pressed this frame
|
||||
outInputState.actions[static_cast<int>(InputAction::MoveForward)] =
|
||||
m_keyStates[GLFW_KEY_W] && !m_keyPrevStates[GLFW_KEY_W];
|
||||
outInputState.actions[static_cast<int>(InputAction::MoveBackward)] =
|
||||
m_keyStates[GLFW_KEY_S] && !m_keyPrevStates[GLFW_KEY_S];
|
||||
outInputState.actions[static_cast<int>(InputAction::MoveLeft)] =
|
||||
m_keyStates[GLFW_KEY_A] && !m_keyPrevStates[GLFW_KEY_A];
|
||||
outInputState.actions[static_cast<int>(InputAction::MoveRight)] =
|
||||
m_keyStates[GLFW_KEY_D] && !m_keyPrevStates[GLFW_KEY_D];
|
||||
|
||||
// Menu
|
||||
outInputState.actions[static_cast<int>(InputAction::MenuOpen)] =
|
||||
m_keyStates[GLFW_KEY_ESCAPE] && !m_keyPrevStates[GLFW_KEY_ESCAPE];
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::MenuOpen)] = m_keyStates[GLFW_KEY_ESCAPE];
|
||||
|
||||
outInputState.actions[static_cast<int>(InputAction::MenuConfirm)] =
|
||||
m_keyStates[GLFW_KEY_ENTER] && !m_keyPrevStates[GLFW_KEY_ENTER];
|
||||
|
||||
// Other actions
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::Jump)] = m_keyStates[GLFW_KEY_SPACE];
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::Crouch)] = m_keyStates[GLFW_KEY_LEFT_SHIFT];
|
||||
outInputState.actionsHeld[static_cast<int>(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<float>(m_mouseX - m_prevMouseX);
|
||||
outInputState.pointerDeltaY = static_cast<float>(m_mouseY - m_prevMouseY);
|
||||
m_prevMouseX = m_mouseX;
|
||||
m_prevMouseY = m_mouseY;
|
||||
|
||||
outInputState.pointerX = static_cast<float>(m_mouseX);
|
||||
outInputState.pointerY = static_cast<float>(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<int>(InputAction::Action1)] = m_mouseButtonStates[0];
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::Action2)] = m_mouseButtonStates[1];
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::Action3)] = m_mouseButtonStates[2];
|
||||
|
||||
// Pressed this frame
|
||||
outInputState.actions[static_cast<int>(InputAction::Action1)] =
|
||||
m_mouseButtonStates[0] && !m_mouseButtonPrevStates[0];
|
||||
outInputState.actions[static_cast<int>(InputAction::Action2)] =
|
||||
m_mouseButtonStates[1] && !m_mouseButtonPrevStates[1];
|
||||
outInputState.actions[static_cast<int>(InputAction::Action3)] =
|
||||
m_mouseButtonStates[2] && !m_mouseButtonPrevStates[2];
|
||||
|
||||
// Released this frame
|
||||
outInputState.actionsReleased[static_cast<int>(InputAction::Action1)] =
|
||||
!m_mouseButtonStates[0] && m_mouseButtonPrevStates[0];
|
||||
outInputState.actionsReleased[static_cast<int>(InputAction::Action2)] =
|
||||
!m_mouseButtonStates[1] && m_mouseButtonPrevStates[1];
|
||||
|
||||
// Scroll
|
||||
outInputState.scrollDelta = static_cast<float>(m_scrollY);
|
||||
|
||||
// Scroll for cycling
|
||||
if (m_scrollY > 0.1)
|
||||
outInputState.actions[static_cast<int>(InputAction::CycleRight)] = true;
|
||||
else if (m_scrollY < -0.1)
|
||||
outInputState.actions[static_cast<int>(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<int>(InputAction::Action1)] =
|
||||
state.buttons[GLFW_GAMEPAD_BUTTON_A] == GLFW_PRESS;
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::Action2)] =
|
||||
state.buttons[GLFW_GAMEPAD_BUTTON_B] == GLFW_PRESS;
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::Action3)] =
|
||||
state.buttons[GLFW_GAMEPAD_BUTTON_X] == GLFW_PRESS;
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::Action4)] =
|
||||
state.buttons[GLFW_GAMEPAD_BUTTON_Y] == GLFW_PRESS;
|
||||
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::Jump)] =
|
||||
state.buttons[GLFW_GAMEPAD_BUTTON_A] == GLFW_PRESS;
|
||||
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::MenuOpen)] =
|
||||
state.buttons[GLFW_GAMEPAD_BUTTON_START] == GLFW_PRESS;
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::MenuBack)] =
|
||||
state.buttons[GLFW_GAMEPAD_BUTTON_BACK] == GLFW_PRESS;
|
||||
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::CycleLeft)] =
|
||||
state.buttons[GLFW_GAMEPAD_BUTTON_LEFT_BUMPER] == GLFW_PRESS;
|
||||
outInputState.actionsHeld[static_cast<int>(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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
|
||||
#include <wv/platform/IPlatform.h>
|
||||
#include "DesktopGraphicsContext.h"
|
||||
#include <memory>
|
||||
|
||||
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<DesktopGraphicsContext> 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);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <wv/platform/IGraphicsContext.h>
|
||||
|
||||
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};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include <wv/platform/IPlatform.h>
|
||||
#include <memory>
|
||||
|
||||
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<GameCubeGraphicsContext> m_graphicsContext;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include <wv/platform/IGraphicsContext.h>
|
||||
|
||||
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;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
#include "SwitchPlatform.h"
|
||||
#include "SwitchGraphicsContext.h"
|
||||
#include <wv/Logger.h>
|
||||
|
||||
#ifdef PLATFORM_SWITCH
|
||||
|
||||
// libnx includes (devkitA64 homebrew SDK)
|
||||
#include <switch.h>
|
||||
|
||||
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<SwitchGraphicsContext>();
|
||||
|
||||
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<int>(InputAction::Action1)] = btnA;
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::Action2)] = btnB;
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::Action3)] = btnX;
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::Action4)] = btnY;
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::Jump)] = btnA;
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::Crouch)] = btnZL;
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::Sprint)] = btnZR;
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::CycleLeft)] = btnL;
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::CycleRight)] = btnR;
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::MenuOpen)] = btnPlus;
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::MenuBack)] = btnMinus || btnB;
|
||||
|
||||
// Pressed this frame (using libnx HidNpadButton constants)
|
||||
uint64_t pressed = m_buttons & ~m_prevButtons;
|
||||
outInputState.actions[static_cast<int>(InputAction::Action1)] = (pressed & HidNpadButton_A) != 0;
|
||||
outInputState.actions[static_cast<int>(InputAction::Action2)] = (pressed & HidNpadButton_B) != 0;
|
||||
outInputState.actions[static_cast<int>(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<int>(InputAction::MoveForward)] = true;
|
||||
else
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::MoveBackward)] = true;
|
||||
}
|
||||
if (std::abs(outInputState.moveAxisX) > 0.3f)
|
||||
{
|
||||
if (outInputState.moveAxisX > 0.3f)
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::MoveRight)] = true;
|
||||
else
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::MoveLeft)] = true;
|
||||
}
|
||||
}
|
||||
|
||||
float SwitchPlatform::NormalizeAxis(int32_t value) const
|
||||
{
|
||||
// Switch analog sticks return values from -32768 to 32767
|
||||
float normalized = static_cast<float>(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
|
||||
@@ -0,0 +1,68 @@
|
||||
#pragma once
|
||||
|
||||
#include <wv/platform/IPlatform.h>
|
||||
#include <memory>
|
||||
|
||||
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<SwitchGraphicsContext> 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;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <wv/platform/IGraphicsContext.h>
|
||||
|
||||
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};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include <wv/platform/IPlatform.h>
|
||||
#include <memory>
|
||||
|
||||
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<WiiGraphicsContext> m_graphicsContext;
|
||||
// Wii-specific input handling
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <wv/platform/IGraphicsContext.h>
|
||||
|
||||
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};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include <wv/platform/IPlatform.h>
|
||||
#include <memory>
|
||||
|
||||
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<WiiUGraphicsContext> m_graphicsContext;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
#include "PS3GraphicsContext.h"
|
||||
#include <wv/Logger.h>
|
||||
|
||||
#ifdef PLATFORM_PS3
|
||||
|
||||
#include <rsx/rsx.h>
|
||||
#include <sysutil/video.h>
|
||||
#include <PSGL/psgl.h>
|
||||
#include <PSGL/psglu.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
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
|
||||
@@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include <wv/platform/IGraphicsContext.h>
|
||||
|
||||
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;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
#include "PS3Platform.h"
|
||||
#include "PS3GraphicsContext.h"
|
||||
#include <wv/Logger.h>
|
||||
|
||||
#ifdef PLATFORM_PS3
|
||||
|
||||
// PSL1GHT includes (homebrew SDK)
|
||||
#include <io/pad.h>
|
||||
#include <sysutil/sysutil.h>
|
||||
#include <sys/time.h>
|
||||
|
||||
// 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<PS3GraphicsContext>();
|
||||
|
||||
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<int>(InputAction::Action1)] = btnCross;
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::Action2)] = btnCircle;
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::Action3)] = btnSquare;
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::Action4)] = btnTriangle;
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::Jump)] = btnCross;
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::Crouch)] = btnL2;
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::Sprint)] = btnR2;
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::CycleLeft)] = btnL1;
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::CycleRight)] = btnR1;
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::MenuOpen)] = btnStart;
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::MenuBack)] = btnSelect || btnCircle;
|
||||
|
||||
// Pressed this frame
|
||||
uint16_t pressed = m_padState.buttons & ~m_padState.prevButtons;
|
||||
outInputState.actions[static_cast<int>(InputAction::Action1)] = (pressed & PAD_BTN_CROSS) != 0;
|
||||
outInputState.actions[static_cast<int>(InputAction::Action2)] = (pressed & PAD_BTN_CIRCLE) != 0;
|
||||
outInputState.actions[static_cast<int>(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<int>(InputAction::MoveForward)] = true;
|
||||
else if (outInputState.moveAxisY < -0.3f)
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::MoveBackward)] = true;
|
||||
}
|
||||
if (std::abs(outInputState.moveAxisX) > 0.3f)
|
||||
{
|
||||
if (outInputState.moveAxisX > 0.3f)
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::MoveRight)] = true;
|
||||
else if (outInputState.moveAxisX < -0.3f)
|
||||
outInputState.actionsHeld[static_cast<int>(InputAction::MoveLeft)] = true;
|
||||
}
|
||||
}
|
||||
|
||||
float PS3Platform::NormalizeAxis(uint8_t value) const
|
||||
{
|
||||
// PS3 pad axes are 0-255, center at 128
|
||||
float normalized = (static_cast<float>(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
|
||||
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
|
||||
#include <wv/platform/IPlatform.h>
|
||||
#include <memory>
|
||||
|
||||
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<PS3GraphicsContext> 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;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <wv/platform/IGraphicsContext.h>
|
||||
|
||||
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};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include <wv/platform/IPlatform.h>
|
||||
#include <memory>
|
||||
|
||||
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<PS4GraphicsContext> m_graphicsContext;
|
||||
// PS4-specific pad handling (similar to PS3)
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
#include <wv/platform/IPlatform.h>
|
||||
#include <wv/platform/IGraphicsContext.h>
|
||||
#include <wv/platform/PlatformConfig.h>
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <wv/platform/IGraphicsContext.h>
|
||||
|
||||
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};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include <wv/platform/IPlatform.h>
|
||||
#include <memory>
|
||||
|
||||
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<XboxSeriesGraphicsContext> m_graphicsContext;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <wv/platform/IGraphicsContext.h>
|
||||
|
||||
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};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include <wv/platform/IPlatform.h>
|
||||
#include <memory>
|
||||
|
||||
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<iOSGraphicsContext> m_graphicsContext;
|
||||
// Touch input tracking (similar to Android)
|
||||
// Implementation details omitted for brevity
|
||||
};
|
||||
}
|
||||
@@ -39,8 +39,6 @@ namespace WillowVox
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
|
||||
std::function<void()> job;
|
||||
{
|
||||
/*m_mutexCondition.wait(lock, [this] {
|
||||
|
||||
Reference in New Issue
Block a user