Kelsidavis-WoWee/include/rendering/camera_controller.hpp

428 lines
20 KiB
C++
Raw Normal View History

#pragma once
#include "rendering/camera.hpp"
#include "core/input.hpp"
#include <SDL2/SDL.h>
#include <algorithm>
#include <functional>
#include <optional>
namespace wowee {
namespace rendering {
class TerrainManager;
class WMORenderer;
class M2Renderer;
class WaterRenderer;
class CameraController {
public:
CameraController(Camera* camera);
void update(float deltaTime);
void processMouseMotion(const SDL_MouseMotionEvent& event);
void processMouseButton(const SDL_MouseButtonEvent& event);
void setMovementSpeed(float speed) { movementSpeed = speed; }
void setMouseSensitivity(float sensitivity) { mouseSensitivity = sensitivity; }
float getMouseSensitivity() const { return mouseSensitivity; }
void setInvertMouse(bool invert) { invertMouse = invert; }
bool isInvertMouse() const { return invertMouse; }
void setExtendedZoom(bool extended) { extendedZoom_ = extended; }
bool isExtendedZoom() const { return extendedZoom_; }
void setEnabled(bool enabled) { this->enabled = enabled; }
void setTerrainManager(TerrainManager* tm) { terrainManager = tm; }
void setWMORenderer(WMORenderer* wmo) { wmoRenderer = wmo; }
void setM2Renderer(M2Renderer* m2) { m2Renderer = m2; }
void setWaterRenderer(WaterRenderer* wr) { waterRenderer = wr; }
void processMouseWheel(float delta);
void setFollowTarget(glm::vec3* target);
void setDefaultSpawn(const glm::vec3& position, float yawDeg, float pitchDeg) {
defaultPosition = position;
defaultYaw = yawDeg;
defaultPitch = pitchDeg;
}
void reset();
void resetAngles();
void teleportTo(const glm::vec3& pos);
void setOnlineMode(bool online) { onlineMode = online; }
// Last known safe position (grounded, not falling)
bool hasLastSafePosition() const { return hasLastSafe_; }
const glm::vec3& getLastSafePosition() const { return lastSafePos_; }
float getContinuousFallTime() const { return continuousFallTime_; }
// Auto-unstuck callback (triggered when falling too long)
using AutoUnstuckCallback = std::function<void()>;
void setAutoUnstuckCallback(AutoUnstuckCallback cb) { autoUnstuckCallback_ = std::move(cb); }
void startIntroPan(float durationSec = 2.8f, float orbitDegrees = 140.0f);
2026-02-05 18:06:52 -08:00
bool isIntroActive() const { return introActive; }
bool isIdleOrbit() const { return idleOrbit_; }
float getMovementSpeed() const { return movementSpeed; }
const glm::vec3& getDefaultPosition() const { return defaultPosition; }
bool isMoving() const;
float getYaw() const { return yaw; }
float getPitch() const { return pitch; }
float getFacingYaw() const { return facingYaw; }
bool isThirdPerson() const { return thirdPerson; }
bool isFirstPersonView() const { return thirdPerson && (userTargetDistance <= MIN_DISTANCE + 0.15f); }
bool isGrounded() const { return grounded; }
bool isJumping() const { return !grounded && verticalVelocity > 0.0f; }
bool isFalling() const { return !grounded && verticalVelocity <= 0.0f; }
bool isJumpKeyPressed() const { return jumpBufferTimer > 0.0f; }
bool isSprinting() const;
bool isMovingForward() const { return moveForwardActive; }
bool isMovingBackward() const { return moveBackwardActive; }
bool isStrafingLeft() const { return strafeLeftActive; }
bool isStrafingRight() const { return strafeRightActive; }
bool isAutoRunning() const { return autoRunning; }
bool isRightMouseHeld() const { return rightMouseDown; }
bool isSitting() const { return sitting; }
bool isSwimming() const { return swimming; }
bool isInsideWMO() const { return cachedInsideWMO; }
feat(rendering): add HiZ occlusion culling & fix WMO interior shadows Implement GPU-driven Hierarchical-Z occlusion culling for M2 doodads using a depth pyramid built from the previous frame's depth buffer. The cull shader projects bounding spheres via prevViewProj (temporal reprojection) and samples the HiZ pyramid to reject hidden objects before the main render pass. Key implementation details: - Separate early compute submission (beginSingleTimeCommands + fence wait) eliminates 2-frame visibility staleness - Conservative safeguards prevent false culls: screen-edge guard, full VP row-vector AABB projection (Cauchy-Schwarz), 50% sphere inflation, depth bias, mip+1, min screen size threshold, camera motion dampening (auto-disable on fast rotations), and per-instance previouslyVisible flag tracking - Graceful fallback to frustum-only culling if HiZ init fails Fix dark WMO interiors by gating shadow map sampling on isInterior==0 in the WMO fragment shader. Interior groups (flag 0x2000) now rely solely on pre-baked MOCV vertex-color lighting + MOHD ambient color. Disable interiorDarken globally (was incorrectly darkening outdoor M2s when camera was inside a WMO). Use isInsideInteriorWMO() instead of isInsideWMO() for correct indoor detection. New files: - hiz_system.hpp/cpp: pyramid image management, compute pipeline, descriptors, mip-chain build dispatch, resize handling - hiz_build.comp.glsl: MAX-depth 2x2 reduction compute shader - m2_cull_hiz.comp.glsl: frustum + HiZ occlusion cull compute shader - test_indoor_shadows.cpp: 14 unit tests for shadow/interior contracts Modified: - CullUniformsGPU expanded 128->272 bytes (HiZ params, viewProj, prevViewProj) - Depth buffer images gain VK_IMAGE_USAGE_SAMPLED_BIT for HiZ reads - wmo.frag.glsl: interior branch before unlit, shadow skip for 0x2000 - Render graph: hiz_build + compute_cull disabled (run in early compute) - .gitignore: ignore compiled .spv binaries - MEGA_BONE_MAX_INSTANCES: 2048 -> 4096 Signed-off-by: Pavel Okhlopkov <pavel.okhlopkov@flant.com>
2026-04-06 16:40:59 +03:00
bool isInsideInteriorWMO() const { return cachedInsideInteriorWMO; }
void setGrounded(bool g) { grounded = g; }
void setSitting(bool s) { sitting = s; }
bool isOnTaxi() const { return externalFollow_; }
const glm::vec3* getFollowTarget() const { return followTarget; }
glm::vec3* getFollowTargetMutable() { return followTarget; }
// Movement callback for sending opcodes to server
using MovementCallback = std::function<void(uint32_t opcode)>;
void setMovementCallback(MovementCallback cb) { movementCallback = std::move(cb); }
// Callback invoked when the player stands up via local input (space/X/movement key
// while server-sitting), so the caller can send CMSG_STAND_STATE_CHANGE(0).
using StandUpCallback = std::function<void()>;
void setStandUpCallback(StandUpCallback cb) { standUpCallback_ = std::move(cb); }
feat(animation): decompose AnimationController into FSM-based architecture Replace the 2,200-line monolithic AnimationController (goto-driven, single class, untestable) with a composed FSM architecture per refactor.md. New subsystem (src/rendering/animation/ — 16 headers, 10 sources): - CharacterAnimator: FSM composer implementing ICharacterAnimator - LocomotionFSM: idle/walk/run/sprint/jump/swim/strafe - CombatFSM: melee/ranged/spell cast/stun/hit reaction/charge - ActivityFSM: emote/loot/sit-down/sitting/sit-up - MountFSM: idle/run/flight/taxi/fidget/rear-up (per-instance RNG) - AnimCapabilitySet + AnimCapabilityProbe: probe once at model load, eliminate per-frame hasAnimation() linear search - AnimationManager: registry of CharacterAnimator by GUID - EmoteRegistry: DBC-backed emote command → animId singleton - FootstepDriver, SfxStateDriver: extracted from AnimationController animation_ids.hpp/.cpp moved to animation/ subdirectory (452 named constants); all include paths updated. AnimationController retained as thin adapter (~400 LOC): collects FrameInput, delegates to CharacterAnimator, applies AnimOutput. Priority order: Mount > Stun > HitReaction > Spell > Charge > Melee/Ranged > CombatIdle > Emote > Loot > Sit > Locomotion. STAY_IN_STATE policy when all FSMs return valid=false. Bugs fixed: - Remove static mt19937 in mount fidget (shared state across all mounted units) — replaced with per-instance seeded RNG - Remove goto from mounted animation branch (skipped init) - Remove per-frame hasAnimation() calls (now one probe at load) - Fix VK_INDEX_TYPE_UINT16 → UINT32 in shadow pass Tests (4 new suites, all ASAN+UBSan clean): - test_locomotion_fsm: 167 assertions - test_combat_fsm: 125 cases - test_activity_fsm: 112 cases - test_anim_capability: 56 cases docs/ANIMATION_SYSTEM.md added (architecture reference).
2026-04-05 12:27:35 +03:00
// Callback invoked when the player sits down via local input (X key).
using SitDownCallback = std::function<void()>;
void setSitDownCallback(SitDownCallback cb) { sitDownCallback_ = std::move(cb); }
// Callback invoked when auto-follow is cancelled by user movement input.
using AutoFollowCancelCallback = std::function<void()>;
void setAutoFollowCancelCallback(AutoFollowCancelCallback cb) { autoFollowCancelCallback_ = std::move(cb); }
void setUseWoWSpeed(bool use) { useWoWSpeed = use; }
void setRunSpeedOverride(float speed) { runSpeedOverride_ = speed; }
void setWalkSpeedOverride(float speed) { walkSpeedOverride_ = speed; }
void setSwimSpeedOverride(float speed) { swimSpeedOverride_ = speed; }
void setSwimBackSpeedOverride(float speed) { swimBackSpeedOverride_ = speed; }
void setFlightSpeedOverride(float speed) { flightSpeedOverride_ = speed; }
void setFlightBackSpeedOverride(float speed) { flightBackSpeedOverride_ = speed; }
void setRunBackSpeedOverride(float speed) { runBackSpeedOverride_ = speed; }
// Server turn rate in rad/s (SMSG_FORCE_TURN_RATE_CHANGE); 0 = use WOW_TURN_SPEED default
void setTurnRateOverride(float rateRadS) { turnRateOverride_ = rateRadS; }
void setMovementRooted(bool rooted) { movementRooted_ = rooted; }
bool isMovementRooted() const { return movementRooted_; }
void setGravityDisabled(bool disabled) { gravityDisabled_ = disabled; }
void setFeatherFallActive(bool active) { featherFallActive_ = active; }
void setWaterWalkActive(bool active) { waterWalkActive_ = active; }
void setFlyingActive(bool active) { flyingActive_ = active; }
feat(animation): 452 named constants, 30-phase character animation state machine Add animation_ids.hpp/cpp with all 452 WoW animation ID constants (anim::STAND, anim::RUN, anim::FIRE_BOW, ... anim::FLY_BACKWARDS, etc.), nameFromId() O(1) lookup, and flyVariant() compact 218-element ground→FLY_* resolver. Expand AnimationController into a full state machine with 20+ named states: spell cast (directed→omni→cast fallback chain, instant one-shot release), hit reactions (WOUND/CRIT/DODGE/BLOCK/SHIELD_BLOCK), stun, wounded idle, stealth animation substitution, loot, fishing channel, sit/sleep/kneel down→loop→up transitions, sheathe/unsheathe combat enter/exit, ranged weapons (BOW/GUN/CROSSBOW/THROWN with reload states), game object OPEN/CLOSE/DESTROY, vehicle enter/exit, mount flight directionals (FLY_LEFT/RIGHT/UP/DOWN/BACKWARDS), emote state variants, off-hand/pierce/dual-wield alternation, NPC birth/spawn/drown/rise, sprint aura override, totem idle, NPC greeting/farewell. Add spell_defines.hpp with SpellEffect (~45 constants) and SpellMissInfo (12 constants) namespaces; replace all magic numbers in spell_handler.cpp. Add GAMEOBJECT_BYTES_1 to update field table (all 4 expansion JSONs) and wire GameObjectStateCallback. Add DBC cross-validation on world entry. Expand tools/_ANIM_NAMES from ~35 to 452 entries in m2_viewer.py and asset_pipeline_gui.py. Add tests/test_animation_ids.cpp. Bug fixes included: - Stand state 1 was animating READY_2H(27) — fixed to SITTING(97) - Spell casts ended freeze-frame — add one-shot release animation - NPC 2H swing probe chain missing ATTACK_2H_LOOSE (polearm/staff) - Chair sits (states 2/4/5/6) incorrectly played floor-sit transition - STOP(3) used for all spell casts — replaced with model-aware chain
2026-04-04 23:02:53 +03:00
bool isFlyingActive() const { return flyingActive_; }
bool isAscending() const { return wasAscending_; }
bool isDescending() const { return wasDescending_; }
void setHoverActive(bool active) { hoverActive_ = active; }
void setMounted(bool m) { mounted_ = m; }
2026-02-07 20:05:07 -08:00
void setMountHeightOffset(float offset) { mountHeightOffset_ = offset; }
void setExternalFollow(bool enabled) { externalFollow_ = enabled; }
void setExternalMoving(bool moving) { externalMoving_ = moving; }
void setFacingYaw(float yaw) { facingYaw = yaw; } // For taxi/scripted movement
void clearMovementInputs();
void suppressMovementFor(float seconds) { movementSuppressTimer_ = seconds; }
void suspendGravityFor(float seconds) { gravitySuspendTimer_ = seconds; }
// Auto-follow: walk toward a target position each frame (WoW /follow).
// The caller updates *targetPos every frame with the followed entity's render position.
// Stops within FOLLOW_STOP_DIST; cancels on manual WASD input.
void setAutoFollow(const glm::vec3* targetPos) { autoFollowTarget_ = targetPos; }
void cancelAutoFollow() { autoFollowTarget_ = nullptr; }
bool isAutoFollowing() const { return autoFollowTarget_ != nullptr; }
// Trigger mount jump (applies vertical velocity for physics hop)
void triggerMountJump();
// Apply server-driven knockback impulse.
// dir: render-space 2D direction unit vector (from vcos/vsin in packet)
// hspeed: horizontal speed magnitude (units/s)
// vspeed: raw packet vspeed field (server sends negative for upward launch)
void applyKnockBack(float vcos, float vsin, float hspeed, float vspeed);
// Trigger a camera shake effect (e.g. from SMSG_CAMERA_SHAKE).
// magnitude: peak positional offset in world units
// frequency: oscillation frequency in Hz
// duration: shake duration in seconds
void triggerShake(float magnitude, float frequency, float duration);
// For first-person player hiding
void setCharacterRenderer(class CharacterRenderer* cr, uint32_t playerId) {
characterRenderer = cr;
playerInstanceId = playerId;
}
private:
Camera* camera;
TerrainManager* terrainManager = nullptr;
WMORenderer* wmoRenderer = nullptr;
M2Renderer* m2Renderer = nullptr;
WaterRenderer* waterRenderer = nullptr;
CharacterRenderer* characterRenderer = nullptr;
uint32_t playerInstanceId = 0;
// Stored rotation (avoids lossy forward-vector round-trip)
float yaw = 180.0f;
float pitch = -30.0f;
float facingYaw = 180.0f; // Character-facing yaw (can differ from camera yaw)
// Movement settings
float movementSpeed = 50.0f;
float sprintMultiplier = 3.0f;
float slowMultiplier = 0.3f;
// Mouse settings
float mouseSensitivity = 0.2f;
bool invertMouse = true;
bool mouseButtonDown = false;
bool leftMouseDown = false;
bool rightMouseDown = false;
// Third-person orbit camera (WoW-style)
bool thirdPerson = false;
float userTargetDistance = 10.0f; // What the player wants (scroll wheel)
float currentDistance = 10.0f; // Smoothed actual distance
float collisionDistance = 10.0f; // Max allowed by collision
bool externalFollow_ = false;
static constexpr float MIN_DISTANCE = 0.5f; // Minimum zoom (first-person threshold)
static constexpr float MAX_DISTANCE_NORMAL = 22.0f; // Default max zoom out
static constexpr float MAX_DISTANCE_EXTENDED = 50.0f; // Extended max zoom out
static constexpr float MAX_DISTANCE_INTERIOR = 12.0f; // Max zoom inside WMOs
bool extendedZoom_ = false;
static constexpr float ZOOM_SMOOTH_SPEED = 15.0f; // How fast zoom eases
static constexpr float CAM_SMOOTH_SPEED_DEFAULT = 30.0f;
float camSmoothSpeed_ = CAM_SMOOTH_SPEED_DEFAULT; // User-configurable camera smoothing (higher = tighter)
public:
void setCameraSmoothSpeed(float speed) { camSmoothSpeed_ = std::clamp(speed, 5.0f, 100.0f); }
float getCameraSmoothSpeed() const { return camSmoothSpeed_; }
void setPivotHeight(float h) { pivotHeight_ = std::clamp(h, 0.0f, 3.0f); }
float getPivotHeight() const { return pivotHeight_; }
private:
static constexpr float PIVOT_HEIGHT_DEFAULT = 1.6f;
float pivotHeight_ = PIVOT_HEIGHT_DEFAULT; // User-configurable pivot height
static constexpr float CAM_SPHERE_RADIUS = 0.32f; // Keep camera farther from geometry to avoid clipping-through surfaces
static constexpr float CAM_EPSILON = 0.22f; // Extra wall offset to avoid near-plane clipping artifacts
static constexpr float COLLISION_FOCUS_RADIUS_THIRD_PERSON = 20.0f; // Reduced for performance
static constexpr float COLLISION_FOCUS_RADIUS_FREE_FLY = 20.0f;
static constexpr float MIN_PITCH = -88.0f; // Look almost straight down
static constexpr float MAX_PITCH = 88.0f; // Look almost straight up (WoW standard)
glm::vec3* followTarget = nullptr;
glm::vec3 smoothedCamPos = glm::vec3(0.0f); // For smooth camera movement
float smoothedCollisionDist_ = -1.0f; // Asymmetrically-smoothed WMO collision limit (-1 = uninitialised)
// Gravity / grounding
float verticalVelocity = 0.0f;
bool grounded = false;
static constexpr float STAND_EYE_HEIGHT = 1.2f; // Standing eye height
static constexpr float CROUCH_EYE_HEIGHT = 0.6f; // Crouching eye height
float eyeHeight = STAND_EYE_HEIGHT;
float lastGroundZ = 0.0f; // Last known ground height (fallback when no terrain)
static constexpr float GRAVITY = -30.0f;
static constexpr float JUMP_VELOCITY = 15.0f;
float jumpBufferTimer = 0.0f; // Time since space was pressed
float coyoteTimer = 0.0f; // Time since last grounded
static constexpr float JUMP_BUFFER_TIME = 0.15f; // 150ms input buffer
static constexpr float COYOTE_TIME = 0.10f; // 100ms grace after leaving ground
// Cached isInsideWMO result (throttled to avoid per-frame cost)
bool cachedInsideWMO = false;
bool cachedInsideInteriorWMO = false;
int insideStateCheckCounter_ = 0;
glm::vec3 lastInsideStateCheckPos_ = glm::vec3(0.0f);
int insideWMOCheckCounter = 0;
glm::vec3 lastInsideWMOCheckPos = glm::vec3(0.0f);
// Cached camera WMO floor query (skip if camera moved < 0.3 units)
glm::vec3 lastCamFloorQueryPos = glm::vec3(0.0f);
std::optional<float> cachedCamWmoFloor;
bool hasCachedCamFloor = false;
// Terrain-aware camera pivot lift cache (throttled for performance).
glm::vec3 lastPivotLiftQueryPos_ = glm::vec3(0.0f);
float lastPivotLiftDistance_ = 0.0f;
int pivotLiftQueryCounter_ = 0;
float cachedPivotLift_ = 0.0f;
static constexpr int PIVOT_LIFT_QUERY_INTERVAL = 3;
static constexpr float PIVOT_LIFT_POS_THRESHOLD = 0.5f;
static constexpr float PIVOT_LIFT_DIST_THRESHOLD = 0.5f;
// Cached floor height queries (update every 5 frames or 2 unit movement)
glm::vec3 lastFloorQueryPos = glm::vec3(0.0f);
std::optional<float> cachedFloorHeight;
int floorQueryFrameCounter = 0;
static constexpr float FLOOR_QUERY_DISTANCE_THRESHOLD = 2.0f; // Increased from 1.0
static constexpr int FLOOR_QUERY_FRAME_INTERVAL = 5; // Increased from 3
// Helper to get cached floor height (reduces expensive queries)
std::optional<float> getCachedFloorHeight(float x, float y, float z);
// Swimming
bool swimming = false;
bool wasSwimming = false;
static constexpr float SWIM_SPEED_FACTOR = 0.67f;
static constexpr float SWIM_GRAVITY = -5.0f;
static constexpr float SWIM_BUOYANCY = 8.0f;
static constexpr float SWIM_SINK_SPEED = -3.0f;
static constexpr float WATER_SURFACE_OFFSET = 0.9f;
// Movement input suppression (after teleport/portal, ignore held keys)
float movementSuppressTimer_ = 0.0f;
// Gravity suspension (after world entry, hold Z until ground detected)
float gravitySuspendTimer_ = 0.0f;
// State
bool enabled = true;
bool sitting = false;
bool xKeyWasDown = false;
bool rKeyWasDown = false;
bool runPace = false;
bool autoRunning = false;
bool tildeWasDown = false;
// Auto-follow target position (WoW /follow). Non-null when following.
const glm::vec3* autoFollowTarget_ = nullptr;
static constexpr float FOLLOW_STOP_DIST = 3.0f; // Stop within 3 units of target
static constexpr float FOLLOW_MAX_DIST = 40.0f; // Cancel if > 40 units away
// Movement state tracking (for sending opcodes on state change)
bool wasMovingForward = false;
bool wasMovingBackward = false;
bool wasStrafingLeft = false;
bool wasStrafingRight = false;
bool wasTurningLeft = false;
bool wasTurningRight = false;
bool wasJumping = false;
bool wasFalling = false;
bool wasAscending_ = false; // Space held while flyingActive_
bool wasDescending_ = false; // X held while flyingActive_
bool moveForwardActive = false;
bool moveBackwardActive = false;
bool strafeLeftActive = false;
bool strafeRightActive = false;
// Movement callback
MovementCallback movementCallback;
StandUpCallback standUpCallback_;
feat(animation): decompose AnimationController into FSM-based architecture Replace the 2,200-line monolithic AnimationController (goto-driven, single class, untestable) with a composed FSM architecture per refactor.md. New subsystem (src/rendering/animation/ — 16 headers, 10 sources): - CharacterAnimator: FSM composer implementing ICharacterAnimator - LocomotionFSM: idle/walk/run/sprint/jump/swim/strafe - CombatFSM: melee/ranged/spell cast/stun/hit reaction/charge - ActivityFSM: emote/loot/sit-down/sitting/sit-up - MountFSM: idle/run/flight/taxi/fidget/rear-up (per-instance RNG) - AnimCapabilitySet + AnimCapabilityProbe: probe once at model load, eliminate per-frame hasAnimation() linear search - AnimationManager: registry of CharacterAnimator by GUID - EmoteRegistry: DBC-backed emote command → animId singleton - FootstepDriver, SfxStateDriver: extracted from AnimationController animation_ids.hpp/.cpp moved to animation/ subdirectory (452 named constants); all include paths updated. AnimationController retained as thin adapter (~400 LOC): collects FrameInput, delegates to CharacterAnimator, applies AnimOutput. Priority order: Mount > Stun > HitReaction > Spell > Charge > Melee/Ranged > CombatIdle > Emote > Loot > Sit > Locomotion. STAY_IN_STATE policy when all FSMs return valid=false. Bugs fixed: - Remove static mt19937 in mount fidget (shared state across all mounted units) — replaced with per-instance seeded RNG - Remove goto from mounted animation branch (skipped init) - Remove per-frame hasAnimation() calls (now one probe at load) - Fix VK_INDEX_TYPE_UINT16 → UINT32 in shadow pass Tests (4 new suites, all ASAN+UBSan clean): - test_locomotion_fsm: 167 assertions - test_combat_fsm: 125 cases - test_activity_fsm: 112 cases - test_anim_capability: 56 cases docs/ANIMATION_SYSTEM.md added (architecture reference).
2026-04-05 12:27:35 +03:00
SitDownCallback sitDownCallback_;
AutoFollowCancelCallback autoFollowCancelCallback_;
// Movement speeds
bool useWoWSpeed = false;
static constexpr float WOW_RUN_SPEED = 7.0f; // Normal run (WotLK)
static constexpr float WOW_SPRINT_SPEED = 10.5f; // Optional fast mode (not default WoW behavior)
static constexpr float WOW_WALK_SPEED = 2.5f; // Walk
static constexpr float WOW_BACK_SPEED = 4.5f; // Backpedal
static constexpr float WOW_TURN_SPEED = 180.0f; // Keyboard turn deg/sec
static constexpr float WOW_GRAVITY = -19.29f;
static constexpr float WOW_JUMP_VELOCITY = 7.96f;
static constexpr float MOUNT_GRAVITY = -18.0f; // Snappy WoW-feel jump
static constexpr float MOUNT_JUMP_HEIGHT = 1.0f; // Desired jump height in meters
// Computed jump velocity using vz = sqrt(2 * g * h)
static inline float getMountJumpVelocity() {
return std::sqrt(2.0f * std::abs(MOUNT_GRAVITY) * MOUNT_JUMP_HEIGHT);
}
// Server-driven speed overrides (0 = use hardcoded default)
float runSpeedOverride_ = 0.0f;
float walkSpeedOverride_ = 0.0f;
float swimSpeedOverride_ = 0.0f;
float swimBackSpeedOverride_ = 0.0f;
float flightSpeedOverride_ = 0.0f;
float flightBackSpeedOverride_ = 0.0f;
float runBackSpeedOverride_ = 0.0f;
float turnRateOverride_ = 0.0f; // rad/s; 0 = WOW_TURN_SPEED default (π rad/s)
// Server-driven root state: when true, block all horizontal movement input.
bool movementRooted_ = false;
// Server-driven gravity disable (levitate/hover): skip gravity accumulation.
bool gravityDisabled_ = false;
// Server-driven feather fall: cap downward velocity to slow-fall terminal.
bool featherFallActive_ = false;
// Server-driven water walk: treat water surface as ground (don't swim).
bool waterWalkActive_ = false;
// Player-controlled flight (CAN_FLY + FLYING): 3D movement, no gravity.
bool flyingActive_ = false;
// Server-driven hover (HOVER flag): float at fixed height above ground.
bool hoverActive_ = false;
bool mounted_ = false;
2026-02-07 20:05:07 -08:00
float mountHeightOffset_ = 0.0f;
bool externalMoving_ = false;
// Online mode: trust server position, don't prefer outdoors over WMO floors
bool onlineMode = false;
// Default spawn position (Goldshire Inn)
glm::vec3 defaultPosition = glm::vec3(-9464.0f, 62.0f, 200.0f);
float defaultYaw = 0.0f;
float defaultPitch = -5.0f;
2026-02-05 18:06:52 -08:00
// Spawn intro camera pan
bool introActive = false;
float introTimer = 0.0f;
float introDuration = 0.0f;
float introStartYaw = 0.0f;
float introEndYaw = 0.0f;
2026-02-05 18:06:52 -08:00
float introOrbitDegrees = 0.0f;
float introStartPitch = -15.0f;
float introEndPitch = -5.0f;
float introStartDistance = 12.0f;
float introEndDistance = 10.0f;
// Idle camera: triggers intro pan after IDLE_TIMEOUT seconds of no input
float idleTimer_ = 0.0f;
bool idleOrbit_ = false; // true when current intro pan is an idle orbit (loops)
static constexpr float IDLE_TIMEOUT = 120.0f; // 2 minutes
// Last known safe position (saved periodically when grounded on real geometry)
bool hasLastSafe_ = false;
glm::vec3 lastSafePos_ = glm::vec3(0.0f);
float safePosSaveTimer_ = 0.0f;
bool hasRealGround_ = false; // True only when terrain/WMO/M2 floor is detected
static constexpr float SAFE_POS_SAVE_INTERVAL = 2.0f; // Save every 2 seconds
// No-ground timer: after grace period, let the player fall instead of hovering
float noGroundTimer_ = 0.0f;
static constexpr float NO_GROUND_GRACE = 0.5f; // 500ms grace for terrain streaming
// Continuous fall time (for auto-unstuck detection)
float continuousFallTime_ = 0.0f;
bool autoUnstuckFired_ = false;
AutoUnstuckCallback autoUnstuckCallback_;
static constexpr float AUTO_UNSTUCK_FALL_TIME = 5.0f; // 5 seconds of falling
// Collision query cache (skip expensive checks if position barely changed)
glm::vec3 lastCollisionCheckPos_ = glm::vec3(0.0f);
float cachedFloorHeight_ = 0.0f;
bool hasCachedFloor_ = false;
static constexpr float COLLISION_CACHE_DISTANCE = 0.15f; // Re-check every 15cm
// Server-driven knockback state.
// When the server sends SMSG_MOVE_KNOCK_BACK, we apply horizontal + vertical
// impulse here and let the normal physics loop (gravity, collision) resolve it.
bool knockbackActive_ = false;
glm::vec2 knockbackHorizVel_ = glm::vec2(0.0f); // render-space horizontal velocity (units/s)
// Horizontal velocity decays via WoW-like drag so the player doesn't slide forever.
static constexpr float KNOCKBACK_HORIZ_DRAG = 4.5f; // exponential decay rate (1/s)
// Camera shake state (SMSG_CAMERA_SHAKE)
float shakeElapsed_ = 0.0f;
float shakeDuration_ = 0.0f;
float shakeMagnitude_ = 0.0f;
float shakeFrequency_ = 0.0f;
};
} // namespace rendering
} // namespace wowee