Platform Specific Rendering enhanced + Centralized input API

This commit is contained in:
ApfelTeeSaft
2025-12-04 07:58:05 +01:00
parent dabb85f936
commit 5d89665d85
37 changed files with 4773 additions and 23 deletions
+546
View File
@@ -0,0 +1,546 @@
# WillowVox Input API Documentation
The WillowVox engine provides a Unity-like input system that abstracts platform-specific input into a consistent API. All input is handled through three main classes:
## Core Classes
### 1. **InputManager** - Main Input API
The primary interface for all input queries. Works across all platforms (PC, mobile, consoles).
### 2. **GamepadInput** - Gamepad-Specific Features
Advanced gamepad features like individual stick values, triggers, and vibration.
### 3. **TouchInput** - Touch-Specific Features
Multi-touch support for mobile (Android, iOS) and Wii U GamePad.
---
## InputManager - Core Input API
### Button Input
```cpp
#include <wv/core.h>
using namespace WillowVox;
void Update(const InputState& input)
{
// Check if jump button is held down
if (InputManager::GetButton(InputAction::Jump))
{
player->Jump();
}
// Check if action button was pressed THIS FRAME
if (InputManager::GetButtonDown(InputAction::Action1))
{
player->Attack();
}
// Check if button was released THIS FRAME
if (InputManager::GetButtonUp(InputAction::Action2))
{
player->StopAiming();
}
}
```
### Axis Input (Movement & Look)
```cpp
// Get individual axes by name
float horizontal = InputManager::GetAxis("MoveX"); // -1.0 to 1.0
float vertical = InputManager::GetAxis("MoveY"); // -1.0 to 1.0
float lookX = InputManager::GetAxis("LookX");
float lookY = InputManager::GetAxis("LookY");
// Or get as vectors (recommended)
glm::vec2 movement = InputManager::GetMovementVector(); // (x, y)
glm::vec2 look = InputManager::GetLookVector(); // (x, y)
// Apply to player
player->Move(movement.x, movement.y);
camera->Rotate(look.x, look.y);
```
**Supported Axis Names:**
- `"MoveX"` or `"Horizontal"` - Left/Right movement
- `"MoveY"` or `"Vertical"` - Forward/Backward movement
- `"LookX"` or `"Mouse X"` - Horizontal look/camera
- `"LookY"` or `"Mouse Y"` - Vertical look/camera
### Pointer/Mouse Input
```cpp
// Get pointer position (mouse or touch)
glm::vec2 pointerPos = InputManager::GetPointerPosition();
// Get pointer movement delta (useful for camera)
glm::vec2 pointerDelta = InputManager::GetPointerDelta();
camera->Rotate(pointerDelta.x * sensitivity, pointerDelta.y * sensitivity);
// Check if pointer/mouse button is down
if (InputManager::GetPointerDown())
{
// Click or touch is active
}
// Get scroll wheel delta
float scroll = InputManager::GetScrollDelta();
camera->Zoom(scroll);
```
### Device Detection
```cpp
// Check what device is being used
InputDeviceType device = InputManager::GetDeviceType();
// Check for specific device types
if (InputManager::IsGamepad())
{
// Show gamepad button prompts
UI::ShowPrompt("Press A to continue");
}
else if (InputManager::IsTouchscreen())
{
// Show touch controls
UI::ShowVirtualButtons();
}
// Check if specific device
if (InputManager::IsDeviceType(InputDeviceType::XboxController))
{
// Xbox-specific UI
}
```
### Convenience Methods
```cpp
// Check if player is trying to move
if (InputManager::IsMoving())
{
player->PlayWalkAnimation();
}
// Check if player is looking around
if (InputManager::IsLooking())
{
camera->UpdateRotation();
}
```
---
## GamepadInput - Gamepad Features
For more advanced gamepad control, use the `GamepadInput` class:
### Individual Stick Values
```cpp
#include <wv/input/GamepadInput.h>
// Get left stick (movement)
float leftX = GamepadInput::GetLeftStickX();
float leftY = GamepadInput::GetLeftStickY();
glm::vec2 leftStick = GamepadInput::GetLeftStick();
// Get right stick (camera/look)
float rightX = GamepadInput::GetRightStickX();
float rightY = GamepadInput::GetRightStickY();
glm::vec2 rightStick = GamepadInput::GetRightStick();
```
### Trigger Values
```cpp
// Get analog trigger values (0.0 to 1.0)
float leftTrigger = GamepadInput::GetTriggerLeft();
float rightTrigger = GamepadInput::GetTriggerRight();
// Use for vehicle acceleration
vehicle->Accelerate(rightTrigger);
vehicle->Brake(leftTrigger);
// Check if trigger exceeds threshold
if (GamepadInput::GetTriggerRightDown(0.5f))
{
// Trigger pressed at least 50%
}
```
### D-Pad Input
```cpp
// Get D-Pad as vector
glm::vec2 dpad = GamepadInput::GetDPad();
// Or check individual directions
if (GamepadInput::GetDPadUp()) { menu->MoveUp(); }
if (GamepadInput::GetDPadDown()) { menu->MoveDown(); }
if (GamepadInput::GetDPadLeft()) { menu->MoveLeft(); }
if (GamepadInput::GetDPadRight()) { menu->MoveRight(); }
```
### Face Buttons (Position-Based)
```cpp
// These map to physical button locations, not labels
bool south = GamepadInput::GetButtonSouth(); // A / Cross (bottom)
bool east = GamepadInput::GetButtonEast(); // B / Circle (right)
bool west = GamepadInput::GetButtonWest(); // X / Square (left)
bool north = GamepadInput::GetButtonNorth(); // Y / Triangle (top)
// Useful for QTE sequences or when you need exact button positions
if (GamepadInput::GetButtonSouth())
{
qte->PressCorrectButton();
}
```
### Shoulder Buttons
```cpp
bool lb = GamepadInput::GetShoulderLeft(); // LB / L1
bool rb = GamepadInput::GetShoulderRight(); // RB / R1
```
### Connection Status
```cpp
// Check if gamepad is connected
if (GamepadInput::IsConnected(0))
{
// Player 1 gamepad connected
}
// Get gamepad type
InputDeviceType type = GamepadInput::GetGamepadType();
if (type == InputDeviceType::XboxController)
{
UI::ShowXboxButtons();
}
else if (type == InputDeviceType::PSController)
{
UI::ShowPlayStationButtons();
}
```
### Vibration/Rumble
```cpp
// Set vibration (platform-dependent)
GamepadInput::SetVibration(0.5f, 0.8f, 1.0f); // (low motor, high motor, duration)
// Stop vibration
GamepadInput::StopVibration();
```
---
## TouchInput - Mobile & Touch Features
For touchscreen platforms (Android, iOS, Wii U GamePad):
### Basic Touch Input
```cpp
#include <wv/input/TouchInput.h>
// Check if touchscreen is supported
if (TouchInput::IsSupported())
{
// Get number of active touches
int touchCount = TouchInput::GetTouchCount();
// Check if any finger is touching
if (TouchInput::IsTouching())
{
// Handle touch
}
}
```
### Primary Touch
```cpp
// Get the first/primary touch
Touch touch = TouchInput::GetPrimaryTouch();
if (touch.isActive)
{
glm::vec2 position = touch.position;
glm::vec2 delta = touch.deltaPosition;
// Check touch phase
if (touch.phase == Touch::Phase::Began)
{
// Touch just started
}
else if (touch.phase == Touch::Phase::Moved)
{
// Touch is moving
camera->Pan(delta.x, delta.y);
}
}
```
### Multi-Touch Support
```cpp
// Get all active touches
std::vector<Touch> touches = TouchInput::GetTouches();
for (const Touch& touch : touches)
{
// Process each touch point
UI::DrawTouchIndicator(touch.position);
}
// Or access by index
Touch touch0 = TouchInput::GetTouch(0); // First touch
Touch touch1 = TouchInput::GetTouch(1); // Second touch
// Pinch-to-zoom example
if (TouchInput::GetTouchCount() == 2)
{
Touch t0 = TouchInput::GetTouch(0);
Touch t1 = TouchInput::GetTouch(1);
float distance = glm::distance(t0.position, t1.position);
camera->Zoom(distance);
}
```
### Touch Phase Detection
```cpp
// Check for specific touch events
if (TouchInput::TouchBegan())
{
// New touch detected this frame
}
if (TouchInput::TouchMoved())
{
// Active touch moved this frame
}
if (TouchInput::TouchEnded())
{
// Touch was released this frame
}
```
### Platform Features
```cpp
// Check capabilities
bool multiTouch = TouchInput::SupportsMultiTouch();
int maxTouches = TouchInput::GetMaxTouchCount();
// Wii U GamePad specific
if (TouchInput::IsWiiUGamePad())
{
// Use GamePad-specific features
}
```
---
## Platform-Specific Notes
### Desktop (Windows/Linux/macOS)
- **Movement**: WASD or arrow keys → `MoveX/MoveY`
- **Look**: Mouse movement → `LookX/LookY`
- **Actions**: Mouse buttons, Space, etc. → `Action1/2/3, Jump`
- **Gamepad**: XInput controllers supported
### Mobile (Android/iOS)
- **Movement**: Virtual joystick (left side) → `MoveX/MoveY`
- **Look**: Touch drag (right side) → `LookX/LookY`
- **Actions**: Virtual buttons → `Action1/2, Jump`
- **Multi-touch**: Supported via `TouchInput`
### PlayStation (PS3/PS4)
- **Movement**: Left analog stick → `MoveX/MoveY`
- **Look**: Right analog stick → `LookX/LookY`
- **Actions**: DualShock buttons → `Action1/2/3/4`
- **Triggers**: L2/R2 → `Crouch/Sprint`
### Xbox Series X/S
- **Movement**: Left analog stick → `MoveX/MoveY`
- **Look**: Right analog stick → `LookX/LookY`
- **Actions**: ABXY buttons → `Action1/2/3/4`
- **Triggers**: LT/RT → `Crouch/Sprint`
### Nintendo Switch
- **Movement**: Left analog stick → `MoveX/MoveY`
- **Look**: Right analog stick → `LookX/LookY`
- **Actions**: ABXY buttons → `Action1/2/3/4`
- **Triggers**: ZL/ZR → `Crouch/Sprint`
### Nintendo Wii
- **Movement**: Nunchuk joystick or D-pad → `MoveX/MoveY`
- **Look**: Wiimote IR pointer → `LookX/LookY`
- **Actions**: A/B/1/2 buttons → `Action1/2/3/4`
### Nintendo GameCube
- **Movement**: Control stick → `MoveX/MoveY`
- **Look**: C-stick → `LookX/LookY`
- **Actions**: ABXY buttons → `Action1/2/3/4`
- **Triggers**: L/R analog triggers supported
### Nintendo Wii U
- **Movement**: Left analog stick → `MoveX/MoveY`
- **Look**: Right analog stick → `LookX/LookY`
- **Actions**: ABXY buttons → `Action1/2/3/4`
- **Touchscreen**: GamePad touchscreen via `TouchInput`
---
## Complete Usage Example
```cpp
#include <wv/core.h>
using namespace WillowVox;
class MyGame : public App
{
public:
void Start() override
{
Logger::Log("Game Started!");
}
void Update(const InputState& input) override
{
// === Movement ===
glm::vec2 movement = InputManager::GetMovementVector();
player->Move(movement * speed * App::m_deltaTime);
// === Camera/Look ===
glm::vec2 look = InputManager::GetLookVector();
camera->Rotate(look * sensitivity);
// === Actions ===
if (InputManager::GetButtonDown(InputAction::Jump))
{
player->Jump();
}
if (InputManager::GetButton(InputAction::Action1))
{
player->PrimaryAction();
}
if (InputManager::GetButtonDown(InputAction::MenuOpen))
{
menu->Toggle();
}
// === Platform-Specific ===
if (InputManager::IsGamepad())
{
// Advanced gamepad features
float rightTrigger = GamepadInput::GetTriggerRight();
if (rightTrigger > 0.5f)
{
player->Sprint();
}
}
else if (InputManager::IsTouchscreen())
{
// Touch-specific features
int touchCount = TouchInput::GetTouchCount();
if (touchCount == 2)
{
// Pinch to zoom
Touch t0 = TouchInput::GetTouch(0);
Touch t1 = TouchInput::GetTouch(1);
float dist = glm::distance(t0.position, t1.position);
camera->Zoom(dist);
}
}
}
void Render() override
{
// Your rendering code
}
private:
Player* player;
Camera* camera;
Menu* menu;
float speed = 5.0f;
float sensitivity = 2.0f;
};
```
---
## InputAction Reference
All abstract actions available in `InputAction` enum:
| Action | Description | Common Mapping |
|--------|-------------|----------------|
| `MoveForward` | Move forward | W, Up, D-pad Up, Left Stick Up |
| `MoveBackward` | Move backward | S, Down, D-pad Down, Left Stick Down |
| `MoveLeft` | Move left | A, Left, D-pad Left, Left Stick Left |
| `MoveRight` | Move right | D, Right, D-pad Right, Left Stick Right |
| `Action1` | Primary action | Mouse Left, A/Cross, Touch Button |
| `Action2` | Secondary action | Mouse Right, B/Circle |
| `Action3` | Tertiary action | X/Square |
| `Action4` | Quaternary action | Y/Triangle |
| `Jump` | Jump | Space, A/Cross |
| `Crouch` | Crouch | Ctrl, L2/LT, Z |
| `Sprint` | Sprint | Shift, R2/RT |
| `MenuOpen` | Open menu | Esc, Start, Plus |
| `MenuBack` | Back/Cancel | Esc, B/Circle, Minus |
| `CycleLeft` | Cycle items left | Q, LB/L1 |
| `CycleRight` | Cycle items right | E, RB/R1 |
---
## Best Practices
1. **Always use InputManager for basic input** - It works across all platforms
2. **Use GamepadInput only when you need advanced gamepad features** (triggers, vibration)
3. **Use TouchInput only for multi-touch or touch-specific features**
4. **Detect device type to show appropriate UI prompts** (keyboard vs gamepad vs touch)
5. **Test on all target platforms** - Input behavior may vary slightly
6. **Use GetButtonDown for single-press actions** (menus, attacks)
7. **Use GetButton for continuous actions** (movement, aiming)
8. **Normalize and multiply by deltaTime for frame-independent movement**
---
## Migration from Old Input System
If you're using the old `Input` class (keyboard/mouse only):
```cpp
// OLD (Desktop only)
if (Input::GetKey(Key::W))
player->Move(0, 1);
// NEW (All platforms)
glm::vec2 movement = InputManager::GetMovementVector();
player->Move(movement.x, movement.y);
```
```cpp
// OLD (Desktop only)
glm::vec2 mouseDelta = Input::GetMouseDelta();
// NEW (All platforms)
glm::vec2 lookDelta = InputManager::GetLookVector();
```
The new API is **backward compatible** - you can still use the old `Input` class for desktop-specific features, but `InputManager` is recommended for cross-platform projects.
+3
View File
@@ -12,6 +12,9 @@
#include <wv/events/EventDispatcher.h>
#include <wv/input/Input.h>
#include <wv/input/InputManager.h>
#include <wv/input/GamepadInput.h>
#include <wv/input/TouchInput.h>
#include <wv/rendering/Camera.h>
#include <wv/rendering/Renderer.h>
+177
View File
@@ -0,0 +1,177 @@
#pragma once
#include <wv/platform/InputState.h>
#include <wv/wvpch.h>
namespace WillowVox
{
/**
* Gamepad-Specific Input API
*
* Provides access to gamepad-specific features like:
* - Individual stick values
* - Trigger values
* - Vibration/haptics (future)
* - Multiple gamepad support (future)
*
* Usage:
* float leftX = GamepadInput::GetLeftStickX();
* float rightTrigger = GamepadInput::GetTriggerRight();
* bool connected = GamepadInput::IsConnected(0);
*/
class GamepadInput
{
public:
/**
* Initialize gamepad input with current state
* Called automatically by InputManager
*/
static void SetInputState(const InputState* state);
// ===== Stick Input =====
/**
* Get left stick X axis (-1.0 to 1.0)
* @return Left stick horizontal value
*/
static float GetLeftStickX();
/**
* Get left stick Y axis (-1.0 to 1.0)
* @return Left stick vertical value
*/
static float GetLeftStickY();
/**
* Get left stick as a vector
* @return glm::vec2 (x, y) of left stick
*/
static glm::vec2 GetLeftStick();
/**
* Get right stick X axis (-1.0 to 1.0)
* @return Right stick horizontal value
*/
static float GetRightStickX();
/**
* Get right stick Y axis (-1.0 to 1.0)
* @return Right stick vertical value
*/
static float GetRightStickY();
/**
* Get right stick as a vector
* @return glm::vec2 (x, y) of right stick
*/
static glm::vec2 GetRightStick();
// ===== Trigger Input =====
/**
* Get left trigger value (0.0 to 1.0)
* Simulated on platforms without analog triggers
* @return Left trigger pressure
*/
static float GetTriggerLeft();
/**
* Get right trigger value (0.0 to 1.0)
* Simulated on platforms without analog triggers
* @return Right trigger pressure
*/
static float GetTriggerRight();
/**
* Check if left trigger is pressed past threshold
* @param threshold Threshold value (default 0.5)
* @return True if trigger exceeds threshold
*/
static bool GetTriggerLeftDown(float threshold = 0.5f);
/**
* Check if right trigger is pressed past threshold
* @param threshold Threshold value (default 0.5)
* @return True if trigger exceeds threshold
*/
static bool GetTriggerRightDown(float threshold = 0.5f);
// ===== D-Pad Input =====
/**
* Get D-Pad as a vector
* @return glm::vec2 (-1/0/1 for each axis)
*/
static glm::vec2 GetDPad();
/**
* Check if D-Pad direction is pressed
*/
static bool GetDPadUp();
static bool GetDPadDown();
static bool GetDPadLeft();
static bool GetDPadRight();
// ===== Face Buttons =====
/**
* Get face button state (A/B/X/Y or Cross/Circle/Square/Triangle)
* These map to the physical location, not semantic meaning
*/
static bool GetButtonSouth(); // A / Cross
static bool GetButtonEast(); // B / Circle
static bool GetButtonWest(); // X / Square
static bool GetButtonNorth(); // Y / Triangle
// ===== Shoulder Buttons =====
static bool GetShoulderLeft(); // LB / L1
static bool GetShoulderRight(); // RB / R1
// ===== Special Buttons =====
static bool GetButtonStart(); // Start / Options / Plus
static bool GetButtonSelect(); // Back / Share / Minus
// ===== Connection Status =====
/**
* Check if a gamepad is connected
* @param index Gamepad index (0-3), default 0
* @return True if gamepad is connected
*/
static bool IsConnected(int index = 0);
/**
* Get the gamepad type
* @return InputDeviceType of the connected gamepad
*/
static InputDeviceType GetGamepadType();
// ===== Vibration (Platform-Specific) =====
/**
* Set vibration/rumble on the gamepad
* Not all platforms support this
* @param leftMotor Low frequency motor (0.0-1.0)
* @param rightMotor High frequency motor (0.0-1.0)
* @param duration Duration in seconds (0 = continuous)
*/
static void SetVibration(float leftMotor, float rightMotor, float duration = 0.0f);
/**
* Stop all vibration
*/
static void StopVibration();
private:
static const InputState* s_currentState;
static class IPlatform* s_platform;
// Trigger values (simulated on platforms without analog triggers)
static float s_leftTrigger;
static float s_rightTrigger;
friend class InputManager; // Allow InputManager to set platform pointer
};
}
+156
View File
@@ -0,0 +1,156 @@
#pragma once
#include <wv/platform/InputState.h>
#include <wv/wvpch.h>
namespace WillowVox
{
/**
* Centralized Input Manager (Unity-like API)
*
* Provides a static interface for querying input state across all platforms.
* Automatically adapts to the current platform's input device.
*
* Usage:
* if (InputManager::GetButton(InputAction::Jump))
* if (InputManager::GetButtonDown(InputAction::Action1))
* float moveX = InputManager::GetAxis("MoveX");
* float lookY = InputManager::GetAxis("LookY");
*/
class InputManager
{
public:
/**
* Initialize the input manager with the current input state
* Called automatically by the engine each frame
*/
static void SetInputState(const InputState* state);
// ===== Button Input =====
/**
* Check if a button is currently held down
* @param action The abstract input action to check
* @return True if the button is held down
*/
static bool GetButton(InputAction action);
/**
* Check if a button was pressed this frame
* @param action The abstract input action to check
* @return True if the button was pressed this frame
*/
static bool GetButtonDown(InputAction action);
/**
* Check if a button was released this frame
* @param action The abstract input action to check
* @return True if the button was released this frame
*/
static bool GetButtonUp(InputAction action);
// ===== Axis Input =====
/**
* Get the value of a virtual axis (-1.0 to 1.0)
* @param axisName Name of the axis ("MoveX", "MoveY", "LookX", "LookY")
* @return The axis value normalized to -1.0 to 1.0
*/
static float GetAxis(const char* axisName);
/**
* Get raw axis value without smoothing
* Same as GetAxis() for now, but provided for Unity compatibility
*/
static float GetAxisRaw(const char* axisName);
// ===== Movement =====
/**
* Get movement vector (X, Y) from analog stick or keyboard
* @return glm::vec2 where x = left/right, y = forward/backward
*/
static glm::vec2 GetMovementVector();
/**
* Get look vector (X, Y) from analog stick or mouse
* @return glm::vec2 where x = horizontal, y = vertical
*/
static glm::vec2 GetLookVector();
// ===== Pointer/Touch =====
/**
* Get pointer position (mouse or primary touch)
* @return glm::vec2 screen position
*/
static glm::vec2 GetPointerPosition();
/**
* Get pointer delta (mouse movement or touch drag)
* @return glm::vec2 delta since last frame
*/
static glm::vec2 GetPointerDelta();
/**
* Check if pointer is down (mouse button or touch)
* @return True if pointer is currently down
*/
static bool GetPointerDown();
// ===== Device Info =====
/**
* Get the current input device type
* @return InputDeviceType enum value
*/
static InputDeviceType GetDeviceType();
/**
* Check if a specific device type is active
* @param deviceType Device type to check
* @return True if the device type matches
*/
static bool IsDeviceType(InputDeviceType deviceType);
/**
* Check if the current device is a gamepad
* @return True if using any gamepad type
*/
static bool IsGamepad();
/**
* Check if the current device has a touchscreen
* @return True if using touchscreen
*/
static bool IsTouchscreen();
// ===== Scroll =====
/**
* Get scroll wheel delta
* @return Scroll delta this frame
*/
static float GetScrollDelta();
// ===== Convenience Methods =====
/**
* Check if any movement input is active
* @return True if player is trying to move
*/
static bool IsMoving();
/**
* Check if any look input is active
* @return True if player is trying to look around
*/
static bool IsLooking();
private:
static const InputState* s_currentState;
// Helper to get axis by name
static float GetAxisInternal(const char* axisName);
};
}
+169
View File
@@ -0,0 +1,169 @@
#pragma once
#include <wv/platform/InputState.h>
#include <wv/wvpch.h>
#include <vector>
namespace WillowVox
{
/**
* Touch Point Information
*
* Represents a single touch point on the screen
*/
struct Touch
{
int fingerId; // Unique identifier for this touch
glm::vec2 position; // Current position (screen coordinates)
glm::vec2 deltaPosition; // Movement since last frame
float pressure; // Touch pressure (0.0-1.0), if supported
bool isActive; // Is this touch currently active
// Touch phase
enum class Phase
{
Began, // Touch just started
Moved, // Touch moved
Stationary, // Touch is down but hasn't moved
Ended, // Touch just ended
Canceled // Touch was canceled
};
Phase phase;
Touch() : fingerId(-1), position(0.0f), deltaPosition(0.0f),
pressure(1.0f), isActive(false), phase(Phase::Ended) {}
};
/**
* Touch-Specific Input API
*
* Provides access to touchscreen features for mobile and Wii U GamePad:
* - Multi-touch support
* - Touch position and delta
* - Touch phase tracking
* - Gesture recognition (future)
*
* Usage:
* int touchCount = TouchInput::GetTouchCount();
* Touch touch = TouchInput::GetTouch(0);
* if (touch.phase == Touch::Phase::Began) { ... }
*/
class TouchInput
{
public:
/**
* Initialize touch input with current state
* Called automatically by InputManager
*/
static void SetInputState(const InputState* state);
// ===== Touch Count =====
/**
* Get the number of active touches
* @return Number of fingers currently touching the screen
*/
static int GetTouchCount();
/**
* Check if any touch is active
* @return True if at least one finger is touching
*/
static bool IsTouching();
// ===== Touch Access =====
/**
* Get touch information by index
* @param index Touch index (0 to GetTouchCount()-1)
* @return Touch information, or invalid touch if index out of range
*/
static Touch GetTouch(int index);
/**
* Get the primary touch (first touch point)
* @return Primary touch information
*/
static Touch GetPrimaryTouch();
/**
* Get all active touches
* @return Vector of all active touch points
*/
static std::vector<Touch> GetTouches();
// ===== Touch Position =====
/**
* Get primary touch position
* @return Screen position of first touch
*/
static glm::vec2 GetTouchPosition();
/**
* Get primary touch delta
* @return Movement delta of first touch
*/
static glm::vec2 GetTouchDelta();
// ===== Touch Phase Queries =====
/**
* Check if any touch just began this frame
* @return True if new touch detected
*/
static bool TouchBegan();
/**
* Check if any touch moved this frame
* @return True if active touch moved
*/
static bool TouchMoved();
/**
* Check if any touch ended this frame
* @return True if touch was released
*/
static bool TouchEnded();
// ===== Platform Features =====
/**
* Check if the current platform supports touch input
* @return True if touchscreen is available
*/
static bool IsSupported();
/**
* Check if the platform supports multi-touch
* @return True if multiple simultaneous touches are supported
*/
static bool SupportsMultiTouch();
/**
* Get maximum number of simultaneous touches supported
* @return Max touch points (typically 5-10)
*/
static int GetMaxTouchCount();
// ===== Wii U GamePad Specific =====
/**
* Check if touch input is from Wii U GamePad
* @return True if device is Wii U GamePad
*/
static bool IsWiiUGamePad();
private:
static const InputState* s_currentState;
static std::vector<Touch> s_touches;
static std::vector<Touch> s_prevTouches;
static constexpr int MAX_TOUCHES = 10;
// Update touch list from input state
static void UpdateTouches();
// Helper to find touch in previous frame
static Touch* FindPreviousTouch(int fingerId);
};
}
+5
View File
@@ -49,6 +49,11 @@ namespace WillowVox
// Special platform features
virtual bool HasFeature(const char* featureName) const = 0;
// Vibration/Rumble (controllers, gamepads)
// Not all platforms support this, default implementation is no-op
virtual void SetVibration(int playerIndex, float lowFrequency, float highFrequency) {}
virtual void StopVibration(int playerIndex) {}
// Optional: Some platforms may need special per-frame updates
virtual void Update(float deltaTime) {}
};
+2
View File
@@ -62,6 +62,8 @@ namespace WillowVox
Touchscreen,
Gamepad,
WiiRemote,
GameCubeController,
WiiUGamePad,
PSController,
XboxController,
SwitchController
+9
View File
@@ -3,6 +3,9 @@
#include <wv/Logger.h>
#include <wv/platform/IPlatform.h>
#include <wv/platform/IGraphicsContext.h>
#include <wv/input/InputManager.h>
#include <wv/input/GamepadInput.h>
#include <wv/input/TouchInput.h>
#include <iostream>
namespace WillowVox
@@ -70,6 +73,12 @@ namespace WillowVox
// Poll input
m_platform->PollInput(m_inputState);
// Update centralized input managers
InputManager::SetInputState(&m_inputState);
GamepadInput::SetInputState(&m_inputState);
GamepadInput::s_platform = m_platform; // Set platform for vibration
TouchInput::SetInputState(&m_inputState);
// Begin frame
m_graphicsContext->BeginFrame();
+209
View File
@@ -0,0 +1,209 @@
#include <wv/input/GamepadInput.h>
#include <wv/platform/IPlatform.h>
#include <cmath>
namespace WillowVox
{
const InputState* GamepadInput::s_currentState = nullptr;
IPlatform* GamepadInput::s_platform = nullptr;
float GamepadInput::s_leftTrigger = 0.0f;
float GamepadInput::s_rightTrigger = 0.0f;
void GamepadInput::SetInputState(const InputState* state)
{
s_currentState = state;
// Update simulated trigger values based on button state
if (s_currentState)
{
s_leftTrigger = s_currentState->IsActionHeld(InputAction::Crouch) ? 1.0f : 0.0f;
s_rightTrigger = s_currentState->IsActionHeld(InputAction::Sprint) ? 1.0f : 0.0f;
}
}
float GamepadInput::GetLeftStickX()
{
if (!s_currentState) return 0.0f;
return s_currentState->moveAxisX;
}
float GamepadInput::GetLeftStickY()
{
if (!s_currentState) return 0.0f;
return s_currentState->moveAxisY;
}
glm::vec2 GamepadInput::GetLeftStick()
{
if (!s_currentState) return glm::vec2(0.0f);
return glm::vec2(s_currentState->moveAxisX, s_currentState->moveAxisY);
}
float GamepadInput::GetRightStickX()
{
if (!s_currentState) return 0.0f;
// Look axis from platforms is typically scaled up (e.g., 100x for sensitivity)
// Normalize it back to -1..1 range
float value = s_currentState->lookAxisX;
return glm::clamp(value / 100.0f, -1.0f, 1.0f);
}
float GamepadInput::GetRightStickY()
{
if (!s_currentState) return 0.0f;
float value = s_currentState->lookAxisY;
return glm::clamp(value / 100.0f, -1.0f, 1.0f);
}
glm::vec2 GamepadInput::GetRightStick()
{
return glm::vec2(GetRightStickX(), GetRightStickY());
}
float GamepadInput::GetTriggerLeft()
{
return s_leftTrigger;
}
float GamepadInput::GetTriggerRight()
{
return s_rightTrigger;
}
bool GamepadInput::GetTriggerLeftDown(float threshold)
{
return s_leftTrigger > threshold;
}
bool GamepadInput::GetTriggerRightDown(float threshold)
{
return s_rightTrigger > threshold;
}
glm::vec2 GamepadInput::GetDPad()
{
if (!s_currentState) return glm::vec2(0.0f);
float x = 0.0f;
float y = 0.0f;
if (s_currentState->IsActionHeld(InputAction::MoveLeft)) x = -1.0f;
if (s_currentState->IsActionHeld(InputAction::MoveRight)) x = 1.0f;
if (s_currentState->IsActionHeld(InputAction::MoveForward)) y = 1.0f;
if (s_currentState->IsActionHeld(InputAction::MoveBackward)) y = -1.0f;
return glm::vec2(x, y);
}
bool GamepadInput::GetDPadUp()
{
if (!s_currentState) return false;
return s_currentState->IsActionHeld(InputAction::MoveForward);
}
bool GamepadInput::GetDPadDown()
{
if (!s_currentState) return false;
return s_currentState->IsActionHeld(InputAction::MoveBackward);
}
bool GamepadInput::GetDPadLeft()
{
if (!s_currentState) return false;
return s_currentState->IsActionHeld(InputAction::MoveLeft);
}
bool GamepadInput::GetDPadRight()
{
if (!s_currentState) return false;
return s_currentState->IsActionHeld(InputAction::MoveRight);
}
bool GamepadInput::GetButtonSouth()
{
if (!s_currentState) return false;
return s_currentState->IsActionHeld(InputAction::Action1);
}
bool GamepadInput::GetButtonEast()
{
if (!s_currentState) return false;
return s_currentState->IsActionHeld(InputAction::Action2);
}
bool GamepadInput::GetButtonWest()
{
if (!s_currentState) return false;
return s_currentState->IsActionHeld(InputAction::Action3);
}
bool GamepadInput::GetButtonNorth()
{
if (!s_currentState) return false;
return s_currentState->IsActionHeld(InputAction::Action4);
}
bool GamepadInput::GetShoulderLeft()
{
if (!s_currentState) return false;
return s_currentState->IsActionHeld(InputAction::CycleLeft);
}
bool GamepadInput::GetShoulderRight()
{
if (!s_currentState) return false;
return s_currentState->IsActionHeld(InputAction::CycleRight);
}
bool GamepadInput::GetButtonStart()
{
if (!s_currentState) return false;
return s_currentState->IsActionHeld(InputAction::MenuOpen);
}
bool GamepadInput::GetButtonSelect()
{
if (!s_currentState) return false;
return s_currentState->IsActionHeld(InputAction::MenuBack);
}
bool GamepadInput::IsConnected(int index)
{
if (index != 0) return false; // Only support player 0 for now
if (!s_currentState) return false;
// Consider connected if device type is a gamepad
InputDeviceType device = s_currentState->deviceType;
return device == InputDeviceType::Gamepad ||
device == InputDeviceType::XboxController ||
device == InputDeviceType::PSController ||
device == InputDeviceType::SwitchController ||
device == InputDeviceType::WiiRemote ||
device == InputDeviceType::GameCubeController ||
device == InputDeviceType::WiiUGamePad;
}
InputDeviceType GamepadInput::GetGamepadType()
{
if (!s_currentState) return InputDeviceType::Unknown;
return s_currentState->deviceType;
}
void GamepadInput::SetVibration(float leftMotor, float rightMotor, float duration)
{
if (!s_platform)
return;
// Call platform's vibration implementation
// Duration is not yet implemented (would need a timer system)
s_platform->SetVibration(0, leftMotor, rightMotor);
}
void GamepadInput::StopVibration()
{
if (!s_platform)
return;
s_platform->StopVibration(0);
}
}
+141
View File
@@ -0,0 +1,141 @@
#include <wv/input/InputManager.h>
#include <cstring>
#include <cmath>
namespace WillowVox
{
const InputState* InputManager::s_currentState = nullptr;
void InputManager::SetInputState(const InputState* state)
{
s_currentState = state;
}
bool InputManager::GetButton(InputAction action)
{
if (!s_currentState) return false;
return s_currentState->IsActionHeld(action);
}
bool InputManager::GetButtonDown(InputAction action)
{
if (!s_currentState) return false;
return s_currentState->IsActionPressed(action);
}
bool InputManager::GetButtonUp(InputAction action)
{
if (!s_currentState) return false;
return s_currentState->IsActionReleased(action);
}
float InputManager::GetAxis(const char* axisName)
{
return GetAxisInternal(axisName);
}
float InputManager::GetAxisRaw(const char* axisName)
{
// For now, same as GetAxis - could add smoothing to GetAxis() later
return GetAxisInternal(axisName);
}
float InputManager::GetAxisInternal(const char* axisName)
{
if (!s_currentState) return 0.0f;
// Map axis names to input state values
if (strcmp(axisName, "MoveX") == 0 || strcmp(axisName, "Horizontal") == 0)
return s_currentState->moveAxisX;
else if (strcmp(axisName, "MoveY") == 0 || strcmp(axisName, "Vertical") == 0)
return s_currentState->moveAxisY;
else if (strcmp(axisName, "LookX") == 0 || strcmp(axisName, "Mouse X") == 0)
return s_currentState->lookAxisX;
else if (strcmp(axisName, "LookY") == 0 || strcmp(axisName, "Mouse Y") == 0)
return s_currentState->lookAxisY;
return 0.0f;
}
glm::vec2 InputManager::GetMovementVector()
{
if (!s_currentState) return glm::vec2(0.0f);
return glm::vec2(s_currentState->moveAxisX, s_currentState->moveAxisY);
}
glm::vec2 InputManager::GetLookVector()
{
if (!s_currentState) return glm::vec2(0.0f);
return glm::vec2(s_currentState->lookAxisX, s_currentState->lookAxisY);
}
glm::vec2 InputManager::GetPointerPosition()
{
if (!s_currentState) return glm::vec2(0.0f);
return glm::vec2(s_currentState->pointerX, s_currentState->pointerY);
}
glm::vec2 InputManager::GetPointerDelta()
{
if (!s_currentState) return glm::vec2(0.0f);
return glm::vec2(s_currentState->pointerDeltaX, s_currentState->pointerDeltaY);
}
bool InputManager::GetPointerDown()
{
if (!s_currentState) return false;
return s_currentState->pointerDown;
}
InputDeviceType InputManager::GetDeviceType()
{
if (!s_currentState) return InputDeviceType::Unknown;
return s_currentState->deviceType;
}
bool InputManager::IsDeviceType(InputDeviceType deviceType)
{
return GetDeviceType() == deviceType;
}
bool InputManager::IsGamepad()
{
InputDeviceType device = GetDeviceType();
return device == InputDeviceType::Gamepad ||
device == InputDeviceType::XboxController ||
device == InputDeviceType::PSController ||
device == InputDeviceType::SwitchController ||
device == InputDeviceType::WiiRemote;
}
bool InputManager::IsTouchscreen()
{
return GetDeviceType() == InputDeviceType::Touchscreen;
}
float InputManager::GetScrollDelta()
{
if (!s_currentState) return 0.0f;
return s_currentState->scrollDelta;
}
bool InputManager::IsMoving()
{
if (!s_currentState) return false;
float threshold = 0.1f;
return std::abs(s_currentState->moveAxisX) > threshold ||
std::abs(s_currentState->moveAxisY) > threshold ||
GetButton(InputAction::MoveForward) ||
GetButton(InputAction::MoveBackward) ||
GetButton(InputAction::MoveLeft) ||
GetButton(InputAction::MoveRight);
}
bool InputManager::IsLooking()
{
if (!s_currentState) return false;
float threshold = 0.1f;
return std::abs(s_currentState->lookAxisX) > threshold ||
std::abs(s_currentState->lookAxisY) > threshold;
}
}
+186
View File
@@ -0,0 +1,186 @@
#include <wv/input/TouchInput.h>
namespace WillowVox
{
const InputState* TouchInput::s_currentState = nullptr;
std::vector<Touch> TouchInput::s_touches;
std::vector<Touch> TouchInput::s_prevTouches;
void TouchInput::SetInputState(const InputState* state)
{
s_currentState = state;
// Store previous frame's touches
s_prevTouches = s_touches;
UpdateTouches();
}
void TouchInput::UpdateTouches()
{
s_touches.clear();
if (!s_currentState) return;
if (!IsSupported()) return;
// Create touch from pointer state
if (s_currentState->pointerDown)
{
Touch touch;
touch.fingerId = 0;
touch.position = glm::vec2(s_currentState->pointerX, s_currentState->pointerY);
touch.deltaPosition = glm::vec2(s_currentState->pointerDeltaX, s_currentState->pointerDeltaY);
touch.pressure = 1.0f;
touch.isActive = true;
// Determine phase based on previous frame
Touch* prevTouch = FindPreviousTouch(touch.fingerId);
if (!prevTouch)
{
// New touch - just began
touch.phase = Touch::Phase::Began;
}
else
{
// Existing touch - check if moved
float deltaX = touch.position.x - prevTouch->position.x;
float deltaY = touch.position.y - prevTouch->position.y;
float moveDist = std::sqrt(deltaX * deltaX + deltaY * deltaY);
if (moveDist > 0.01f)
touch.phase = Touch::Phase::Moved;
else
touch.phase = Touch::Phase::Stationary;
}
s_touches.push_back(touch);
}
}
Touch* TouchInput::FindPreviousTouch(int fingerId)
{
for (auto& touch : s_prevTouches)
{
if (touch.fingerId == fingerId && touch.isActive)
return &touch;
}
return nullptr;
}
int TouchInput::GetTouchCount()
{
return static_cast<int>(s_touches.size());
}
bool TouchInput::IsTouching()
{
return GetTouchCount() > 0;
}
Touch TouchInput::GetTouch(int index)
{
if (index < 0 || index >= static_cast<int>(s_touches.size()))
return Touch(); // Return invalid touch
return s_touches[index];
}
Touch TouchInput::GetPrimaryTouch()
{
return GetTouch(0);
}
std::vector<Touch> TouchInput::GetTouches()
{
return s_touches;
}
glm::vec2 TouchInput::GetTouchPosition()
{
if (!s_currentState) return glm::vec2(0.0f);
return glm::vec2(s_currentState->pointerX, s_currentState->pointerY);
}
glm::vec2 TouchInput::GetTouchDelta()
{
if (!s_currentState) return glm::vec2(0.0f);
return glm::vec2(s_currentState->pointerDeltaX, s_currentState->pointerDeltaY);
}
bool TouchInput::TouchBegan()
{
if (s_touches.empty()) return false;
for (const auto& touch : s_touches)
{
if (touch.phase == Touch::Phase::Began)
return true;
}
return false;
}
bool TouchInput::TouchMoved()
{
if (s_touches.empty()) return false;
for (const auto& touch : s_touches)
{
if (touch.phase == Touch::Phase::Moved)
return true;
}
return false;
}
bool TouchInput::TouchEnded()
{
// Check if any touch from previous frame is no longer active
for (const auto& prevTouch : s_prevTouches)
{
if (!prevTouch.isActive)
continue;
// Check if this touch still exists in current frame
bool stillActive = false;
for (const auto& currentTouch : s_touches)
{
if (currentTouch.fingerId == prevTouch.fingerId)
{
stillActive = true;
break;
}
}
if (!stillActive)
return true; // Touch ended this frame
}
return false;
}
bool TouchInput::IsSupported()
{
if (!s_currentState) return false;
return s_currentState->deviceType == InputDeviceType::Touchscreen ||
s_currentState->deviceType == InputDeviceType::WiiUGamePad;
}
bool TouchInput::SupportsMultiTouch()
{
// Most modern touchscreens support multi-touch
return IsSupported();
}
int TouchInput::GetMaxTouchCount()
{
if (!IsSupported()) return 0;
return MAX_TOUCHES;
}
bool TouchInput::IsWiiUGamePad()
{
if (!s_currentState) return false;
return s_currentState->deviceType == InputDeviceType::WiiUGamePad;
}
}
+27 -3
View File
@@ -260,11 +260,14 @@ namespace WillowVox
{
// Platform-specific user data paths
#if defined(PLATFORM_WINDOWS)
static const char* path = "./userdata"; // TODO: Use AppData
// Windows: %APPDATA%\WillowVox
static const char* path = "%APPDATA%\\WillowVox";
#elif defined(PLATFORM_MACOS)
static const char* path = "./userdata"; // TODO: Use ~/Library/Application Support
// macOS: ~/Library/Application Support/WillowVox
static const char* path = "~/Library/Application Support/WillowVox";
#else
static const char* path = "./userdata"; // TODO: Use ~/.local/share
// Linux: ~/.local/share/WillowVox
static const char* path = "~/.local/share/WillowVox";
#endif
return path;
}
@@ -293,4 +296,25 @@ namespace WillowVox
if (strcmp(featureName, "filesystem") == 0) return true;
return false;
}
void DesktopPlatform::SetVibration(int playerIndex, float lowFrequency, float highFrequency)
{
#if defined(PLATFORM_WINDOWS)
// Windows: Use XInput for gamepad vibration
// Note: This requires linking against Xinput.lib
// For now, GLFW doesn't expose vibration API, so we'd need platform-specific code
// This is a placeholder - actual implementation would use XInput directly
Logger::EngineLog("Vibration requested (Windows XInput not yet implemented)");
#else
// Linux/macOS: GLFW doesn't support vibration
// Would need to use platform-specific APIs (evdev on Linux, IOKit on macOS)
Logger::EngineLog("Vibration not supported on this platform");
#endif
}
void DesktopPlatform::StopVibration(int playerIndex)
{
// Stop vibration by setting both motors to 0
SetVibration(playerIndex, 0.0f, 0.0f);
}
}
+3
View File
@@ -48,6 +48,9 @@ namespace WillowVox
bool HasFeature(const char* featureName) const override;
void SetVibration(int playerIndex, float lowFrequency, float highFrequency) override;
void StopVibration(int playerIndex) override;
private:
std::unique_ptr<DesktopGraphicsContext> m_graphicsContext;
@@ -0,0 +1,225 @@
#include "GameCubeGraphicsContext.h"
#include <wv/Logger.h>
#ifdef PLATFORM_GAMECUBE
// libogc includes
#include <gccore.h>
#include <ogc/system.h>
#include <ogc/video.h>
#include <ogc/gx.h>
#include <malloc.h>
#include <cstring>
#define DEFAULT_FIFO_SIZE (256 * 1024)
namespace WillowVox
{
GameCubeGraphicsContext::GameCubeGraphicsContext()
{
m_startTime = static_cast<float>(SYS_Time()) / TB_TIMER_CLOCK;
}
GameCubeGraphicsContext::~GameCubeGraphicsContext()
{
Shutdown();
}
bool GameCubeGraphicsContext::Initialize(int width, int height, const char* title)
{
Logger::EngineLog("Initializing GameCube Graphics (GX)");
// Initialize VIDEO subsystem
VIDEO_Init();
// Get preferred video mode
GXRModeObj* rmode = VIDEO_GetPreferredMode(NULL);
m_renderMode = rmode;
m_width = rmode->fbWidth;
m_height = rmode->efbHeight;
Logger::EngineLog("Video mode: %dx%d", m_width, m_height);
// Allocate framebuffers (double buffering)
m_frameBuffer[0] = MEM_K0_TO_K1(SYS_AllocateFramebuffer(rmode));
m_frameBuffer[1] = MEM_K0_TO_K1(SYS_AllocateFramebuffer(rmode));
if (!m_frameBuffer[0] || !m_frameBuffer[1])
{
Logger::EngineError("Failed to allocate framebuffers");
return false;
}
// Configure VIDEO
VIDEO_Configure(rmode);
VIDEO_SetNextFramebuffer(m_frameBuffer[0]);
VIDEO_SetBlack(FALSE);
VIDEO_Flush();
VIDEO_WaitVSync();
if (rmode->viTVMode & VI_NON_INTERLACE)
VIDEO_WaitVSync();
// Allocate GX FIFO buffer
m_fifoBuffer = memalign(32, DEFAULT_FIFO_SIZE);
if (!m_fifoBuffer)
{
Logger::EngineError("Failed to allocate GX FIFO buffer");
return false;
}
memset(m_fifoBuffer, 0, DEFAULT_FIFO_SIZE);
// Initialize GX
GX_Init(m_fifoBuffer, DEFAULT_FIFO_SIZE);
// Set up GX for 3D rendering
GX_SetCopyClear((GXColor){26, 26, 26, 255}, 0x00FFFFFF);
// Set viewport
GX_SetViewport(0, 0, rmode->fbWidth, rmode->efbHeight, 0, 1);
f32 yscale = GX_GetYScaleFactor(rmode->efbHeight, rmode->xfbHeight);
u32 xfbHeight = GX_SetDispCopyYScale(yscale);
GX_SetScissor(0, 0, rmode->fbWidth, rmode->efbHeight);
GX_SetDispCopySrc(0, 0, rmode->fbWidth, rmode->efbHeight);
GX_SetDispCopyDst(rmode->fbWidth, xfbHeight);
GX_SetCopyFilter(rmode->aa, rmode->sample_pattern, GX_TRUE, rmode->vfilter);
GX_SetFieldMode(rmode->field_rendering, ((rmode->viHeight == 2 * rmode->xfbHeight) ? GX_ENABLE : GX_DISABLE));
// Set pixel format
if (rmode->aa)
GX_SetPixelFmt(GX_PF_RGB565_Z16, GX_ZC_LINEAR);
else
GX_SetPixelFmt(GX_PF_RGB8_Z24, GX_ZC_LINEAR);
// Enable depth testing
GX_SetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE);
// Enable backface culling
GX_SetCullMode(GX_CULL_BACK);
// Enable blending
GX_SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR);
// Set color update
GX_SetColorUpdate(GX_TRUE);
Logger::EngineLog("GameCube Graphics initialized (GX, %dx%d)", m_width, m_height);
m_initialized = true;
return true;
}
void GameCubeGraphicsContext::Shutdown()
{
if (m_initialized)
{
if (m_fifoBuffer)
{
free(m_fifoBuffer);
m_fifoBuffer = nullptr;
}
// Framebuffers are managed by libogc, no need to free
m_initialized = false;
Logger::EngineLog("GameCube Graphics shutdown");
}
}
void GameCubeGraphicsContext::BeginFrame()
{
// Nothing specific needed
}
void GameCubeGraphicsContext::EndFrame()
{
// Execute all pending GX commands
GX_DrawDone();
}
void GameCubeGraphicsContext::SwapBuffers()
{
// Copy EFB to XFB
GX_CopyDisp(m_frameBuffer[m_currentFB], GX_TRUE);
GX_Flush();
// Swap framebuffers
VIDEO_SetNextFramebuffer(m_frameBuffer[m_currentFB]);
VIDEO_Flush();
if (m_vsyncEnabled)
VIDEO_WaitVSync();
m_currentFB ^= 1; // Toggle between 0 and 1
}
void GameCubeGraphicsContext::Clear(float r, float g, float b, float a)
{
GXColor color = {
static_cast<u8>(r * 255),
static_cast<u8>(g * 255),
static_cast<u8>(b * 255),
static_cast<u8>(a * 255)
};
GX_SetCopyClear(color, 0x00FFFFFF);
}
void GameCubeGraphicsContext::ClearDepth()
{
// GX clears depth automatically with GX_CopyDisp
}
bool GameCubeGraphicsContext::ShouldClose() const
{
return m_shouldClose;
}
void GameCubeGraphicsContext::SetShouldClose(bool shouldClose)
{
m_shouldClose = shouldClose;
}
void GameCubeGraphicsContext::GetFramebufferSize(int& width, int& height) const
{
width = m_width;
height = m_height;
}
void GameCubeGraphicsContext::GetWindowSize(int& width, int& height) const
{
width = m_width;
height = m_height;
}
void GameCubeGraphicsContext::SetVSync(bool enabled)
{
m_vsyncEnabled = enabled;
}
bool GameCubeGraphicsContext::IsVSyncEnabled() const
{
return m_vsyncEnabled;
}
float GameCubeGraphicsContext::GetTime() const
{
float currentTime = static_cast<float>(SYS_Time()) / TB_TIMER_CLOCK;
return currentTime - m_startTime;
}
void GameCubeGraphicsContext::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 GameCubeGraphicsContext::SetViewport(int x, int y, int width, int height)
{
GX_SetViewport(x, y, width, height, 0, 1);
GX_SetScissor(x, y, width, height);
}
}
#endif // PLATFORM_GAMECUBE
@@ -36,8 +36,16 @@ namespace WillowVox
private:
bool m_initialized = false;
bool m_shouldClose = false;
bool m_vsyncEnabled = true;
int m_width = 640;
int m_height = 480;
float m_clearColor[4] = {0.1f, 0.1f, 0.1f, 1.0f};
float m_startTime = 0.0f;
// GX-specific framebuffer (using void* to avoid including libogc headers)
void* m_fifoBuffer = nullptr; // GX FIFO buffer
void* m_frameBuffer[2] = {nullptr, nullptr}; // Double buffering
int m_currentFB = 0;
void* m_renderMode = nullptr; // GXRModeObj*
};
}
+221
View File
@@ -0,0 +1,221 @@
#include "GameCubePlatform.h"
#include "GameCubeGraphicsContext.h"
#include <wv/Logger.h>
#ifdef PLATFORM_GAMECUBE
// libogc includes
#include <gccore.h>
#include <ogc/pad.h>
#include <cmath>
namespace WillowVox
{
GameCubePlatform::GameCubePlatform()
{
}
GameCubePlatform::~GameCubePlatform()
{
Shutdown();
}
bool GameCubePlatform::Initialize()
{
Logger::EngineLog("Initializing GameCube Platform (libogc)");
// Initialize PAD (GameCube controller)
PAD_Init();
// Initialize graphics
m_graphicsContext = std::make_unique<GameCubeGraphicsContext>();
Logger::EngineLog("GameCube Platform initialized");
return true;
}
void GameCubePlatform::Shutdown()
{
m_graphicsContext.reset();
Logger::EngineLog("GameCube Platform shutdown");
}
void GameCubePlatform::ProcessEvents()
{
// GameCube doesn't have traditional event polling
// Input is polled directly via PAD_ScanPads()
}
void GameCubePlatform::PollInput(InputState& outInputState)
{
UpdateGamepadInput(outInputState);
outInputState.deviceType = InputDeviceType::GameCubeController;
}
void GameCubePlatform::UpdateGamepadInput(InputState& outInputState)
{
// Store previous button state
m_prevButtons = m_buttons;
// Scan for button presses
PAD_ScanPads();
// Read controller 0 state
u16 buttonsDown = PAD_ButtonsDown(0);
u16 buttonsHeld = PAD_ButtonsHeld(0);
m_buttons = buttonsHeld;
// Read analog sticks
s8 mainStickX = PAD_StickX(0);
s8 mainStickY = PAD_StickY(0);
s8 cStickX = PAD_SubStickX(0);
s8 cStickY = PAD_SubStickY(0);
m_mainStickX = NormalizeAxis(mainStickX);
m_mainStickY = NormalizeAxis(mainStickY);
m_cStickX = NormalizeAxis(cStickX);
m_cStickY = NormalizeAxis(cStickY);
// Read triggers
u8 triggerL = PAD_TriggerL(0);
u8 triggerR = PAD_TriggerR(0);
m_leftTrigger = NormalizeTrigger(triggerL);
m_rightTrigger = NormalizeTrigger(triggerR);
// Map analog sticks
outInputState.moveAxisX = m_mainStickX;
outInputState.moveAxisY = m_mainStickY;
outInputState.lookAxisX = m_cStickX * 100.0f;
outInputState.lookAxisY = -m_cStickY * 100.0f; // Invert Y for camera
// Button mapping (GameCube controller)
bool btnA = (m_buttons & PAD_BUTTON_A) != 0;
bool btnB = (m_buttons & PAD_BUTTON_B) != 0;
bool btnX = (m_buttons & PAD_BUTTON_X) != 0;
bool btnY = (m_buttons & PAD_BUTTON_Y) != 0;
bool btnZ = (m_buttons & PAD_TRIGGER_Z) != 0;
bool btnL = (m_buttons & PAD_TRIGGER_L) != 0;
bool btnR = (m_buttons & PAD_TRIGGER_R) != 0;
bool btnStart = (m_buttons & PAD_BUTTON_START) != 0;
bool btnDpadUp = (m_buttons & PAD_BUTTON_UP) != 0;
bool btnDpadDown = (m_buttons & PAD_BUTTON_DOWN) != 0;
bool btnDpadLeft = (m_buttons & PAD_BUTTON_LEFT) != 0;
bool btnDpadRight = (m_buttons & PAD_BUTTON_RIGHT) != 0;
// Map to abstract actions
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)] = btnZ;
outInputState.actionsHeld[static_cast<int>(InputAction::CycleLeft)] = btnL;
outInputState.actionsHeld[static_cast<int>(InputAction::CycleRight)] = btnR;
outInputState.actionsHeld[static_cast<int>(InputAction::MenuOpen)] = btnStart;
outInputState.actionsHeld[static_cast<int>(InputAction::MenuBack)] = btnB;
// Analog triggers for sprint/crouch
if (m_leftTrigger > 0.5f)
outInputState.actionsHeld[static_cast<int>(InputAction::CycleLeft)] = true;
if (m_rightTrigger > 0.5f)
outInputState.actionsHeld[static_cast<int>(InputAction::Sprint)] = true;
// D-pad for movement
outInputState.actionsHeld[static_cast<int>(InputAction::MoveForward)] = btnDpadUp;
outInputState.actionsHeld[static_cast<int>(InputAction::MoveBackward)] = btnDpadDown;
outInputState.actionsHeld[static_cast<int>(InputAction::MoveLeft)] = btnDpadLeft;
outInputState.actionsHeld[static_cast<int>(InputAction::MoveRight)] = btnDpadRight;
// Pressed this frame
outInputState.actions[static_cast<int>(InputAction::Action1)] = (buttonsDown & PAD_BUTTON_A) != 0;
outInputState.actions[static_cast<int>(InputAction::Action2)] = (buttonsDown & PAD_BUTTON_B) != 0;
outInputState.actions[static_cast<int>(InputAction::MenuOpen)] = (buttonsDown & PAD_BUTTON_START) != 0;
outInputState.actions[static_cast<int>(InputAction::Jump)] = (buttonsDown & PAD_BUTTON_A) != 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
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 GameCubePlatform::NormalizeAxis(int8_t value) const
{
// GameCube sticks are -128 to 127
float normalized = static_cast<float>(value) / 127.0f;
// Apply deadzone
const float deadzone = 0.15f;
if (std::abs(normalized) < deadzone)
return 0.0f;
// Rescale to account for deadzone
float sign = (normalized > 0.0f) ? 1.0f : -1.0f;
float absValue = std::abs(normalized);
return sign * ((absValue - deadzone) / (1.0f - deadzone));
}
float GameCubePlatform::NormalizeTrigger(uint8_t value) const
{
// GameCube triggers are 0-255
float normalized = static_cast<float>(value) / 255.0f;
// Apply trigger deadzone
const float deadzone = 0.1f;
if (normalized < deadzone)
return 0.0f;
// Rescale to account for deadzone
return (normalized - deadzone) / (1.0f - deadzone);
}
void GameCubePlatform::ResetInputFrameState(InputState& inputState)
{
inputState.ResetFrameStates();
}
IGraphicsContext* GameCubePlatform::GetGraphicsContext()
{
return m_graphicsContext.get();
}
const char* GameCubePlatform::GetUserDataPath() const
{
return "sd:/WillowVox/userdata";
}
const char* GameCubePlatform::GetAssetsPath() const
{
return "sd:/WillowVox/assets";
}
const char* GameCubePlatform::GetPlatformName() const
{
return "Nintendo GameCube";
}
InputDeviceType GameCubePlatform::GetPrimaryInputDevice() const
{
return InputDeviceType::GameCubeController;
}
bool GameCubePlatform::HasFeature(const char* featureName) const
{
if (strcmp(featureName, "gamepad") == 0) return true;
if (strcmp(featureName, "filesystem") == 0) return true;
return false;
}
}
#endif // PLATFORM_GAMECUBE
+14
View File
@@ -44,5 +44,19 @@ namespace WillowVox
private:
std::unique_ptr<GameCubeGraphicsContext> m_graphicsContext;
// GameCube controller input state
uint16_t m_buttons = 0;
uint16_t m_prevButtons = 0;
float m_mainStickX = 0.0f;
float m_mainStickY = 0.0f;
float m_cStickX = 0.0f;
float m_cStickY = 0.0f;
float m_leftTrigger = 0.0f;
float m_rightTrigger = 0.0f;
void UpdateGamepadInput(InputState& outInputState);
float NormalizeAxis(int8_t value) const;
float NormalizeTrigger(uint8_t value) const;
};
}
@@ -0,0 +1,225 @@
#include "WiiGraphicsContext.h"
#include <wv/Logger.h>
#ifdef PLATFORM_WII
// libogc includes
#include <gccore.h>
#include <ogc/system.h>
#include <ogc/video.h>
#include <ogc/gx.h>
#include <malloc.h>
#include <cstring>
#define DEFAULT_FIFO_SIZE (256 * 1024)
namespace WillowVox
{
WiiGraphicsContext::WiiGraphicsContext()
{
m_startTime = static_cast<float>(SYS_Time()) / TB_TIMER_CLOCK;
}
WiiGraphicsContext::~WiiGraphicsContext()
{
Shutdown();
}
bool WiiGraphicsContext::Initialize(int width, int height, const char* title)
{
Logger::EngineLog("Initializing Wii Graphics (GX)");
// Initialize VIDEO subsystem
VIDEO_Init();
// Get preferred video mode
GXRModeObj* rmode = VIDEO_GetPreferredMode(NULL);
m_renderMode = rmode;
m_width = rmode->fbWidth;
m_height = rmode->efbHeight;
Logger::EngineLog("Video mode: %dx%d", m_width, m_height);
// Allocate framebuffers (double buffering)
m_frameBuffer[0] = MEM_K0_TO_K1(SYS_AllocateFramebuffer(rmode));
m_frameBuffer[1] = MEM_K0_TO_K1(SYS_AllocateFramebuffer(rmode));
if (!m_frameBuffer[0] || !m_frameBuffer[1])
{
Logger::EngineError("Failed to allocate framebuffers");
return false;
}
// Configure VIDEO
VIDEO_Configure(rmode);
VIDEO_SetNextFramebuffer(m_frameBuffer[0]);
VIDEO_SetBlack(FALSE);
VIDEO_Flush();
VIDEO_WaitVSync();
if (rmode->viTVMode & VI_NON_INTERLACE)
VIDEO_WaitVSync();
// Allocate GX FIFO buffer
m_fifoBuffer = memalign(32, DEFAULT_FIFO_SIZE);
if (!m_fifoBuffer)
{
Logger::EngineError("Failed to allocate GX FIFO buffer");
return false;
}
memset(m_fifoBuffer, 0, DEFAULT_FIFO_SIZE);
// Initialize GX
GX_Init(m_fifoBuffer, DEFAULT_FIFO_SIZE);
// Set up GX for 3D rendering
GX_SetCopyClear((GXColor){26, 26, 26, 255}, 0x00FFFFFF);
// Set viewport
GX_SetViewport(0, 0, rmode->fbWidth, rmode->efbHeight, 0, 1);
f32 yscale = GX_GetYScaleFactor(rmode->efbHeight, rmode->xfbHeight);
u32 xfbHeight = GX_SetDispCopyYScale(yscale);
GX_SetScissor(0, 0, rmode->fbWidth, rmode->efbHeight);
GX_SetDispCopySrc(0, 0, rmode->fbWidth, rmode->efbHeight);
GX_SetDispCopyDst(rmode->fbWidth, xfbHeight);
GX_SetCopyFilter(rmode->aa, rmode->sample_pattern, GX_TRUE, rmode->vfilter);
GX_SetFieldMode(rmode->field_rendering, ((rmode->viHeight == 2 * rmode->xfbHeight) ? GX_ENABLE : GX_DISABLE));
// Set pixel format
if (rmode->aa)
GX_SetPixelFmt(GX_PF_RGB565_Z16, GX_ZC_LINEAR);
else
GX_SetPixelFmt(GX_PF_RGB8_Z24, GX_ZC_LINEAR);
// Enable depth testing
GX_SetZMode(GX_TRUE, GX_LEQUAL, GX_TRUE);
// Enable backface culling
GX_SetCullMode(GX_CULL_BACK);
// Enable blending
GX_SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR);
// Set color update
GX_SetColorUpdate(GX_TRUE);
Logger::EngineLog("Wii Graphics initialized (GX, %dx%d)", m_width, m_height);
m_initialized = true;
return true;
}
void WiiGraphicsContext::Shutdown()
{
if (m_initialized)
{
if (m_fifoBuffer)
{
free(m_fifoBuffer);
m_fifoBuffer = nullptr;
}
// Framebuffers are managed by libogc, no need to free
m_initialized = false;
Logger::EngineLog("Wii Graphics shutdown");
}
}
void WiiGraphicsContext::BeginFrame()
{
// Nothing specific needed
}
void WiiGraphicsContext::EndFrame()
{
// Execute all pending GX commands
GX_DrawDone();
}
void WiiGraphicsContext::SwapBuffers()
{
// Copy EFB to XFB
GX_CopyDisp(m_frameBuffer[m_currentFB], GX_TRUE);
GX_Flush();
// Swap framebuffers
VIDEO_SetNextFramebuffer(m_frameBuffer[m_currentFB]);
VIDEO_Flush();
if (m_vsyncEnabled)
VIDEO_WaitVSync();
m_currentFB ^= 1; // Toggle between 0 and 1
}
void WiiGraphicsContext::Clear(float r, float g, float b, float a)
{
GXColor color = {
static_cast<u8>(r * 255),
static_cast<u8>(g * 255),
static_cast<u8>(b * 255),
static_cast<u8>(a * 255)
};
GX_SetCopyClear(color, 0x00FFFFFF);
}
void WiiGraphicsContext::ClearDepth()
{
// GX clears depth automatically with GX_CopyDisp
}
bool WiiGraphicsContext::ShouldClose() const
{
return m_shouldClose;
}
void WiiGraphicsContext::SetShouldClose(bool shouldClose)
{
m_shouldClose = shouldClose;
}
void WiiGraphicsContext::GetFramebufferSize(int& width, int& height) const
{
width = m_width;
height = m_height;
}
void WiiGraphicsContext::GetWindowSize(int& width, int& height) const
{
width = m_width;
height = m_height;
}
void WiiGraphicsContext::SetVSync(bool enabled)
{
m_vsyncEnabled = enabled;
}
bool WiiGraphicsContext::IsVSyncEnabled() const
{
return m_vsyncEnabled;
}
float WiiGraphicsContext::GetTime() const
{
float currentTime = static_cast<float>(SYS_Time()) / TB_TIMER_CLOCK;
return currentTime - m_startTime;
}
void WiiGraphicsContext::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 WiiGraphicsContext::SetViewport(int x, int y, int width, int height)
{
GX_SetViewport(x, y, width, height, 0, 1);
GX_SetScissor(x, y, width, height);
}
}
#endif // PLATFORM_WII
@@ -36,8 +36,16 @@ namespace WillowVox
private:
bool m_initialized = false;
bool m_shouldClose = false;
bool m_vsyncEnabled = true;
int m_width = 640;
int m_height = 480;
float m_clearColor[4] = {0.1f, 0.1f, 0.1f, 1.0f};
float m_startTime = 0.0f;
// GX-specific framebuffer (using void* to avoid including libogc headers)
void* m_fifoBuffer = nullptr; // GX FIFO buffer
void* m_frameBuffer[2] = {nullptr, nullptr}; // Double buffering
int m_currentFB = 0;
void* m_renderMode = nullptr; // GXRModeObj*
};
}
+223
View File
@@ -0,0 +1,223 @@
#include "WiiPlatform.h"
#include "WiiGraphicsContext.h"
#include <wv/Logger.h>
#ifdef PLATFORM_WII
// libogc includes
#include <gccore.h>
#include <wiiuse/wpad.h>
#include <cmath>
namespace WillowVox
{
WiiPlatform::WiiPlatform()
{
}
WiiPlatform::~WiiPlatform()
{
Shutdown();
}
bool WiiPlatform::Initialize()
{
Logger::EngineLog("Initializing Wii Platform (libogc)");
// Initialize WPAD (Wiimote)
WPAD_Init();
WPAD_SetDataFormat(WPAD_CHAN_0, WPAD_FMT_BTNS_ACC_IR);
WPAD_SetVRes(WPAD_CHAN_0, 640, 480);
// Initialize graphics
m_graphicsContext = std::make_unique<WiiGraphicsContext>();
Logger::EngineLog("Wii Platform initialized");
return true;
}
void WiiPlatform::Shutdown()
{
WPAD_Shutdown();
m_graphicsContext.reset();
Logger::EngineLog("Wii Platform shutdown");
}
void WiiPlatform::ProcessEvents()
{
// Wii doesn't have traditional event polling
// Input is polled directly via WPAD_ScanPads()
}
void WiiPlatform::PollInput(InputState& outInputState)
{
UpdateWiimoteInput(outInputState);
outInputState.deviceType = InputDeviceType::WiiRemote;
}
void WiiPlatform::UpdateWiimoteInput(InputState& outInputState)
{
// Store previous button state
m_prevButtons = m_buttons;
// Scan for button presses
WPAD_ScanPads();
// Read Wiimote data
u32 pressed = WPAD_ButtonsDown(WPAD_CHAN_0);
u32 held = WPAD_ButtonsHeld(WPAD_CHAN_0);
m_buttons = held;
// Get expansion data
WPADData* data = WPAD_Data(WPAD_CHAN_0);
if (data && data->exp.type == WPAD_EXP_NUNCHUK)
{
// Nunchuk is connected - use for movement
joystick_t nunchuk = data->exp.nunchuk.js;
m_nunchukX = NormalizeAxis(nunchuk.pos.x, nunchuk.center.x);
m_nunchukY = NormalizeAxis(nunchuk.pos.y, nunchuk.center.y);
// Nunchuk buttons
if (data->exp.nunchuk.btns & NUNCHUK_BUTTON_Z)
m_buttons |= WPAD_BUTTON_HOME; // Map Z to crouch
if (data->exp.nunchuk.btns & NUNCHUK_BUTTON_C)
m_buttons |= WPAD_BUTTON_1; // Map C to sprint
}
else
{
// No Nunchuk - reset analog values
m_nunchukX = 0.0f;
m_nunchukY = 0.0f;
}
// Get IR pointer data
if (data && data->ir.valid)
{
m_irValid = true;
m_irX = static_cast<float>(data->ir.x) / 640.0f;
m_irY = static_cast<float>(data->ir.y) / 480.0f;
}
else
{
m_irValid = false;
}
// Map analog sticks
outInputState.moveAxisX = m_nunchukX;
outInputState.moveAxisY = m_nunchukY;
// IR pointer for look (if valid)
if (m_irValid)
{
// Center IR to -1..1 range
outInputState.lookAxisX = (m_irX - 0.5f) * 200.0f;
outInputState.lookAxisY = (m_irY - 0.5f) * 200.0f;
}
// Button mapping (Wiimote)
bool btnA = (m_buttons & WPAD_BUTTON_A) != 0;
bool btnB = (m_buttons & WPAD_BUTTON_B) != 0;
bool btn1 = (m_buttons & WPAD_BUTTON_1) != 0;
bool btn2 = (m_buttons & WPAD_BUTTON_2) != 0;
bool btnHome = (m_buttons & WPAD_BUTTON_HOME) != 0;
bool btnPlus = (m_buttons & WPAD_BUTTON_PLUS) != 0;
bool btnMinus = (m_buttons & WPAD_BUTTON_MINUS) != 0;
bool btnUp = (m_buttons & WPAD_BUTTON_UP) != 0;
bool btnDown = (m_buttons & WPAD_BUTTON_DOWN) != 0;
bool btnLeft = (m_buttons & WPAD_BUTTON_LEFT) != 0;
bool btnRight = (m_buttons & WPAD_BUTTON_RIGHT) != 0;
// Map to abstract actions
outInputState.actionsHeld[static_cast<int>(InputAction::Action1)] = btnA;
outInputState.actionsHeld[static_cast<int>(InputAction::Action2)] = btnB;
outInputState.actionsHeld[static_cast<int>(InputAction::Action3)] = btn1;
outInputState.actionsHeld[static_cast<int>(InputAction::Action4)] = btn2;
outInputState.actionsHeld[static_cast<int>(InputAction::Jump)] = btnA;
outInputState.actionsHeld[static_cast<int>(InputAction::MenuOpen)] = btnHome;
outInputState.actionsHeld[static_cast<int>(InputAction::MenuBack)] = btnB;
outInputState.actionsHeld[static_cast<int>(InputAction::CycleLeft)] = btnMinus;
outInputState.actionsHeld[static_cast<int>(InputAction::CycleRight)] = btnPlus;
// D-pad for movement
outInputState.actionsHeld[static_cast<int>(InputAction::MoveForward)] = btnUp;
outInputState.actionsHeld[static_cast<int>(InputAction::MoveBackward)] = btnDown;
outInputState.actionsHeld[static_cast<int>(InputAction::MoveLeft)] = btnLeft;
outInputState.actionsHeld[static_cast<int>(InputAction::MoveRight)] = btnRight;
// Pressed this frame
outInputState.actions[static_cast<int>(InputAction::Action1)] = (pressed & WPAD_BUTTON_A) != 0;
outInputState.actions[static_cast<int>(InputAction::Action2)] = (pressed & WPAD_BUTTON_B) != 0;
outInputState.actions[static_cast<int>(InputAction::MenuOpen)] = (pressed & WPAD_BUTTON_HOME) != 0;
outInputState.actions[static_cast<int>(InputAction::Jump)] = (pressed & WPAD_BUTTON_A) != 0;
// Analog to digital movement (Nunchuk)
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 WiiPlatform::NormalizeAxis(uint8_t value, uint8_t center) const
{
// Nunchuk stick is 0-255, center varies by device
float normalized = (static_cast<float>(value) - static_cast<float>(center)) / 128.0f;
// Apply deadzone
const float deadzone = 0.15f;
if (std::abs(normalized) < deadzone)
return 0.0f;
return normalized;
}
void WiiPlatform::ResetInputFrameState(InputState& inputState)
{
inputState.ResetFrameStates();
}
IGraphicsContext* WiiPlatform::GetGraphicsContext()
{
return m_graphicsContext.get();
}
const char* WiiPlatform::GetUserDataPath() const
{
return "sd:/WillowVox/userdata";
}
const char* WiiPlatform::GetAssetsPath() const
{
return "sd:/WillowVox/assets";
}
const char* WiiPlatform::GetPlatformName() const
{
return "Nintendo Wii";
}
InputDeviceType WiiPlatform::GetPrimaryInputDevice() const
{
return InputDeviceType::WiiRemote;
}
bool WiiPlatform::HasFeature(const char* featureName) const
{
if (strcmp(featureName, "gamepad") == 0) return true;
if (strcmp(featureName, "motion") == 0) return true;
if (strcmp(featureName, "filesystem") == 0) return true;
return false;
}
}
#endif // PLATFORM_WII
+12 -1
View File
@@ -43,6 +43,17 @@ namespace WillowVox
private:
std::unique_ptr<WiiGraphicsContext> m_graphicsContext;
// Wii-specific input handling
// Wiimote + Nunchuk input state
uint32_t m_buttons = 0;
uint32_t m_prevButtons = 0;
float m_nunchukX = 0.0f;
float m_nunchukY = 0.0f;
float m_irX = 0.0f;
float m_irY = 0.0f;
bool m_irValid = false;
void UpdateWiimoteInput(InputState& outInputState);
float NormalizeAxis(uint8_t value, uint8_t center = 128) const;
};
}
@@ -0,0 +1,275 @@
#include "WiiUGraphicsContext.h"
#include <wv/Logger.h>
#ifdef PLATFORM_WIIU
// wut library includes (Wii U homebrew)
#include <coreinit/time.h>
#include <coreinit/cache.h>
#include <coreinit/memheap.h>
#include <coreinit/screen.h>
#include <gx2/display.h>
#include <gx2/event.h>
#include <gx2/state.h>
#include <gx2/clear.h>
#include <gx2/context.h>
#include <gx2/swap.h>
#include <gx2/enum.h>
#include <malloc.h>
#include <cstring>
namespace WillowVox
{
WiiUGraphicsContext::WiiUGraphicsContext()
{
m_startTime = static_cast<float>(OSGetTime()) / static_cast<float>(OSTimerClockSpeed);
}
WiiUGraphicsContext::~WiiUGraphicsContext()
{
Shutdown();
}
bool WiiUGraphicsContext::Initialize(int width, int height, const char* title)
{
Logger::EngineLog("Initializing Wii U Graphics (GX2)");
// Get TV scan buffer dimensions
m_width = 1280; // Wii U TV default
m_height = 720;
// Initialize GX2
GX2Init(NULL);
// Allocate color buffer
GX2ColorBuffer* colorBuffer = (GX2ColorBuffer*)memalign(0x100, sizeof(GX2ColorBuffer));
if (!colorBuffer)
{
Logger::EngineError("Failed to allocate color buffer");
return false;
}
memset(colorBuffer, 0, sizeof(GX2ColorBuffer));
m_colorBuffer = colorBuffer;
// Setup color buffer
colorBuffer->surface.dim = GX2_SURFACE_DIM_TEXTURE_2D;
colorBuffer->surface.width = m_width;
colorBuffer->surface.height = m_height;
colorBuffer->surface.depth = 1;
colorBuffer->surface.mipLevels = 1;
colorBuffer->surface.format = GX2_SURFACE_FORMAT_UNORM_R8_G8_B8_A8;
colorBuffer->surface.use = GX2_SURFACE_USE_TEXTURE_COLOR_BUFFER_TV;
colorBuffer->viewNumSlices = 1;
// Calculate surface size
GX2CalcSurfaceSizeAndAlignment(&colorBuffer->surface);
GX2InitColorBufferRegs(colorBuffer);
// Allocate color buffer surface
colorBuffer->surface.image = memalign(colorBuffer->surface.alignment, colorBuffer->surface.imageSize);
if (!colorBuffer->surface.image)
{
Logger::EngineError("Failed to allocate color buffer surface");
return false;
}
// Allocate depth buffer
GX2DepthBuffer* depthBuffer = (GX2DepthBuffer*)memalign(0x100, sizeof(GX2DepthBuffer));
if (!depthBuffer)
{
Logger::EngineError("Failed to allocate depth buffer");
return false;
}
memset(depthBuffer, 0, sizeof(GX2DepthBuffer));
m_depthBuffer = depthBuffer;
// Setup depth buffer
depthBuffer->surface.dim = GX2_SURFACE_DIM_TEXTURE_2D;
depthBuffer->surface.width = m_width;
depthBuffer->surface.height = m_height;
depthBuffer->surface.depth = 1;
depthBuffer->surface.mipLevels = 1;
depthBuffer->surface.format = GX2_SURFACE_FORMAT_FLOAT_D24_S8;
depthBuffer->surface.use = GX2_SURFACE_USE_DEPTH_BUFFER;
depthBuffer->viewNumSlices = 1;
// Calculate depth surface size
GX2CalcSurfaceSizeAndAlignment(&depthBuffer->surface);
GX2InitDepthBufferRegs(depthBuffer);
// Allocate depth buffer surface
depthBuffer->surface.image = memalign(depthBuffer->surface.alignment, depthBuffer->surface.imageSize);
if (!depthBuffer->surface.image)
{
Logger::EngineError("Failed to allocate depth buffer surface");
return false;
}
// Allocate context state
GX2ContextState* contextState = (GX2ContextState*)memalign(GX2_CONTEXT_STATE_ALIGNMENT, sizeof(GX2ContextState));
if (!contextState)
{
Logger::EngineError("Failed to allocate context state");
return false;
}
GX2SetupContextStateEx(contextState, GX2_TRUE);
m_contextState = contextState;
// Set context state
GX2SetContextState(contextState);
// Set color and depth buffers
GX2SetColorBuffer(colorBuffer, GX2_RENDER_TARGET_0);
GX2SetDepthBuffer(depthBuffer);
// Set viewport
GX2SetViewport(0, 0, m_width, m_height, 0.0f, 1.0f);
GX2SetScissor(0, 0, m_width, m_height);
// Enable depth test
GX2SetDepthOnlyControl(GX2_ENABLE, GX2_ENABLE, GX2_COMPARE_FUNC_LEQUAL);
// Enable backface culling
GX2SetCullOnlyControl(GX2_FRONT_FACE_CCW, GX2_ENABLE, GX2_DISABLE);
// Enable alpha blending
GX2SetColorControl(GX2_LOGIC_OP_COPY, GX2_ENABLE, GX2_DISABLE, GX2_ENABLE);
GX2SetBlendControl(GX2_RENDER_TARGET_0, GX2_BLEND_MODE_SRC_ALPHA, GX2_BLEND_MODE_INV_SRC_ALPHA,
GX2_BLEND_COMBINE_MODE_ADD, GX2_ENABLE,
GX2_BLEND_MODE_SRC_ALPHA, GX2_BLEND_MODE_INV_SRC_ALPHA,
GX2_BLEND_COMBINE_MODE_ADD);
Logger::EngineLog("Wii U Graphics initialized (GX2, %dx%d)", m_width, m_height);
m_initialized = true;
return true;
}
void WiiUGraphicsContext::Shutdown()
{
if (m_initialized)
{
if (m_colorBuffer)
{
GX2ColorBuffer* cb = (GX2ColorBuffer*)m_colorBuffer;
if (cb->surface.image)
free(cb->surface.image);
free(cb);
m_colorBuffer = nullptr;
}
if (m_depthBuffer)
{
GX2DepthBuffer* db = (GX2DepthBuffer*)m_depthBuffer;
if (db->surface.image)
free(db->surface.image);
free(db);
m_depthBuffer = nullptr;
}
if (m_contextState)
{
free(m_contextState);
m_contextState = nullptr;
}
GX2Shutdown();
m_initialized = false;
Logger::EngineLog("Wii U Graphics shutdown");
}
}
void WiiUGraphicsContext::BeginFrame()
{
// Nothing specific needed
}
void WiiUGraphicsContext::EndFrame()
{
// Flush GPU
GX2Flush();
}
void WiiUGraphicsContext::SwapBuffers()
{
// Copy color buffer to TV scan buffer
GX2CopyColorBufferToScanBuffer((GX2ColorBuffer*)m_colorBuffer, GX2_SCAN_TARGET_TV);
// Swap scan buffers
GX2SwapScanBuffers();
if (m_vsyncEnabled)
{
GX2WaitForVsync();
}
// Flush and wait
GX2Flush();
GX2DrawDone();
}
void WiiUGraphicsContext::Clear(float r, float g, float b, float a)
{
GX2ClearColor((GX2ColorBuffer*)m_colorBuffer, r, g, b, a);
GX2ClearDepthStencilEx((GX2DepthBuffer*)m_depthBuffer, 1.0f, 0, GX2_CLEAR_FLAGS_BOTH);
}
void WiiUGraphicsContext::ClearDepth()
{
GX2ClearDepthStencilEx((GX2DepthBuffer*)m_depthBuffer, 1.0f, 0, GX2_CLEAR_FLAGS_DEPTH);
}
bool WiiUGraphicsContext::ShouldClose() const
{
return m_shouldClose;
}
void WiiUGraphicsContext::SetShouldClose(bool shouldClose)
{
m_shouldClose = shouldClose;
}
void WiiUGraphicsContext::GetFramebufferSize(int& width, int& height) const
{
width = m_width;
height = m_height;
}
void WiiUGraphicsContext::GetWindowSize(int& width, int& height) const
{
width = m_width;
height = m_height;
}
void WiiUGraphicsContext::SetVSync(bool enabled)
{
m_vsyncEnabled = enabled;
}
bool WiiUGraphicsContext::IsVSyncEnabled() const
{
return m_vsyncEnabled;
}
float WiiUGraphicsContext::GetTime() const
{
float currentTime = static_cast<float>(OSGetTime()) / static_cast<float>(OSTimerClockSpeed);
return currentTime - m_startTime;
}
void WiiUGraphicsContext::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 WiiUGraphicsContext::SetViewport(int x, int y, int width, int height)
{
GX2SetViewport(x, y, width, height, 0.0f, 1.0f);
GX2SetScissor(x, y, width, height);
}
}
#endif // PLATFORM_WIIU
@@ -36,8 +36,16 @@ namespace WillowVox
private:
bool m_initialized = false;
bool m_shouldClose = false;
bool m_vsyncEnabled = true;
int m_width = 1920;
int m_height = 1080;
float m_clearColor[4] = {0.1f, 0.1f, 0.1f, 1.0f};
float m_startTime = 0.0f;
// GX2-specific (using void* to avoid including wut headers)
void* m_colorBuffer = nullptr; // GX2ColorBuffer*
void* m_depthBuffer = nullptr; // GX2DepthBuffer*
void* m_contextState = nullptr; // GX2ContextState*
int m_currentTV = 0; // TV scan buffer index
};
}
+211
View File
@@ -0,0 +1,211 @@
#include "WiiUPlatform.h"
#include "WiiUGraphicsContext.h"
#include <wv/Logger.h>
#ifdef PLATFORM_WIIU
// wut library includes (Wii U homebrew)
#include <vpad/input.h>
#include <padscore/kpad.h>
#include <coreinit/time.h>
#include <cmath>
namespace WillowVox
{
WiiUPlatform::WiiUPlatform()
{
}
WiiUPlatform::~WiiUPlatform()
{
Shutdown();
}
bool WiiUPlatform::Initialize()
{
Logger::EngineLog("Initializing Wii U Platform (wut)");
// Initialize VPAD (GamePad)
VPADInit();
// Initialize KPAD (Pro Controller / Wiimote)
KPADInit();
// Initialize graphics
m_graphicsContext = std::make_unique<WiiUGraphicsContext>();
Logger::EngineLog("Wii U Platform initialized");
return true;
}
void WiiUPlatform::Shutdown()
{
KPADShutdown();
VPADShutdown();
m_graphicsContext.reset();
Logger::EngineLog("Wii U Platform shutdown");
}
void WiiUPlatform::ProcessEvents()
{
// Wii U doesn't have traditional event polling
// Input is polled directly via VPADRead()
}
void WiiUPlatform::PollInput(InputState& outInputState)
{
UpdateGamepadInput(outInputState);
outInputState.deviceType = InputDeviceType::WiiUGamePad;
}
void WiiUPlatform::UpdateGamepadInput(InputState& outInputState)
{
// Store previous button state
m_prevButtons = m_buttons;
// Read GamePad data (VPAD)
VPADStatus vpadStatus;
VPADReadError error;
VPADRead(VPAD_CHAN_0, &vpadStatus, 1, &error);
if (error == VPAD_READ_SUCCESS)
{
// Store button state
m_buttons = vpadStatus.hold;
// Read analog sticks
m_lstickX = NormalizeAxis(vpadStatus.leftStick.x, -1.0f, 1.0f);
m_lstickY = NormalizeAxis(vpadStatus.leftStick.y, -1.0f, 1.0f);
m_rstickX = NormalizeAxis(vpadStatus.rightStick.x, -1.0f, 1.0f);
m_rstickY = NormalizeAxis(vpadStatus.rightStick.y, -1.0f, 1.0f);
}
else
{
// No input, reset to defaults
m_buttons = 0;
m_lstickX = 0.0f;
m_lstickY = 0.0f;
m_rstickX = 0.0f;
m_rstickY = 0.0f;
}
// Map analog sticks
outInputState.moveAxisX = m_lstickX;
outInputState.moveAxisY = m_lstickY;
outInputState.lookAxisX = m_rstickX * 100.0f;
outInputState.lookAxisY = -m_rstickY * 100.0f; // Invert Y for camera
// Button mapping (Wii U GamePad - similar to Pro Controller)
bool btnA = (m_buttons & VPAD_BUTTON_A) != 0;
bool btnB = (m_buttons & VPAD_BUTTON_B) != 0;
bool btnX = (m_buttons & VPAD_BUTTON_X) != 0;
bool btnY = (m_buttons & VPAD_BUTTON_Y) != 0;
bool btnL = (m_buttons & VPAD_BUTTON_L) != 0;
bool btnR = (m_buttons & VPAD_BUTTON_R) != 0;
bool btnZL = (m_buttons & VPAD_BUTTON_ZL) != 0;
bool btnZR = (m_buttons & VPAD_BUTTON_ZR) != 0;
bool btnPlus = (m_buttons & VPAD_BUTTON_PLUS) != 0;
bool btnMinus = (m_buttons & VPAD_BUTTON_MINUS) != 0;
bool btnHome = (m_buttons & VPAD_BUTTON_HOME) != 0;
bool btnDpadUp = (m_buttons & VPAD_BUTTON_UP) != 0;
bool btnDpadDown = (m_buttons & VPAD_BUTTON_DOWN) != 0;
bool btnDpadLeft = (m_buttons & VPAD_BUTTON_LEFT) != 0;
bool btnDpadRight = (m_buttons & VPAD_BUTTON_RIGHT) != 0;
// Map to abstract actions
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 || btnHome;
outInputState.actionsHeld[static_cast<int>(InputAction::MenuBack)] = btnB || btnMinus;
// D-pad for movement
outInputState.actionsHeld[static_cast<int>(InputAction::MoveForward)] = btnDpadUp;
outInputState.actionsHeld[static_cast<int>(InputAction::MoveBackward)] = btnDpadDown;
outInputState.actionsHeld[static_cast<int>(InputAction::MoveLeft)] = btnDpadLeft;
outInputState.actionsHeld[static_cast<int>(InputAction::MoveRight)] = btnDpadRight;
// Pressed this frame
uint32_t pressed = m_buttons & ~m_prevButtons;
outInputState.actions[static_cast<int>(InputAction::Action1)] = (pressed & VPAD_BUTTON_A) != 0;
outInputState.actions[static_cast<int>(InputAction::Action2)] = (pressed & VPAD_BUTTON_B) != 0;
outInputState.actions[static_cast<int>(InputAction::MenuOpen)] = (pressed & VPAD_BUTTON_PLUS) != 0;
outInputState.actions[static_cast<int>(InputAction::Jump)] = (pressed & VPAD_BUTTON_A) != 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
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 WiiUPlatform::NormalizeAxis(float value, float min, float max) const
{
// VPAD sticks are already -1.0 to 1.0
// Apply deadzone
const float deadzone = 0.15f;
if (std::abs(value) < deadzone)
return 0.0f;
// Rescale to account for deadzone
float sign = (value > 0.0f) ? 1.0f : -1.0f;
float absValue = std::abs(value);
return sign * ((absValue - deadzone) / (1.0f - deadzone));
}
void WiiUPlatform::ResetInputFrameState(InputState& inputState)
{
inputState.ResetFrameStates();
}
IGraphicsContext* WiiUPlatform::GetGraphicsContext()
{
return m_graphicsContext.get();
}
const char* WiiUPlatform::GetUserDataPath() const
{
return "fs:/vol/external01/WillowVox/userdata";
}
const char* WiiUPlatform::GetAssetsPath() const
{
return "fs:/vol/content/WillowVox/assets";
}
const char* WiiUPlatform::GetPlatformName() const
{
return "Nintendo Wii U";
}
InputDeviceType WiiUPlatform::GetPrimaryInputDevice() const
{
return InputDeviceType::WiiUGamePad;
}
bool WiiUPlatform::HasFeature(const char* featureName) const
{
if (strcmp(featureName, "gamepad") == 0) return true;
if (strcmp(featureName, "touchscreen") == 0) return true; // GamePad has touchscreen
if (strcmp(featureName, "filesystem") == 0) return true;
return false;
}
}
#endif // PLATFORM_WIIU
+11
View File
@@ -36,5 +36,16 @@ namespace WillowVox
private:
std::unique_ptr<WiiUGraphicsContext> m_graphicsContext;
// Wii U GamePad/Pro Controller input state
uint32_t m_buttons = 0;
uint32_t m_prevButtons = 0;
float m_lstickX = 0.0f;
float m_lstickY = 0.0f;
float m_rstickX = 0.0f;
float m_rstickY = 0.0f;
void UpdateGamepadInput(InputState& outInputState);
float NormalizeAxis(float value, float min, float max) const;
};
}
+255
View File
@@ -0,0 +1,255 @@
#include "PS4GraphicsContext.h"
#include <wv/Logger.h>
#ifdef PLATFORM_PS4
// OpenOrbis includes
#include <orbis/Pigletv2VSH.h>
#include <orbis/VideoOut.h>
#include <orbis/libkernel.h>
#include <EGL/egl.h>
#include <GLES2/gl2.h>
namespace WillowVox
{
PS4GraphicsContext::PS4GraphicsContext()
{
m_startTime = sceKernelGetProcessTime() / 1000000000.0f;
}
PS4GraphicsContext::~PS4GraphicsContext()
{
Shutdown();
}
bool PS4GraphicsContext::Initialize(int width, int height, const char* title)
{
m_width = width;
m_height = height;
Logger::EngineLog("Initializing PS4 Graphics (OpenGL via piglet)");
// Initialize video out
int ret = sceVideoOutOpen(ORBIS_USER_SERVICE_USER_ID_SYSTEM, ORBIS_VIDEO_OUT_BUS_TYPE_MAIN, 0, NULL);
if (ret < 0)
{
Logger::EngineError("Failed to open video out: 0x%08X", ret);
return false;
}
m_videoHandle = ret;
// Initialize piglet (OpenGL wrapper for PS4)
ret = scePigletSetConfigurationVSH(SCE_PIGLET_CONFIGURATION_VSH_API_VERSION,
SCE_PIGLET_API_VERSION_GLES20);
if (ret != 0)
{
Logger::EngineError("Failed to set piglet configuration: 0x%08X", ret);
return false;
}
// Get EGL display
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (display == EGL_NO_DISPLAY)
{
Logger::EngineError("eglGetDisplay failed");
return false;
}
m_eglDisplay = display;
// Initialize EGL
if (!eglInitialize(display, nullptr, nullptr))
{
Logger::EngineError("eglInitialize failed");
return false;
}
// Choose EGL config
const EGLint attribs[] = {
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_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 EGL window surface
EGLSurface surface = eglCreateWindowSurface(display, config,
(EGLNativeWindowType)m_videoHandle, nullptr);
if (surface == EGL_NO_SURFACE)
{
Logger::EngineError("eglCreateWindowSurface failed");
return false;
}
m_eglSurface = surface;
// Create EGL context
const EGLint contextAttribs[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE
};
EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs);
if (context == EGL_NO_CONTEXT)
{
Logger::EngineError("eglCreateContext failed");
return false;
}
m_eglContext = context;
// Make context current
if (!eglMakeCurrent(display, surface, surface, context))
{
Logger::EngineError("eglMakeCurrent failed");
return false;
}
// Get actual surface size
eglQuerySurface(display, surface, EGL_WIDTH, &m_width);
eglQuerySurface(display, surface, EGL_HEIGHT, &m_height);
// Enable OpenGL ES features
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
Logger::EngineLog("PS4 Graphics initialized (OpenGL ES 2.0, %dx%d)", m_width, m_height);
m_initialized = true;
return true;
}
void PS4GraphicsContext::Shutdown()
{
if (m_initialized)
{
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;
}
if (m_videoHandle >= 0)
{
sceVideoOutClose(m_videoHandle);
m_videoHandle = -1;
}
m_initialized = false;
}
}
void PS4GraphicsContext::BeginFrame()
{
// Nothing specific needed
}
void PS4GraphicsContext::EndFrame()
{
// Nothing specific needed
}
void PS4GraphicsContext::SwapBuffers()
{
if (m_eglDisplay && m_eglSurface)
{
eglSwapBuffers((EGLDisplay)m_eglDisplay, (EGLSurface)m_eglSurface);
}
}
void PS4GraphicsContext::Clear(float r, float g, float b, float a)
{
glClearColor(r, g, b, a);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
void PS4GraphicsContext::ClearDepth()
{
glClear(GL_DEPTH_BUFFER_BIT);
}
bool PS4GraphicsContext::ShouldClose() const
{
return m_shouldClose;
}
void PS4GraphicsContext::SetShouldClose(bool shouldClose)
{
m_shouldClose = shouldClose;
}
void PS4GraphicsContext::GetFramebufferSize(int& width, int& height) const
{
width = m_width;
height = m_height;
}
void PS4GraphicsContext::GetWindowSize(int& width, int& height) const
{
width = m_width;
height = m_height;
}
void PS4GraphicsContext::SetVSync(bool enabled)
{
m_vsyncEnabled = enabled;
if (m_eglDisplay)
{
eglSwapInterval((EGLDisplay)m_eglDisplay, enabled ? 1 : 0);
}
}
bool PS4GraphicsContext::IsVSyncEnabled() const
{
return m_vsyncEnabled;
}
float PS4GraphicsContext::GetTime() const
{
float currentTime = sceKernelGetProcessTime() / 1000000000.0f;
return currentTime - m_startTime;
}
void PS4GraphicsContext::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 PS4GraphicsContext::SetViewport(int x, int y, int width, int height)
{
glViewport(x, y, width, height);
}
}
#endif // PLATFORM_PS4
+11 -3
View File
@@ -5,10 +5,10 @@
namespace WillowVox
{
/**
* PS4 Graphics Context (GNM)
* PS4 Graphics Context (OpenGL ES via piglet)
*
* PS4 uses GNM (low-level graphics API) via OpenOrbis SDK.
* This is a template implementation.
* PS4 uses OpenGL ES 2.0 via piglet library (OpenOrbis SDK).
* This is easier than GNM and provides good compatibility.
*/
class PS4GraphicsContext : public IGraphicsContext
{
@@ -36,8 +36,16 @@ namespace WillowVox
private:
bool m_initialized = false;
bool m_shouldClose = false;
bool m_vsyncEnabled = true;
int m_width = 1920;
int m_height = 1080;
float m_clearColor[4] = {0.1f, 0.1f, 0.1f, 1.0f};
float m_startTime = 0.0f;
// PS4-specific handles (using void* to avoid including headers)
int m_videoHandle = -1;
void* m_eglDisplay = nullptr;
void* m_eglSurface = nullptr;
void* m_eglContext = nullptr;
};
}
+254
View File
@@ -0,0 +1,254 @@
#include "PS4Platform.h"
#include "PS4GraphicsContext.h"
#include <wv/Logger.h>
#ifdef PLATFORM_PS4
// OpenOrbis includes
#include <orbis/Pad.h>
#include <orbis/UserService.h>
#include <orbis/libkernel.h>
namespace WillowVox
{
PS4Platform::PS4Platform()
{
}
PS4Platform::~PS4Platform()
{
Shutdown();
}
bool PS4Platform::Initialize()
{
Logger::EngineLog("Initializing PS4 Platform (OpenOrbis)");
// Initialize user service
int ret = sceUserServiceInitialize(NULL);
if (ret != 0)
{
Logger::EngineError("Failed to initialize user service: 0x%08X", ret);
return false;
}
// Get initial user ID
ret = sceUserServiceGetInitialUser(&m_userId);
if (ret != 0)
{
Logger::EngineError("Failed to get initial user: 0x%08X", ret);
return false;
}
// Initialize pad library
ret = scePadInit();
if (ret != 0)
{
Logger::EngineError("Failed to initialize pad: 0x%08X", ret);
return false;
}
// Open pad for user
m_padHandle = scePadOpen(m_userId, 0, 0, NULL);
if (m_padHandle < 0)
{
Logger::EngineError("Failed to open pad: 0x%08X", m_padHandle);
return false;
}
// Initialize graphics
m_graphicsContext = std::make_unique<PS4GraphicsContext>();
Logger::EngineLog("PS4 Platform initialized (User ID: %d, Pad: %d)", m_userId, m_padHandle);
return true;
}
void PS4Platform::Shutdown()
{
if (m_padHandle >= 0)
{
scePadClose(m_padHandle);
m_padHandle = -1;
}
m_graphicsContext.reset();
Logger::EngineLog("PS4 Platform shutdown");
}
void PS4Platform::ProcessEvents()
{
// PS4 doesn't have traditional event polling like desktop
// Events are handled through system callbacks
}
void PS4Platform::PollInput(InputState& outInputState)
{
UpdateGamepadInput(outInputState);
outInputState.deviceType = InputDeviceType::PSController;
}
void PS4Platform::UpdateGamepadInput(InputState& outInputState)
{
if (m_padHandle < 0)
return;
// Store previous button state
m_prevButtons = m_buttons;
// Read pad data
ScePadData padData;
int ret = scePadReadState(m_padHandle, &padData);
if (ret == 0 && padData.connected)
{
// Get button states
m_buttons = padData.buttons;
// Get analog stick values
m_lstickX = padData.leftStick.x;
m_lstickY = padData.leftStick.y;
m_rstickX = padData.rightStick.x;
m_rstickY = padData.rightStick.y;
// Trigger values
m_l2Value = padData.analogButtons.l2;
m_r2Value = padData.analogButtons.r2;
}
else
{
// Controller disconnected, reset to defaults
m_buttons = 0;
m_lstickX = 128;
m_lstickY = 128;
m_rstickX = 128;
m_rstickY = 128;
m_l2Value = 0;
m_r2Value = 0;
}
// Analog sticks
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 (DualShock 4)
bool btnCross = (m_buttons & SCE_PAD_BUTTON_CROSS) != 0;
bool btnCircle = (m_buttons & SCE_PAD_BUTTON_CIRCLE) != 0;
bool btnSquare = (m_buttons & SCE_PAD_BUTTON_SQUARE) != 0;
bool btnTriangle = (m_buttons & SCE_PAD_BUTTON_TRIANGLE) != 0;
bool btnL1 = (m_buttons & SCE_PAD_BUTTON_L1) != 0;
bool btnR1 = (m_buttons & SCE_PAD_BUTTON_R1) != 0;
bool btnL2 = m_l2Value > 64; // Digital threshold for analog trigger
bool btnR2 = m_r2Value > 64;
bool btnOptions = (m_buttons & SCE_PAD_BUTTON_OPTIONS) != 0;
bool btnTouchPad = (m_buttons & SCE_PAD_BUTTON_TOUCH_PAD) != 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)] = btnOptions;
outInputState.actionsHeld[static_cast<int>(InputAction::MenuBack)] = btnCircle;
// Pressed this frame
uint32_t pressed = m_buttons & ~m_prevButtons;
outInputState.actions[static_cast<int>(InputAction::Action1)] = (pressed & SCE_PAD_BUTTON_CROSS) != 0;
outInputState.actions[static_cast<int>(InputAction::Action2)] = (pressed & SCE_PAD_BUTTON_CIRCLE) != 0;
outInputState.actions[static_cast<int>(InputAction::MenuOpen)] = (pressed & SCE_PAD_BUTTON_OPTIONS) != 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
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 PS4Platform::NormalizeAxis(uint8_t value) const
{
// PS4 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 PS4Platform::ResetInputFrameState(InputState& inputState)
{
inputState.ResetFrameStates();
}
IGraphicsContext* PS4Platform::GetGraphicsContext()
{
return m_graphicsContext.get();
}
const char* PS4Platform::GetUserDataPath() const
{
return "/data/WillowVox/userdata";
}
const char* PS4Platform::GetAssetsPath() const
{
return "/app0/assets";
}
const char* PS4Platform::GetPlatformName() const
{
return "PlayStation 4";
}
InputDeviceType PS4Platform::GetPrimaryInputDevice() const
{
return InputDeviceType::PSController;
}
bool PS4Platform::HasFeature(const char* featureName) const
{
if (strcmp(featureName, "gamepad") == 0) return true;
if (strcmp(featureName, "filesystem") == 0) return true;
return false;
}
void PS4Platform::SetVibration(int playerIndex, float lowFrequency, float highFrequency)
{
if (m_padHandle < 0)
return;
// PS4 uses scePadSetVibration
// Values are 0-255
ScePadVibrationParam vibParam;
vibParam.largeMotor = static_cast<uint8_t>(lowFrequency * 255.0f);
vibParam.smallMotor = static_cast<uint8_t>(highFrequency * 255.0f);
scePadSetVibration(m_padHandle, &vibParam);
}
void PS4Platform::StopVibration(int playerIndex)
{
SetVibration(playerIndex, 0.0f, 0.0f);
}
}
#endif // PLATFORM_PS4
+31 -5
View File
@@ -10,11 +10,18 @@ namespace WillowVox
/**
* PlayStation 4 Platform (OpenOrbis Homebrew)
*
* Controller: DualShock 4 (same mapping as PS3)
* Graphics: GNM (low-level) or Gnm wrapper
* Input: libSceUserService + libScePad
* Controller: DualShock 4
* - Cross (X): Action1 / Jump
* - Circle: Action2 / MenuBack
* - Square: Action3
* - Triangle: Action4
* - L1/R1: Cycle items
* - L2/R2: Crouch/Sprint
* - Options: MenuOpen
* - TouchPad: Special action
*
* See PS3Platform for controller mapping (similar button layout)
* Graphics: OpenGL via piglet (easier than GNM)
* Input: libSceUserService + libScePad (OpenOrbis)
*/
class PS4Platform : public IPlatform
{
@@ -34,8 +41,27 @@ namespace WillowVox
InputDeviceType GetPrimaryInputDevice() const override;
bool HasFeature(const char* featureName) const override;
void SetVibration(int playerIndex, float lowFrequency, float highFrequency) override;
void StopVibration(int playerIndex) override;
private:
std::unique_ptr<PS4GraphicsContext> m_graphicsContext;
// PS4-specific pad handling (similar to PS3)
// PS4-specific handles
int m_userId = 0;
int m_padHandle = -1;
// Pad state
uint32_t m_buttons = 0;
uint32_t m_prevButtons = 0;
uint8_t m_lstickX = 128;
uint8_t m_lstickY = 128;
uint8_t m_rstickX = 128;
uint8_t m_rstickY = 128;
uint8_t m_l2Value = 0;
uint8_t m_r2Value = 0;
void UpdateGamepadInput(InputState& outInputState);
float NormalizeAxis(uint8_t value) const;
};
}
@@ -0,0 +1,242 @@
#include "XboxSeriesGraphicsContext.h"
#include <wv/Logger.h>
#ifdef PLATFORM_XBOX
// UWP includes
#include <Windows.h>
#include <wrl/client.h>
// ANGLE includes (OpenGL ES via DirectX)
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <EGL/eglplatform.h>
#include <GLES3/gl3.h>
using namespace Microsoft::WRL;
namespace WillowVox
{
XboxSeriesGraphicsContext::XboxSeriesGraphicsContext()
{
m_startTime = static_cast<float>(GetTickCount64()) / 1000.0f;
}
XboxSeriesGraphicsContext::~XboxSeriesGraphicsContext()
{
Shutdown();
}
bool XboxSeriesGraphicsContext::Initialize(int width, int height, const char* title)
{
m_width = width;
m_height = height;
Logger::EngineLog("Initializing Xbox Graphics (OpenGL ES via ANGLE)");
// Get EGL display (ANGLE will translate to DirectX)
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (display == EGL_NO_DISPLAY)
{
Logger::EngineError("eglGetDisplay failed");
return false;
}
m_eglDisplay = display;
// Initialize EGL
EGLint major, minor;
if (!eglInitialize(display, &major, &minor))
{
Logger::EngineError("eglInitialize failed");
return false;
}
Logger::EngineLog("EGL Version: %d.%d", major, minor);
// Choose EGL config for OpenGL ES 3.0
const EGLint attribs[] = {
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES3_BIT,
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_BLUE_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;
}
// In a full UWP implementation, you would get the CoreWindow here
// For now, we create a window surface with default parameters
EGLint surfaceAttribs[] = {
EGL_NONE
};
EGLSurface surface = eglCreateWindowSurface(display, config, nullptr, surfaceAttribs);
if (surface == EGL_NO_SURFACE)
{
Logger::EngineError("eglCreateWindowSurface failed: 0x%X", eglGetError());
return false;
}
m_eglSurface = surface;
// Create OpenGL ES 3.0 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;
}
m_eglContext = context;
// Make context current
if (!eglMakeCurrent(display, surface, surface, context))
{
Logger::EngineError("eglMakeCurrent failed");
return false;
}
// Query surface size
eglQuerySurface(display, surface, EGL_WIDTH, &m_width);
eglQuerySurface(display, surface, EGL_HEIGHT, &m_height);
// Enable OpenGL ES features
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
Logger::EngineLog("Xbox Graphics initialized (OpenGL ES 3.0, %dx%d)", m_width, m_height);
m_initialized = true;
return true;
}
void XboxSeriesGraphicsContext::Shutdown()
{
if (m_initialized)
{
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;
}
m_initialized = false;
}
}
void XboxSeriesGraphicsContext::BeginFrame()
{
// Nothing specific needed
}
void XboxSeriesGraphicsContext::EndFrame()
{
// Nothing specific needed
}
void XboxSeriesGraphicsContext::SwapBuffers()
{
if (m_eglDisplay && m_eglSurface)
{
eglSwapBuffers((EGLDisplay)m_eglDisplay, (EGLSurface)m_eglSurface);
}
}
void XboxSeriesGraphicsContext::Clear(float r, float g, float b, float a)
{
glClearColor(r, g, b, a);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
void XboxSeriesGraphicsContext::ClearDepth()
{
glClear(GL_DEPTH_BUFFER_BIT);
}
bool XboxSeriesGraphicsContext::ShouldClose() const
{
return m_shouldClose;
}
void XboxSeriesGraphicsContext::SetShouldClose(bool shouldClose)
{
m_shouldClose = shouldClose;
}
void XboxSeriesGraphicsContext::GetFramebufferSize(int& width, int& height) const
{
width = m_width;
height = m_height;
}
void XboxSeriesGraphicsContext::GetWindowSize(int& width, int& height) const
{
width = m_width;
height = m_height;
}
void XboxSeriesGraphicsContext::SetVSync(bool enabled)
{
m_vsyncEnabled = enabled;
if (m_eglDisplay)
{
eglSwapInterval((EGLDisplay)m_eglDisplay, enabled ? 1 : 0);
}
}
bool XboxSeriesGraphicsContext::IsVSyncEnabled() const
{
return m_vsyncEnabled;
}
float XboxSeriesGraphicsContext::GetTime() const
{
float currentTime = static_cast<float>(GetTickCount64()) / 1000.0f;
return currentTime - m_startTime;
}
void XboxSeriesGraphicsContext::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 XboxSeriesGraphicsContext::SetViewport(int x, int y, int width, int height)
{
glViewport(x, y, width, height);
}
}
#endif // PLATFORM_XBOX
+11 -3
View File
@@ -5,10 +5,10 @@
namespace WillowVox
{
/**
* Xbox Series X/S Graphics Context (DirectX 12)
* Xbox Series X/S Graphics Context (UWP with OpenGL via ANGLE)
*
* Xbox uses DirectX 12 via the GDK (Game Development Kit).
* This is a template implementation.
* Uses ANGLE (Almost Native Graphics Layer Evolution) to translate
* OpenGL ES calls to DirectX on Xbox via UWP.
*/
class XboxSeriesGraphicsContext : public IGraphicsContext
{
@@ -36,8 +36,16 @@ namespace WillowVox
private:
bool m_initialized = false;
bool m_shouldClose = false;
bool m_vsyncEnabled = true;
int m_width = 1920;
int m_height = 1080;
float m_clearColor[4] = {0.1f, 0.1f, 0.1f, 1.0f};
float m_startTime = 0.0f;
// UWP/ANGLE handles (using void* to avoid including headers)
void* m_coreWindow = nullptr; // Windows::UI::Core::CoreWindow^
void* m_eglDisplay = nullptr; // EGLDisplay
void* m_eglSurface = nullptr; // EGLSurface
void* m_eglContext = nullptr; // EGLContext
};
}
+249
View File
@@ -0,0 +1,249 @@
#include "XboxSeriesPlatform.h"
#include "XboxSeriesGraphicsContext.h"
#include <wv/Logger.h>
#ifdef PLATFORM_XBOX
// Windows/Xbox includes
#include <Windows.h>
#include <Xinput.h>
#include <cmath>
// Link XInput library
#pragma comment(lib, "xinput.lib")
namespace WillowVox
{
XboxSeriesPlatform::XboxSeriesPlatform()
{
}
XboxSeriesPlatform::~XboxSeriesPlatform()
{
Shutdown();
}
bool XboxSeriesPlatform::Initialize()
{
Logger::EngineLog("Initializing Xbox Series Platform (UWP)");
// Initialize graphics context
m_graphicsContext = std::make_unique<XboxSeriesGraphicsContext>();
Logger::EngineLog("Xbox Series Platform initialized");
return true;
}
void XboxSeriesPlatform::Shutdown()
{
m_graphicsContext.reset();
Logger::EngineLog("Xbox Series Platform shutdown");
}
void XboxSeriesPlatform::ProcessEvents()
{
// UWP event processing would be handled by CoreWindow dispatcher
// This is called from the main game loop
}
void XboxSeriesPlatform::PollInput(InputState& outInputState)
{
UpdateGamepadInput(outInputState);
outInputState.deviceType = InputDeviceType::XboxController;
}
void XboxSeriesPlatform::UpdateGamepadInput(InputState& outInputState)
{
// Store previous button state
m_prevButtons = m_buttons;
// Read XInput state (controller 0)
XINPUT_STATE state;
ZeroMemory(&state, sizeof(XINPUT_STATE));
DWORD result = XInputGetState(0, &state);
if (result == ERROR_SUCCESS)
{
// Controller is connected
XINPUT_GAMEPAD& pad = state.Gamepad;
// Store button state
m_buttons = pad.wButtons;
// Store analog sticks
m_lstickX = NormalizeAxis(pad.sThumbLX);
m_lstickY = NormalizeAxis(pad.sThumbLY);
m_rstickX = NormalizeAxis(pad.sThumbRX);
m_rstickY = NormalizeAxis(pad.sThumbRY);
// Store triggers
m_leftTrigger = NormalizeTrigger(pad.bLeftTrigger);
m_rightTrigger = NormalizeTrigger(pad.bRightTrigger);
}
else
{
// Controller is not connected, reset to defaults
m_buttons = 0;
m_lstickX = 0.0f;
m_lstickY = 0.0f;
m_rstickX = 0.0f;
m_rstickY = 0.0f;
m_leftTrigger = 0.0f;
m_rightTrigger = 0.0f;
}
// Map analog sticks
outInputState.moveAxisX = m_lstickX;
outInputState.moveAxisY = m_lstickY;
outInputState.lookAxisX = m_rstickX * 100.0f;
outInputState.lookAxisY = -m_rstickY * 100.0f; // Invert Y for camera
// Button mapping (Xbox controller)
bool btnA = (m_buttons & XINPUT_GAMEPAD_A) != 0;
bool btnB = (m_buttons & XINPUT_GAMEPAD_B) != 0;
bool btnX = (m_buttons & XINPUT_GAMEPAD_X) != 0;
bool btnY = (m_buttons & XINPUT_GAMEPAD_Y) != 0;
bool btnLB = (m_buttons & XINPUT_GAMEPAD_LEFT_SHOULDER) != 0;
bool btnRB = (m_buttons & XINPUT_GAMEPAD_RIGHT_SHOULDER) != 0;
bool btnLT = m_leftTrigger > 0.5f; // Digital threshold for analog trigger
bool btnRT = m_rightTrigger > 0.5f;
bool btnMenu = (m_buttons & XINPUT_GAMEPAD_START) != 0;
bool btnView = (m_buttons & XINPUT_GAMEPAD_BACK) != 0;
bool btnDpadUp = (m_buttons & XINPUT_GAMEPAD_DPAD_UP) != 0;
bool btnDpadDown = (m_buttons & XINPUT_GAMEPAD_DPAD_DOWN) != 0;
bool btnDpadLeft = (m_buttons & XINPUT_GAMEPAD_DPAD_LEFT) != 0;
bool btnDpadRight = (m_buttons & XINPUT_GAMEPAD_DPAD_RIGHT) != 0;
// Map to abstract actions
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)] = btnLT;
outInputState.actionsHeld[static_cast<int>(InputAction::Sprint)] = btnRT;
outInputState.actionsHeld[static_cast<int>(InputAction::CycleLeft)] = btnLB;
outInputState.actionsHeld[static_cast<int>(InputAction::CycleRight)] = btnRB;
outInputState.actionsHeld[static_cast<int>(InputAction::MenuOpen)] = btnMenu;
outInputState.actionsHeld[static_cast<int>(InputAction::MenuBack)] = btnB || btnView;
// D-pad for movement
outInputState.actionsHeld[static_cast<int>(InputAction::MoveForward)] = btnDpadUp;
outInputState.actionsHeld[static_cast<int>(InputAction::MoveBackward)] = btnDpadDown;
outInputState.actionsHeld[static_cast<int>(InputAction::MoveLeft)] = btnDpadLeft;
outInputState.actionsHeld[static_cast<int>(InputAction::MoveRight)] = btnDpadRight;
// Pressed this frame
uint16_t pressed = m_buttons & ~m_prevButtons;
outInputState.actions[static_cast<int>(InputAction::Action1)] = (pressed & XINPUT_GAMEPAD_A) != 0;
outInputState.actions[static_cast<int>(InputAction::Action2)] = (pressed & XINPUT_GAMEPAD_B) != 0;
outInputState.actions[static_cast<int>(InputAction::MenuOpen)] = (pressed & XINPUT_GAMEPAD_START) != 0;
outInputState.actions[static_cast<int>(InputAction::Jump)] = (pressed & XINPUT_GAMEPAD_A) != 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
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 XboxSeriesPlatform::NormalizeAxis(int16_t value) const
{
// XInput thumbsticks are -32768 to 32767
float normalized = static_cast<float>(value) / 32767.0f;
// Apply deadzone (XInput default is ~7849 / 32767 = 0.24)
const float deadzone = 0.24f;
if (std::abs(normalized) < deadzone)
return 0.0f;
// Rescale to account for deadzone
float sign = (normalized > 0.0f) ? 1.0f : -1.0f;
float absValue = std::abs(normalized);
return sign * ((absValue - deadzone) / (1.0f - deadzone));
}
float XboxSeriesPlatform::NormalizeTrigger(uint8_t value) const
{
// XInput triggers are 0-255
float normalized = static_cast<float>(value) / 255.0f;
// Apply trigger deadzone (XINPUT_GAMEPAD_TRIGGER_THRESHOLD = 30)
const float deadzone = 30.0f / 255.0f;
if (normalized < deadzone)
return 0.0f;
// Rescale to account for deadzone
return (normalized - deadzone) / (1.0f - deadzone);
}
void XboxSeriesPlatform::ResetInputFrameState(InputState& inputState)
{
inputState.ResetFrameStates();
}
IGraphicsContext* XboxSeriesPlatform::GetGraphicsContext()
{
return m_graphicsContext.get();
}
const char* XboxSeriesPlatform::GetUserDataPath() const
{
// UWP apps have local application data folder
return "LocalState/WillowVox";
}
const char* XboxSeriesPlatform::GetAssetsPath() const
{
// UWP assets are in the app package
return "Assets";
}
const char* XboxSeriesPlatform::GetPlatformName() const
{
return "Xbox Series X|S";
}
InputDeviceType XboxSeriesPlatform::GetPrimaryInputDevice() const
{
return InputDeviceType::XboxController;
}
bool XboxSeriesPlatform::HasFeature(const char* featureName) const
{
if (strcmp(featureName, "gamepad") == 0) return true;
if (strcmp(featureName, "filesystem") == 0) return true;
return false;
}
void XboxSeriesPlatform::SetVibration(int playerIndex, float lowFrequency, float highFrequency)
{
if (playerIndex < 0 || playerIndex >= XUSER_MAX_COUNT)
return;
// XInput vibration values are 0-65535
XINPUT_VIBRATION vibration;
vibration.wLeftMotorSpeed = static_cast<WORD>(lowFrequency * 65535.0f);
vibration.wRightMotorSpeed = static_cast<WORD>(highFrequency * 65535.0f);
XInputSetState(playerIndex, &vibration);
}
void XboxSeriesPlatform::StopVibration(int playerIndex)
{
SetVibration(playerIndex, 0.0f, 0.0f);
}
}
#endif // PLATFORM_XBOX
+20 -3
View File
@@ -8,7 +8,7 @@ namespace WillowVox
class XboxSeriesGraphicsContext;
/**
* Xbox Series X/S Platform (Dev Mode / GDK)
* Xbox Series X/S Platform (UWP with XInput)
*
* Controller: Xbox Series Controller (standard Xbox layout)
* - Left Stick: Movement
@@ -22,8 +22,8 @@ namespace WillowVox
* - Menu (≡): MenuOpen
* - View (::): MenuBack
*
* Graphics: DirectX 12 (via GDK)
* Input: GameInput API or XInput
* Graphics: OpenGL ES via ANGLE (translates to DirectX)
* Input: XInput API
*/
class XboxSeriesPlatform : public IPlatform
{
@@ -43,7 +43,24 @@ namespace WillowVox
InputDeviceType GetPrimaryInputDevice() const override;
bool HasFeature(const char* featureName) const override;
void SetVibration(int playerIndex, float lowFrequency, float highFrequency) override;
void StopVibration(int playerIndex) override;
private:
std::unique_ptr<XboxSeriesGraphicsContext> m_graphicsContext;
// XInput gamepad state
uint16_t m_buttons = 0;
uint16_t m_prevButtons = 0;
float m_lstickX = 0.0f;
float m_lstickY = 0.0f;
float m_rstickX = 0.0f;
float m_rstickY = 0.0f;
float m_leftTrigger = 0.0f;
float m_rightTrigger = 0.0f;
void UpdateGamepadInput(InputState& outInputState);
float NormalizeAxis(int16_t value) const;
float NormalizeTrigger(uint8_t value) const;
};
}
+243
View File
@@ -0,0 +1,243 @@
#include "iOSGraphicsContext.h"
#include <wv/Logger.h>
#ifdef PLATFORM_IOS
// iOS SDK includes
#import <OpenGLES/EAGL.h>
#import <OpenGLES/ES3/gl.h>
#import <OpenGLES/ES3/glext.h>
#import <QuartzCore/CAEAGLLayer.h>
#import <UIKit/UIKit.h>
#include <mach/mach_time.h>
namespace WillowVox
{
iOSGraphicsContext::iOSGraphicsContext()
{
// Get start time
mach_timebase_info_data_t timebase;
mach_timebase_info(&timebase);
uint64_t time = mach_absolute_time();
m_startTime = (double)time * (double)timebase.numer / (double)timebase.denom / 1e9;
}
iOSGraphicsContext::~iOSGraphicsContext()
{
Shutdown();
}
bool iOSGraphicsContext::Initialize(int width, int height, const char* title)
{
m_width = width;
m_height = height;
Logger::EngineLog("Initializing iOS Graphics (OpenGL ES 3.0)");
// Create EAGLContext with OpenGL ES 3.0
EAGLContext* context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
if (!context)
{
Logger::EngineError("Failed to create EAGLContext with ES 3.0, trying ES 2.0");
context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
if (!context)
{
Logger::EngineError("Failed to create EAGLContext");
return false;
}
}
m_eaglContext = (__bridge_retained void*)context;
// Make context current
if (![EAGLContext setCurrentContext:context])
{
Logger::EngineError("Failed to set current EAGLContext");
return false;
}
// Get the main UIWindow's layer (this is a simplification - in real app, you'd get this from your ViewController)
UIWindow* window = [[UIApplication sharedApplication] keyWindow];
if (!window)
{
Logger::EngineError("Failed to get UIWindow");
return false;
}
CAEAGLLayer* eaglLayer = (CAEAGLLayer*)window.layer;
eaglLayer.opaque = YES;
eaglLayer.drawableProperties = @{
kEAGLDrawablePropertyRetainedBacking: @NO,
kEAGLDrawablePropertyColorFormat: kEAGLColorFormatRGBA8
};
m_eaglLayer = (__bridge void*)eaglLayer;
// Create framebuffer
glGenFramebuffers(1, &m_framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);
// Create color renderbuffer
glGenRenderbuffers(1, &m_colorRenderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, m_colorRenderbuffer);
// Allocate storage for the renderbuffer from the layer
[context renderbufferStorage:GL_RENDERBUFFER fromDrawable:eaglLayer];
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, m_colorRenderbuffer);
// Get actual renderbuffer size
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &m_width);
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &m_height);
// Create depth renderbuffer
glGenRenderbuffers(1, &m_depthRenderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, m_depthRenderbuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, m_width, m_height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, m_depthRenderbuffer);
// Check framebuffer status
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (status != GL_FRAMEBUFFER_COMPLETE)
{
Logger::EngineError("Framebuffer is not complete: 0x%X", status);
return false;
}
// Enable OpenGL ES features
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
Logger::EngineLog("iOS Graphics initialized (OpenGL ES, %dx%d)", m_width, m_height);
m_initialized = true;
return true;
}
void iOSGraphicsContext::Shutdown()
{
if (m_initialized)
{
// Delete framebuffer objects
if (m_framebuffer)
{
glDeleteFramebuffers(1, &m_framebuffer);
m_framebuffer = 0;
}
if (m_colorRenderbuffer)
{
glDeleteRenderbuffers(1, &m_colorRenderbuffer);
m_colorRenderbuffer = 0;
}
if (m_depthRenderbuffer)
{
glDeleteRenderbuffers(1, &m_depthRenderbuffer);
m_depthRenderbuffer = 0;
}
// Release EAGL context
if (m_eaglContext)
{
EAGLContext* context = (__bridge_transfer EAGLContext*)m_eaglContext;
if ([EAGLContext currentContext] == context)
{
[EAGLContext setCurrentContext:nil];
}
context = nil;
m_eaglContext = nullptr;
}
m_initialized = false;
}
}
void iOSGraphicsContext::BeginFrame()
{
// Bind framebuffer for rendering
glBindFramebuffer(GL_FRAMEBUFFER, m_framebuffer);
glBindRenderbuffer(GL_RENDERBUFFER, m_colorRenderbuffer);
}
void iOSGraphicsContext::EndFrame()
{
// Nothing specific needed
}
void iOSGraphicsContext::SwapBuffers()
{
// Present the color renderbuffer
glBindRenderbuffer(GL_RENDERBUFFER, m_colorRenderbuffer);
EAGLContext* context = (__bridge EAGLContext*)m_eaglContext;
[context presentRenderbuffer:GL_RENDERBUFFER];
}
void iOSGraphicsContext::Clear(float r, float g, float b, float a)
{
glClearColor(r, g, b, a);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
void iOSGraphicsContext::ClearDepth()
{
glClear(GL_DEPTH_BUFFER_BIT);
}
bool iOSGraphicsContext::ShouldClose() const
{
return m_shouldClose;
}
void iOSGraphicsContext::SetShouldClose(bool shouldClose)
{
m_shouldClose = shouldClose;
}
void iOSGraphicsContext::GetFramebufferSize(int& width, int& height) const
{
width = m_width;
height = m_height;
}
void iOSGraphicsContext::GetWindowSize(int& width, int& height) const
{
width = m_width;
height = m_height;
}
void iOSGraphicsContext::SetVSync(bool enabled)
{
m_vsyncEnabled = enabled;
// VSync on iOS is typically controlled at the CADisplayLink level
// This would require integration with the view controller
}
bool iOSGraphicsContext::IsVSyncEnabled() const
{
return m_vsyncEnabled;
}
float iOSGraphicsContext::GetTime() const
{
mach_timebase_info_data_t timebase;
mach_timebase_info(&timebase);
uint64_t time = mach_absolute_time();
double currentTime = (double)time * (double)timebase.numer / (double)timebase.denom / 1e9;
return static_cast<float>(currentTime - m_startTime);
}
void iOSGraphicsContext::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 iOSGraphicsContext::SetViewport(int x, int y, int width, int height)
{
glViewport(x, y, width, height);
}
}
#endif // PLATFORM_IOS
+12 -3
View File
@@ -5,10 +5,10 @@
namespace WillowVox
{
/**
* iOS Graphics Context (Metal or OpenGL ES)
* iOS Graphics Context (OpenGL ES)
*
* iOS can use Metal (preferred) or OpenGL ES for compatibility.
* This is a template implementation.
* Uses OpenGL ES 3.0 with EAGLContext and CAEAGLLayer.
* Provides compatibility for cross-platform OpenGL code.
*/
class iOSGraphicsContext : public IGraphicsContext
{
@@ -36,8 +36,17 @@ namespace WillowVox
private:
bool m_initialized = false;
bool m_shouldClose = false;
bool m_vsyncEnabled = true;
int m_width = 0;
int m_height = 0;
float m_clearColor[4] = {0.1f, 0.1f, 0.1f, 1.0f};
float m_startTime = 0.0f;
// iOS-specific OpenGL ES handles (using void* to avoid including Objective-C headers)
void* m_eaglContext = nullptr; // EAGLContext*
void* m_eaglLayer = nullptr; // CAEAGLLayer*
unsigned int m_colorRenderbuffer = 0;
unsigned int m_depthRenderbuffer = 0;
unsigned int m_framebuffer = 0;
};
}
+325
View File
@@ -0,0 +1,325 @@
#include "iOSPlatform.h"
#include "iOSGraphicsContext.h"
#include <wv/Logger.h>
#ifdef PLATFORM_IOS
// iOS SDK includes
#include <UIKit/UIKit.h>
#include <cmath>
#include <cstring>
namespace WillowVox
{
iOSPlatform::iOSPlatform()
{
}
iOSPlatform::~iOSPlatform()
{
Shutdown();
}
bool iOSPlatform::Initialize()
{
Logger::EngineLog("Initializing iOS Platform");
// Graphics context will handle UIKit/OpenGL ES setup
m_graphicsContext = std::make_unique<iOSGraphicsContext>();
// Get screen dimensions from UIScreen
UIScreen* screen = [UIScreen mainScreen];
CGRect bounds = [screen bounds];
CGFloat scale = [screen scale];
m_screenWidth = static_cast<int>(bounds.size.width * scale);
m_screenHeight = static_cast<int>(bounds.size.height * scale);
Logger::EngineLog("Screen size: %dx%d (scale: %.1f)", m_screenWidth, m_screenHeight, scale);
// Setup virtual touch controls (similar to Android)
SetupVirtualControls();
Logger::EngineLog("iOS Platform initialized");
return true;
}
void iOSPlatform::Shutdown()
{
m_graphicsContext.reset();
Logger::EngineLog("iOS Platform shutdown");
}
void iOSPlatform::ProcessEvents()
{
// iOS event processing handled by UIKit run loop
// This is called from the main game loop
}
void iOSPlatform::PollInput(InputState& outInputState)
{
ProcessTouchInput(outInputState);
// Copy button states
std::memcpy(m_buttonPrevStates, m_buttonStates, sizeof(m_buttonStates));
outInputState.deviceType = InputDeviceType::Touchscreen;
}
void iOSPlatform::SetupVirtualControls()
{
// Virtual joystick (left side, bottom)
m_virtualControls.push_back({0.15f, 0.85f, 0.12f, InputAction::Count, true});
// Action buttons (right side, bottom)
m_virtualControls.push_back({0.85f, 0.85f, 0.06f, InputAction::Action1, false});
m_virtualControls.push_back({0.75f, 0.80f, 0.06f, InputAction::Action2, false});
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});
}
void iOSPlatform::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
// Note: Touch points are populated by UIKit touch event handlers
// This would be integrated with UIViewController's touchesBegan/Moved/Ended methods
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 iOSPlatform::ResetInputFrameState(InputState& inputState)
{
inputState.ResetFrameStates();
}
IGraphicsContext* iOSPlatform::GetGraphicsContext()
{
return m_graphicsContext.get();
}
const char* iOSPlatform::GetUserDataPath() const
{
// iOS uses app-specific directories
return "~/Documents/WillowVox";
}
const char* iOSPlatform::GetAssetsPath() const
{
// iOS bundles assets in the app bundle
return ""; // Use NSBundle for assets
}
const char* iOSPlatform::GetPlatformName() const
{
return "iOS";
}
InputDeviceType iOSPlatform::GetPrimaryInputDevice() const
{
return InputDeviceType::Touchscreen;
}
bool iOSPlatform::HasFeature(const char* featureName) const
{
if (strcmp(featureName, "touchscreen") == 0) return true;
if (strcmp(featureName, "filesystem") == 0) return true;
return false;
}
TouchPoint* iOSPlatform::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* iOSPlatform::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 iOSPlatform::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 iOSPlatform::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);
}
}
#endif // PLATFORM_IOS
+46 -2
View File
@@ -2,6 +2,7 @@
#include <wv/platform/IPlatform.h>
#include <memory>
#include <vector>
namespace WillowVox
{
@@ -38,8 +39,51 @@ namespace WillowVox
bool HasFeature(const char* featureName) const override;
private:
struct TouchPoint
{
int32_t id;
float x, y;
float startX, startY;
bool active;
};
std::unique_ptr<iOSGraphicsContext> m_graphicsContext;
// Touch input tracking (similar to Android)
// Implementation details omitted for brevity
// 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;
};
}