2026-02-02 12:24:50 -08:00
|
|
|
#include "rendering/camera_controller.hpp"
|
|
|
|
|
#include "rendering/terrain_manager.hpp"
|
|
|
|
|
#include "rendering/wmo_renderer.hpp"
|
2026-02-02 23:03:45 -08:00
|
|
|
#include "rendering/m2_renderer.hpp"
|
2026-02-02 12:24:50 -08:00
|
|
|
#include "rendering/water_renderer.hpp"
|
|
|
|
|
#include "game/opcodes.hpp"
|
|
|
|
|
#include "core/logger.hpp"
|
|
|
|
|
#include <glm/glm.hpp>
|
|
|
|
|
#include <imgui.h>
|
|
|
|
|
|
|
|
|
|
namespace wowee {
|
|
|
|
|
namespace rendering {
|
|
|
|
|
|
|
|
|
|
CameraController::CameraController(Camera* cam) : camera(cam) {
|
|
|
|
|
yaw = defaultYaw;
|
|
|
|
|
pitch = defaultPitch;
|
|
|
|
|
reset();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CameraController::update(float deltaTime) {
|
|
|
|
|
if (!enabled || !camera) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto& input = core::Input::getInstance();
|
|
|
|
|
|
|
|
|
|
// Don't process keyboard input when UI (e.g. chat box) has focus
|
|
|
|
|
bool uiWantsKeyboard = ImGui::GetIO().WantCaptureKeyboard;
|
|
|
|
|
|
|
|
|
|
// Determine current key states
|
|
|
|
|
bool nowForward = !uiWantsKeyboard && !sitting && input.isKeyPressed(SDL_SCANCODE_W);
|
|
|
|
|
bool nowBackward = !uiWantsKeyboard && !sitting && input.isKeyPressed(SDL_SCANCODE_S);
|
|
|
|
|
bool nowStrafeLeft = !uiWantsKeyboard && !sitting && input.isKeyPressed(SDL_SCANCODE_A);
|
|
|
|
|
bool nowStrafeRight = !uiWantsKeyboard && !sitting && input.isKeyPressed(SDL_SCANCODE_D);
|
|
|
|
|
bool nowJump = !uiWantsKeyboard && !sitting && input.isKeyPressed(SDL_SCANCODE_SPACE);
|
|
|
|
|
|
|
|
|
|
// Select physics constants based on mode
|
|
|
|
|
float gravity = useWoWSpeed ? WOW_GRAVITY : GRAVITY;
|
|
|
|
|
float jumpVel = useWoWSpeed ? WOW_JUMP_VELOCITY : JUMP_VELOCITY;
|
|
|
|
|
|
|
|
|
|
// Calculate movement speed based on direction and modifiers
|
|
|
|
|
float speed;
|
|
|
|
|
if (useWoWSpeed) {
|
2026-02-02 23:10:19 -08:00
|
|
|
// Movement speeds (Shift = sprint, Ctrl = walk)
|
2026-02-02 12:24:50 -08:00
|
|
|
if (nowBackward && !nowForward) {
|
|
|
|
|
speed = WOW_BACK_SPEED;
|
|
|
|
|
} else if (!uiWantsKeyboard && (input.isKeyPressed(SDL_SCANCODE_LSHIFT) || input.isKeyPressed(SDL_SCANCODE_RSHIFT))) {
|
2026-02-02 23:10:19 -08:00
|
|
|
speed = WOW_SPRINT_SPEED; // Shift = sprint (faster)
|
|
|
|
|
} else if (!uiWantsKeyboard && (input.isKeyPressed(SDL_SCANCODE_LCTRL) || input.isKeyPressed(SDL_SCANCODE_RCTRL))) {
|
|
|
|
|
speed = WOW_WALK_SPEED; // Ctrl = walk (slower)
|
2026-02-02 12:24:50 -08:00
|
|
|
} else {
|
2026-02-02 23:10:19 -08:00
|
|
|
speed = WOW_RUN_SPEED; // Normal run
|
2026-02-02 12:24:50 -08:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Exploration mode (original behavior)
|
|
|
|
|
speed = movementSpeed;
|
|
|
|
|
if (!uiWantsKeyboard && (input.isKeyPressed(SDL_SCANCODE_LSHIFT) || input.isKeyPressed(SDL_SCANCODE_RSHIFT))) {
|
|
|
|
|
speed *= sprintMultiplier;
|
|
|
|
|
}
|
|
|
|
|
if (!uiWantsKeyboard && (input.isKeyPressed(SDL_SCANCODE_LCTRL) || input.isKeyPressed(SDL_SCANCODE_RCTRL))) {
|
|
|
|
|
speed *= slowMultiplier;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get camera axes — project forward onto XY plane for walking
|
|
|
|
|
glm::vec3 forward3D = camera->getForward();
|
|
|
|
|
glm::vec3 forward = glm::normalize(glm::vec3(forward3D.x, forward3D.y, 0.0f));
|
|
|
|
|
glm::vec3 right = camera->getRight();
|
|
|
|
|
right.z = 0.0f;
|
|
|
|
|
if (glm::length(right) > 0.001f) {
|
|
|
|
|
right = glm::normalize(right);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-02 23:10:19 -08:00
|
|
|
// Toggle sit/crouch with X or C key (edge-triggered) — only when UI doesn't want keyboard
|
|
|
|
|
bool xDown = !uiWantsKeyboard && (input.isKeyPressed(SDL_SCANCODE_X) || input.isKeyPressed(SDL_SCANCODE_C));
|
2026-02-02 12:24:50 -08:00
|
|
|
if (xDown && !xKeyWasDown) {
|
|
|
|
|
sitting = !sitting;
|
|
|
|
|
}
|
|
|
|
|
xKeyWasDown = xDown;
|
|
|
|
|
|
2026-02-02 23:10:19 -08:00
|
|
|
// Update eye height based on crouch state (smooth transition)
|
|
|
|
|
float targetEyeHeight = sitting ? CROUCH_EYE_HEIGHT : STAND_EYE_HEIGHT;
|
|
|
|
|
float heightLerpSpeed = 10.0f * deltaTime;
|
|
|
|
|
eyeHeight = eyeHeight + (targetEyeHeight - eyeHeight) * std::min(1.0f, heightLerpSpeed);
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
// Calculate horizontal movement vector
|
|
|
|
|
glm::vec3 movement(0.0f);
|
|
|
|
|
|
|
|
|
|
if (nowForward) movement += forward;
|
|
|
|
|
if (nowBackward) movement -= forward;
|
|
|
|
|
if (nowStrafeLeft) movement -= right;
|
|
|
|
|
if (nowStrafeRight) movement += right;
|
|
|
|
|
|
2026-02-02 23:10:19 -08:00
|
|
|
// Stand up if jumping while crouched
|
|
|
|
|
if (!uiWantsKeyboard && sitting && input.isKeyPressed(SDL_SCANCODE_SPACE)) {
|
2026-02-02 12:24:50 -08:00
|
|
|
sitting = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Third-person orbit camera mode
|
|
|
|
|
if (thirdPerson && followTarget) {
|
|
|
|
|
// Move the follow target (character position) instead of the camera
|
|
|
|
|
glm::vec3 targetPos = *followTarget;
|
|
|
|
|
|
|
|
|
|
// Check for water at current position
|
|
|
|
|
std::optional<float> waterH;
|
|
|
|
|
if (waterRenderer) {
|
|
|
|
|
waterH = waterRenderer->getWaterHeightAt(targetPos.x, targetPos.y);
|
|
|
|
|
}
|
|
|
|
|
bool inWater = waterH && targetPos.z < *waterH;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (inWater) {
|
|
|
|
|
swimming = true;
|
|
|
|
|
// Reduce horizontal speed while swimming
|
|
|
|
|
float swimSpeed = speed * SWIM_SPEED_FACTOR;
|
|
|
|
|
|
|
|
|
|
if (glm::length(movement) > 0.001f) {
|
|
|
|
|
movement = glm::normalize(movement);
|
|
|
|
|
targetPos += movement * swimSpeed * deltaTime;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Spacebar = swim up (continuous, not a jump)
|
|
|
|
|
if (nowJump) {
|
|
|
|
|
verticalVelocity = SWIM_BUOYANCY;
|
|
|
|
|
} else {
|
|
|
|
|
// Gentle sink when not pressing space
|
|
|
|
|
verticalVelocity += SWIM_GRAVITY * deltaTime;
|
|
|
|
|
if (verticalVelocity < SWIM_SINK_SPEED) {
|
|
|
|
|
verticalVelocity = SWIM_SINK_SPEED;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
targetPos.z += verticalVelocity * deltaTime;
|
|
|
|
|
|
|
|
|
|
// Don't rise above water surface
|
|
|
|
|
if (waterH && targetPos.z > *waterH - WATER_SURFACE_OFFSET) {
|
|
|
|
|
targetPos.z = *waterH - WATER_SURFACE_OFFSET;
|
|
|
|
|
if (verticalVelocity > 0.0f) verticalVelocity = 0.0f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
grounded = false;
|
|
|
|
|
} else {
|
|
|
|
|
swimming = false;
|
|
|
|
|
|
|
|
|
|
if (glm::length(movement) > 0.001f) {
|
|
|
|
|
movement = glm::normalize(movement);
|
|
|
|
|
targetPos += movement * speed * deltaTime;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Jump
|
|
|
|
|
if (nowJump && grounded) {
|
|
|
|
|
verticalVelocity = jumpVel;
|
|
|
|
|
grounded = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Apply gravity
|
|
|
|
|
verticalVelocity += gravity * deltaTime;
|
|
|
|
|
targetPos.z += verticalVelocity * deltaTime;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-02 23:03:45 -08:00
|
|
|
// Wall collision for character (WMO buildings)
|
2026-02-02 12:24:50 -08:00
|
|
|
if (wmoRenderer) {
|
|
|
|
|
glm::vec3 feetPos = targetPos;
|
|
|
|
|
glm::vec3 oldFeetPos = *followTarget;
|
|
|
|
|
glm::vec3 adjusted;
|
|
|
|
|
if (wmoRenderer->checkWallCollision(oldFeetPos, feetPos, adjusted)) {
|
|
|
|
|
targetPos.x = adjusted.x;
|
|
|
|
|
targetPos.y = adjusted.y;
|
|
|
|
|
targetPos.z = adjusted.z;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-02 23:03:45 -08:00
|
|
|
// Collision with M2 doodads (fences, boxes, etc.)
|
|
|
|
|
if (m2Renderer) {
|
|
|
|
|
glm::vec3 adjusted;
|
|
|
|
|
if (m2Renderer->checkCollision(*followTarget, targetPos, adjusted)) {
|
|
|
|
|
targetPos.x = adjusted.x;
|
|
|
|
|
targetPos.y = adjusted.y;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
// Ground the character to terrain or WMO floor
|
|
|
|
|
{
|
|
|
|
|
std::optional<float> terrainH;
|
|
|
|
|
std::optional<float> wmoH;
|
|
|
|
|
|
|
|
|
|
if (terrainManager) {
|
|
|
|
|
terrainH = terrainManager->getHeightAt(targetPos.x, targetPos.y);
|
|
|
|
|
}
|
|
|
|
|
if (wmoRenderer) {
|
|
|
|
|
wmoH = wmoRenderer->getFloorHeight(targetPos.x, targetPos.y, targetPos.z + eyeHeight);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::optional<float> groundH;
|
|
|
|
|
if (terrainH && wmoH) {
|
|
|
|
|
groundH = std::max(*terrainH, *wmoH);
|
|
|
|
|
} else if (terrainH) {
|
|
|
|
|
groundH = terrainH;
|
|
|
|
|
} else if (wmoH) {
|
|
|
|
|
groundH = wmoH;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (groundH) {
|
|
|
|
|
lastGroundZ = *groundH;
|
|
|
|
|
if (targetPos.z <= *groundH) {
|
|
|
|
|
targetPos.z = *groundH;
|
|
|
|
|
verticalVelocity = 0.0f;
|
|
|
|
|
grounded = true;
|
|
|
|
|
swimming = false; // Touching ground = wading, not swimming
|
|
|
|
|
} else if (!swimming) {
|
|
|
|
|
grounded = false;
|
|
|
|
|
}
|
|
|
|
|
} else if (!swimming) {
|
|
|
|
|
// No terrain found — hold at last known ground
|
|
|
|
|
targetPos.z = lastGroundZ;
|
|
|
|
|
verticalVelocity = 0.0f;
|
|
|
|
|
grounded = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update follow target position
|
|
|
|
|
*followTarget = targetPos;
|
|
|
|
|
|
|
|
|
|
// Compute camera position orbiting behind the character
|
|
|
|
|
glm::vec3 lookAtPoint = targetPos + glm::vec3(0.0f, 0.0f, eyeHeight);
|
|
|
|
|
glm::vec3 camPos = lookAtPoint - forward3D * orbitDistance;
|
|
|
|
|
|
|
|
|
|
// Clamp camera above terrain/WMO floor
|
|
|
|
|
{
|
|
|
|
|
float minCamZ = camPos.z;
|
|
|
|
|
if (terrainManager) {
|
|
|
|
|
auto h = terrainManager->getHeightAt(camPos.x, camPos.y);
|
|
|
|
|
if (h) minCamZ = *h + 1.0f; // 1 unit above ground
|
|
|
|
|
}
|
|
|
|
|
if (wmoRenderer) {
|
|
|
|
|
auto wh = wmoRenderer->getFloorHeight(camPos.x, camPos.y, camPos.z + eyeHeight);
|
|
|
|
|
if (wh && (*wh + 1.0f) > minCamZ) minCamZ = *wh + 1.0f;
|
|
|
|
|
}
|
|
|
|
|
if (camPos.z < minCamZ) {
|
|
|
|
|
camPos.z = minCamZ;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
camera->setPosition(camPos);
|
|
|
|
|
} else {
|
|
|
|
|
// Free-fly camera mode (original behavior)
|
|
|
|
|
glm::vec3 newPos = camera->getPosition();
|
|
|
|
|
float feetZ = newPos.z - eyeHeight;
|
|
|
|
|
|
|
|
|
|
// Check for water at feet position
|
|
|
|
|
std::optional<float> waterH;
|
|
|
|
|
if (waterRenderer) {
|
|
|
|
|
waterH = waterRenderer->getWaterHeightAt(newPos.x, newPos.y);
|
|
|
|
|
}
|
|
|
|
|
bool inWater = waterH && feetZ < *waterH;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (inWater) {
|
|
|
|
|
swimming = true;
|
|
|
|
|
float swimSpeed = speed * SWIM_SPEED_FACTOR;
|
|
|
|
|
|
|
|
|
|
if (glm::length(movement) > 0.001f) {
|
|
|
|
|
movement = glm::normalize(movement);
|
|
|
|
|
newPos += movement * swimSpeed * deltaTime;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (nowJump) {
|
|
|
|
|
verticalVelocity = SWIM_BUOYANCY;
|
|
|
|
|
} else {
|
|
|
|
|
verticalVelocity += SWIM_GRAVITY * deltaTime;
|
|
|
|
|
if (verticalVelocity < SWIM_SINK_SPEED) {
|
|
|
|
|
verticalVelocity = SWIM_SINK_SPEED;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
newPos.z += verticalVelocity * deltaTime;
|
|
|
|
|
|
|
|
|
|
// Don't rise above water surface (feet at water level)
|
|
|
|
|
if (waterH && (newPos.z - eyeHeight) > *waterH - WATER_SURFACE_OFFSET) {
|
|
|
|
|
newPos.z = *waterH - WATER_SURFACE_OFFSET + eyeHeight;
|
|
|
|
|
if (verticalVelocity > 0.0f) verticalVelocity = 0.0f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
grounded = false;
|
|
|
|
|
} else {
|
|
|
|
|
swimming = false;
|
|
|
|
|
|
|
|
|
|
if (glm::length(movement) > 0.001f) {
|
|
|
|
|
movement = glm::normalize(movement);
|
|
|
|
|
newPos += movement * speed * deltaTime;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Jump
|
|
|
|
|
if (nowJump && grounded) {
|
|
|
|
|
verticalVelocity = jumpVel;
|
|
|
|
|
grounded = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Apply gravity
|
|
|
|
|
verticalVelocity += gravity * deltaTime;
|
|
|
|
|
newPos.z += verticalVelocity * deltaTime;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Wall collision — push out of WMO walls before grounding
|
|
|
|
|
if (wmoRenderer) {
|
|
|
|
|
glm::vec3 feetPos = newPos - glm::vec3(0, 0, eyeHeight);
|
|
|
|
|
glm::vec3 oldFeetPos = camera->getPosition() - glm::vec3(0, 0, eyeHeight);
|
|
|
|
|
glm::vec3 adjusted;
|
|
|
|
|
if (wmoRenderer->checkWallCollision(oldFeetPos, feetPos, adjusted)) {
|
|
|
|
|
newPos.x = adjusted.x;
|
|
|
|
|
newPos.y = adjusted.y;
|
|
|
|
|
newPos.z = adjusted.z + eyeHeight;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ground to terrain or WMO floor
|
|
|
|
|
{
|
|
|
|
|
std::optional<float> terrainH;
|
|
|
|
|
std::optional<float> wmoH;
|
|
|
|
|
|
|
|
|
|
if (terrainManager) {
|
|
|
|
|
terrainH = terrainManager->getHeightAt(newPos.x, newPos.y);
|
|
|
|
|
}
|
|
|
|
|
if (wmoRenderer) {
|
|
|
|
|
wmoH = wmoRenderer->getFloorHeight(newPos.x, newPos.y, newPos.z);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::optional<float> groundH;
|
|
|
|
|
if (terrainH && wmoH) {
|
|
|
|
|
groundH = std::max(*terrainH, *wmoH);
|
|
|
|
|
} else if (terrainH) {
|
|
|
|
|
groundH = terrainH;
|
|
|
|
|
} else if (wmoH) {
|
|
|
|
|
groundH = wmoH;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (groundH) {
|
|
|
|
|
lastGroundZ = *groundH;
|
|
|
|
|
float groundZ = *groundH + eyeHeight;
|
|
|
|
|
if (newPos.z <= groundZ) {
|
|
|
|
|
newPos.z = groundZ;
|
|
|
|
|
verticalVelocity = 0.0f;
|
|
|
|
|
grounded = true;
|
|
|
|
|
swimming = false; // Touching ground = wading
|
|
|
|
|
} else if (!swimming) {
|
|
|
|
|
grounded = false;
|
|
|
|
|
}
|
|
|
|
|
} else if (!swimming) {
|
|
|
|
|
float groundZ = lastGroundZ + eyeHeight;
|
|
|
|
|
newPos.z = groundZ;
|
|
|
|
|
verticalVelocity = 0.0f;
|
|
|
|
|
grounded = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
camera->setPosition(newPos);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Edge-detection: send movement opcodes on state transitions ---
|
|
|
|
|
if (movementCallback) {
|
|
|
|
|
// Forward/backward
|
|
|
|
|
if (nowForward && !wasMovingForward) {
|
|
|
|
|
movementCallback(static_cast<uint32_t>(game::Opcode::CMSG_MOVE_START_FORWARD));
|
|
|
|
|
}
|
|
|
|
|
if (nowBackward && !wasMovingBackward) {
|
|
|
|
|
movementCallback(static_cast<uint32_t>(game::Opcode::CMSG_MOVE_START_BACKWARD));
|
|
|
|
|
}
|
|
|
|
|
if ((!nowForward && wasMovingForward) || (!nowBackward && wasMovingBackward)) {
|
|
|
|
|
if (!nowForward && !nowBackward) {
|
|
|
|
|
movementCallback(static_cast<uint32_t>(game::Opcode::CMSG_MOVE_STOP));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Strafing
|
|
|
|
|
if (nowStrafeLeft && !wasStrafingLeft) {
|
|
|
|
|
movementCallback(static_cast<uint32_t>(game::Opcode::CMSG_MOVE_START_STRAFE_LEFT));
|
|
|
|
|
}
|
|
|
|
|
if (nowStrafeRight && !wasStrafingRight) {
|
|
|
|
|
movementCallback(static_cast<uint32_t>(game::Opcode::CMSG_MOVE_START_STRAFE_RIGHT));
|
|
|
|
|
}
|
|
|
|
|
if ((!nowStrafeLeft && wasStrafingLeft) || (!nowStrafeRight && wasStrafingRight)) {
|
|
|
|
|
if (!nowStrafeLeft && !nowStrafeRight) {
|
|
|
|
|
movementCallback(static_cast<uint32_t>(game::Opcode::CMSG_MOVE_STOP_STRAFE));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Jump
|
|
|
|
|
if (nowJump && !wasJumping && grounded) {
|
|
|
|
|
movementCallback(static_cast<uint32_t>(game::Opcode::CMSG_MOVE_JUMP));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fall landing
|
|
|
|
|
if (wasFalling && grounded) {
|
|
|
|
|
movementCallback(static_cast<uint32_t>(game::Opcode::CMSG_MOVE_FALL_LAND));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Swimming state transitions
|
|
|
|
|
if (movementCallback) {
|
|
|
|
|
if (swimming && !wasSwimming) {
|
|
|
|
|
movementCallback(static_cast<uint32_t>(game::Opcode::CMSG_MOVE_START_SWIM));
|
|
|
|
|
} else if (!swimming && wasSwimming) {
|
|
|
|
|
movementCallback(static_cast<uint32_t>(game::Opcode::CMSG_MOVE_STOP_SWIM));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update previous-frame state
|
|
|
|
|
wasSwimming = swimming;
|
|
|
|
|
wasMovingForward = nowForward;
|
|
|
|
|
wasMovingBackward = nowBackward;
|
|
|
|
|
wasStrafingLeft = nowStrafeLeft;
|
|
|
|
|
wasStrafingRight = nowStrafeRight;
|
|
|
|
|
wasJumping = nowJump;
|
|
|
|
|
wasFalling = !grounded && verticalVelocity <= 0.0f;
|
|
|
|
|
|
|
|
|
|
// Reset camera (R key)
|
|
|
|
|
if (!uiWantsKeyboard && input.isKeyPressed(SDL_SCANCODE_R)) {
|
|
|
|
|
reset();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CameraController::processMouseMotion(const SDL_MouseMotionEvent& event) {
|
|
|
|
|
if (!enabled || !camera) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!mouseButtonDown) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Directly update stored yaw/pitch (no lossy forward-vector derivation)
|
|
|
|
|
yaw -= event.xrel * mouseSensitivity;
|
|
|
|
|
pitch += event.yrel * mouseSensitivity;
|
|
|
|
|
|
|
|
|
|
pitch = glm::clamp(pitch, -89.0f, 89.0f);
|
|
|
|
|
|
|
|
|
|
camera->setRotation(yaw, pitch);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CameraController::processMouseButton(const SDL_MouseButtonEvent& event) {
|
|
|
|
|
if (!enabled) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (event.button == SDL_BUTTON_LEFT) {
|
|
|
|
|
leftMouseDown = (event.state == SDL_PRESSED);
|
|
|
|
|
}
|
|
|
|
|
if (event.button == SDL_BUTTON_RIGHT) {
|
|
|
|
|
rightMouseDown = (event.state == SDL_PRESSED);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool anyDown = leftMouseDown || rightMouseDown;
|
|
|
|
|
if (anyDown && !mouseButtonDown) {
|
|
|
|
|
SDL_SetRelativeMouseMode(SDL_TRUE);
|
|
|
|
|
} else if (!anyDown && mouseButtonDown) {
|
|
|
|
|
SDL_SetRelativeMouseMode(SDL_FALSE);
|
|
|
|
|
}
|
|
|
|
|
mouseButtonDown = anyDown;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CameraController::reset() {
|
|
|
|
|
if (!camera) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
yaw = defaultYaw;
|
|
|
|
|
pitch = defaultPitch;
|
|
|
|
|
verticalVelocity = 0.0f;
|
|
|
|
|
grounded = false;
|
|
|
|
|
|
|
|
|
|
glm::vec3 spawnPos = defaultPosition;
|
|
|
|
|
|
|
|
|
|
// Snap spawn to terrain or WMO surface
|
|
|
|
|
std::optional<float> h;
|
|
|
|
|
if (terrainManager) {
|
|
|
|
|
h = terrainManager->getHeightAt(spawnPos.x, spawnPos.y);
|
|
|
|
|
}
|
|
|
|
|
if (wmoRenderer) {
|
|
|
|
|
auto wh = wmoRenderer->getFloorHeight(spawnPos.x, spawnPos.y, spawnPos.z);
|
|
|
|
|
if (wh && (!h || *wh > *h)) {
|
|
|
|
|
h = wh;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (h) {
|
|
|
|
|
lastGroundZ = *h;
|
|
|
|
|
spawnPos.z = *h + eyeHeight;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
camera->setPosition(spawnPos);
|
|
|
|
|
camera->setRotation(yaw, pitch);
|
|
|
|
|
|
|
|
|
|
LOG_INFO("Camera reset to default position");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CameraController::processMouseWheel(float delta) {
|
|
|
|
|
orbitDistance -= delta * zoomSpeed;
|
|
|
|
|
orbitDistance = glm::clamp(orbitDistance, minOrbitDistance, maxOrbitDistance);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void CameraController::setFollowTarget(glm::vec3* target) {
|
|
|
|
|
followTarget = target;
|
|
|
|
|
if (target) {
|
|
|
|
|
thirdPerson = true;
|
|
|
|
|
LOG_INFO("Third-person camera enabled");
|
|
|
|
|
} else {
|
|
|
|
|
thirdPerson = false;
|
|
|
|
|
LOG_INFO("Free-fly camera enabled");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CameraController::isMoving() const {
|
|
|
|
|
if (!enabled || !camera) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ImGui::GetIO().WantCaptureKeyboard) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto& input = core::Input::getInstance();
|
|
|
|
|
|
|
|
|
|
return input.isKeyPressed(SDL_SCANCODE_W) ||
|
|
|
|
|
input.isKeyPressed(SDL_SCANCODE_S) ||
|
|
|
|
|
input.isKeyPressed(SDL_SCANCODE_A) ||
|
|
|
|
|
input.isKeyPressed(SDL_SCANCODE_D);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CameraController::isSprinting() const {
|
|
|
|
|
if (!enabled || !camera) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (ImGui::GetIO().WantCaptureKeyboard) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
auto& input = core::Input::getInstance();
|
|
|
|
|
return isMoving() && (input.isKeyPressed(SDL_SCANCODE_LSHIFT) || input.isKeyPressed(SDL_SCANCODE_RSHIFT));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace rendering
|
|
|
|
|
} // namespace wowee
|