mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-24 08:00:14 +00:00
Initial commit: wowee native WoW 3.3.5a client
This commit is contained in:
commit
ce6cb8f38e
147 changed files with 32347 additions and 0 deletions
525
src/rendering/camera_controller.cpp
Normal file
525
src/rendering/camera_controller.cpp
Normal file
|
|
@ -0,0 +1,525 @@
|
|||
#include "rendering/camera_controller.hpp"
|
||||
#include "rendering/terrain_manager.hpp"
|
||||
#include "rendering/wmo_renderer.hpp"
|
||||
#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) {
|
||||
// WoW-correct speeds
|
||||
if (nowBackward && !nowForward) {
|
||||
speed = WOW_BACK_SPEED;
|
||||
} else if (!uiWantsKeyboard && (input.isKeyPressed(SDL_SCANCODE_LSHIFT) || input.isKeyPressed(SDL_SCANCODE_RSHIFT))) {
|
||||
speed = WOW_WALK_SPEED; // Shift = walk in WoW mode
|
||||
} else {
|
||||
speed = WOW_RUN_SPEED;
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
|
||||
// Toggle sit with X key (edge-triggered) — only when UI doesn't want keyboard
|
||||
bool xDown = !uiWantsKeyboard && input.isKeyPressed(SDL_SCANCODE_X);
|
||||
if (xDown && !xKeyWasDown) {
|
||||
sitting = !sitting;
|
||||
}
|
||||
xKeyWasDown = xDown;
|
||||
|
||||
// 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;
|
||||
|
||||
// Stand up if any movement key is pressed while sitting
|
||||
if (!uiWantsKeyboard && sitting && (input.isKeyPressed(SDL_SCANCODE_W) || input.isKeyPressed(SDL_SCANCODE_S) ||
|
||||
input.isKeyPressed(SDL_SCANCODE_A) || input.isKeyPressed(SDL_SCANCODE_D) ||
|
||||
input.isKeyPressed(SDL_SCANCODE_SPACE))) {
|
||||
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;
|
||||
}
|
||||
|
||||
// Wall collision for character
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue