2026-02-02 12:24:50 -08:00
|
|
|
#include "rendering/renderer.hpp"
|
|
|
|
|
#include "rendering/camera.hpp"
|
|
|
|
|
#include "rendering/camera_controller.hpp"
|
|
|
|
|
#include "rendering/terrain_renderer.hpp"
|
|
|
|
|
#include "rendering/terrain_manager.hpp"
|
|
|
|
|
#include "rendering/performance_hud.hpp"
|
|
|
|
|
#include "rendering/water_renderer.hpp"
|
|
|
|
|
#include "rendering/skybox.hpp"
|
|
|
|
|
#include "rendering/celestial.hpp"
|
|
|
|
|
#include "rendering/starfield.hpp"
|
|
|
|
|
#include "rendering/clouds.hpp"
|
|
|
|
|
#include "rendering/lens_flare.hpp"
|
|
|
|
|
#include "rendering/weather.hpp"
|
2026-03-13 09:52:23 -07:00
|
|
|
#include "rendering/lightning.hpp"
|
2026-02-10 13:48:50 -08:00
|
|
|
#include "rendering/lighting_manager.hpp"
|
2026-04-03 09:41:34 +03:00
|
|
|
#include "core/profiler.hpp"
|
2026-02-10 19:30:45 -08:00
|
|
|
#include "rendering/sky_system.hpp"
|
2026-02-02 12:24:50 -08:00
|
|
|
#include "rendering/swim_effects.hpp"
|
2026-02-09 01:24:17 -08:00
|
|
|
#include "rendering/mount_dust.hpp"
|
2026-02-19 21:13:13 -08:00
|
|
|
#include "rendering/charge_effect.hpp"
|
2026-02-19 20:36:25 -08:00
|
|
|
#include "rendering/levelup_effect.hpp"
|
2026-02-02 12:24:50 -08:00
|
|
|
#include "rendering/character_renderer.hpp"
|
2026-02-22 05:58:45 -08:00
|
|
|
#include "rendering/character_preview.hpp"
|
2026-02-02 12:24:50 -08:00
|
|
|
#include "rendering/wmo_renderer.hpp"
|
|
|
|
|
#include "rendering/m2_renderer.hpp"
|
|
|
|
|
#include "rendering/minimap.hpp"
|
2026-02-21 19:41:21 -08:00
|
|
|
#include "rendering/world_map.hpp"
|
2026-02-09 23:41:38 -08:00
|
|
|
#include "rendering/quest_marker_renderer.hpp"
|
2026-02-10 19:30:45 -08:00
|
|
|
#include "game/game_handler.hpp"
|
2026-02-05 14:01:26 -08:00
|
|
|
#include "pipeline/m2_loader.hpp"
|
|
|
|
|
#include <algorithm>
|
2026-02-02 12:24:50 -08:00
|
|
|
#include "pipeline/asset_manager.hpp"
|
2026-02-07 20:02:14 -08:00
|
|
|
#include "pipeline/dbc_loader.hpp"
|
2026-02-12 22:56:36 -08:00
|
|
|
#include "pipeline/dbc_layout.hpp"
|
2026-02-02 12:24:50 -08:00
|
|
|
#include "pipeline/wmo_loader.hpp"
|
|
|
|
|
#include "pipeline/adt_loader.hpp"
|
|
|
|
|
#include "pipeline/terrain_mesh.hpp"
|
2026-02-07 20:02:14 -08:00
|
|
|
#include "core/application.hpp"
|
2026-02-02 12:24:50 -08:00
|
|
|
#include "core/window.hpp"
|
|
|
|
|
#include "core/logger.hpp"
|
|
|
|
|
#include "game/world.hpp"
|
|
|
|
|
#include "game/zone_manager.hpp"
|
2026-04-02 00:21:21 +03:00
|
|
|
#include "audio/audio_coordinator.hpp"
|
2026-02-09 00:40:50 -08:00
|
|
|
#include "audio/audio_engine.hpp"
|
2026-02-02 12:24:50 -08:00
|
|
|
#include "audio/music_manager.hpp"
|
2026-02-03 14:55:32 -08:00
|
|
|
#include "audio/footstep_manager.hpp"
|
2026-02-03 19:49:56 -08:00
|
|
|
#include "audio/activity_sound_manager.hpp"
|
2026-02-09 01:04:53 -08:00
|
|
|
#include "audio/mount_sound_manager.hpp"
|
2026-02-09 01:29:44 -08:00
|
|
|
#include "audio/npc_voice_manager.hpp"
|
2026-02-09 14:50:14 -08:00
|
|
|
#include "audio/ambient_sound_manager.hpp"
|
Implement comprehensive audio control panel with tabbed settings interface
Adds complete audio volume controls for all 11 audio systems with master volume. Reorganizes settings window into Video, Audio, and Gameplay tabs for better UX.
Audio Features:
- Master volume control affecting all audio systems
- Individual volume sliders for: Music, Ambient, UI, Combat, Spell, Movement, Footsteps, NPC Voices, Mounts, Activity sounds
- Real-time volume adjustment with master volume multiplier
- Restore defaults button per tab
Technical Changes:
- Added getVolumeScale() getters to all audio managers
- Integrated all 10 audio managers into renderer (UI, Combat, Spell, Movement added)
- Expanded game_screen.hpp with 11 pending volume variables
- Reorganized settings window using ImGui tab bars (Video/Audio/Gameplay)
- Audio settings uses scrollable child window for 11 volume controls
- Settings window expanded to 520x720px to accommodate comprehensive controls
2026-02-09 17:07:22 -08:00
|
|
|
#include "audio/ui_sound_manager.hpp"
|
|
|
|
|
#include "audio/combat_sound_manager.hpp"
|
|
|
|
|
#include "audio/spell_sound_manager.hpp"
|
|
|
|
|
#include "audio/movement_sound_manager.hpp"
|
2026-02-21 19:41:21 -08:00
|
|
|
#include "rendering/vk_context.hpp"
|
|
|
|
|
#include "rendering/vk_frame_data.hpp"
|
|
|
|
|
#include "rendering/vk_shader.hpp"
|
|
|
|
|
#include "rendering/vk_pipeline.hpp"
|
|
|
|
|
#include "rendering/vk_utils.hpp"
|
2026-03-08 23:13:08 -07:00
|
|
|
#include "rendering/amd_fsr3_runtime.hpp"
|
2026-04-02 00:21:21 +03:00
|
|
|
#include "rendering/spell_visual_system.hpp"
|
|
|
|
|
#include "rendering/post_process_pipeline.hpp"
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
#include "rendering/animation_controller.hpp"
|
2026-02-21 19:41:21 -08:00
|
|
|
#include <imgui.h>
|
|
|
|
|
#include <imgui_impl_vulkan.h>
|
2026-02-02 12:24:50 -08:00
|
|
|
#include <glm/gtc/matrix_transform.hpp>
|
|
|
|
|
#include <glm/gtx/euler_angles.hpp>
|
|
|
|
|
#include <glm/gtc/quaternion.hpp>
|
|
|
|
|
#include <cctype>
|
2026-02-03 14:55:32 -08:00
|
|
|
#include <cmath>
|
2026-02-03 16:21:48 -08:00
|
|
|
#include <chrono>
|
2026-03-18 10:47:34 -07:00
|
|
|
#include <filesystem>
|
|
|
|
|
|
|
|
|
|
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
|
|
|
|
#include "stb_image_write.h"
|
2026-02-20 20:31:04 -08:00
|
|
|
#include <cstdlib>
|
2026-02-03 14:55:32 -08:00
|
|
|
#include <optional>
|
2026-02-02 12:24:50 -08:00
|
|
|
#include <unordered_map>
|
|
|
|
|
#include <unordered_set>
|
2026-02-10 19:30:45 -08:00
|
|
|
#include <set>
|
2026-03-07 22:29:06 -08:00
|
|
|
#include <future>
|
2026-03-09 01:31:01 -07:00
|
|
|
#if defined(_WIN32)
|
|
|
|
|
#include <windows.h>
|
2026-03-09 02:28:49 -07:00
|
|
|
#elif defined(__linux__)
|
|
|
|
|
#include <unistd.h>
|
2026-03-09 01:31:01 -07:00
|
|
|
#endif
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
|
|
|
namespace wowee {
|
|
|
|
|
namespace rendering {
|
|
|
|
|
|
2026-02-20 20:31:04 -08:00
|
|
|
static bool envFlagEnabled(const char* key, bool defaultValue) {
|
|
|
|
|
const char* raw = std::getenv(key);
|
|
|
|
|
if (!raw || !*raw) return defaultValue;
|
|
|
|
|
std::string v(raw);
|
|
|
|
|
std::transform(v.begin(), v.end(), v.begin(), [](unsigned char c) {
|
|
|
|
|
return static_cast<char>(std::tolower(c));
|
|
|
|
|
});
|
|
|
|
|
return !(v == "0" || v == "false" || v == "off" || v == "no");
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-25 10:22:05 -08:00
|
|
|
static int envIntOrDefault(const char* key, int defaultValue) {
|
|
|
|
|
const char* raw = std::getenv(key);
|
|
|
|
|
if (!raw || !*raw) return defaultValue;
|
|
|
|
|
char* end = nullptr;
|
|
|
|
|
long n = std::strtol(raw, &end, 10);
|
|
|
|
|
if (end == raw) return defaultValue;
|
|
|
|
|
return static_cast<int>(n);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
Renderer::Renderer() = default;
|
|
|
|
|
Renderer::~Renderer() = default;
|
|
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
bool Renderer::createPerFrameResources() {
|
|
|
|
|
VkDevice device = vkCtx->getDevice();
|
|
|
|
|
|
2026-03-09 22:14:32 -07:00
|
|
|
// --- Create per-frame shadow depth images (one per in-flight frame) ---
|
|
|
|
|
// Each frame slot has its own depth image so that frame N's shadow read and
|
|
|
|
|
// frame N+1's shadow write cannot race on the same image.
|
2026-02-21 19:41:21 -08:00
|
|
|
VkImageCreateInfo imgCI{};
|
|
|
|
|
imgCI.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
|
|
|
|
imgCI.imageType = VK_IMAGE_TYPE_2D;
|
|
|
|
|
imgCI.format = VK_FORMAT_D32_SFLOAT;
|
|
|
|
|
imgCI.extent = {SHADOW_MAP_SIZE, SHADOW_MAP_SIZE, 1};
|
|
|
|
|
imgCI.mipLevels = 1;
|
|
|
|
|
imgCI.arrayLayers = 1;
|
|
|
|
|
imgCI.samples = VK_SAMPLE_COUNT_1_BIT;
|
|
|
|
|
imgCI.tiling = VK_IMAGE_TILING_OPTIMAL;
|
|
|
|
|
imgCI.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
|
|
|
|
|
VmaAllocationCreateInfo imgAllocCI{};
|
|
|
|
|
imgAllocCI.usage = VMA_MEMORY_USAGE_GPU_ONLY;
|
2026-03-09 22:14:32 -07:00
|
|
|
for (uint32_t i = 0; i < MAX_FRAMES; i++) {
|
|
|
|
|
if (vmaCreateImage(vkCtx->getAllocator(), &imgCI, &imgAllocCI,
|
|
|
|
|
&shadowDepthImage[i], &shadowDepthAlloc[i], nullptr) != VK_SUCCESS) {
|
|
|
|
|
LOG_ERROR("Failed to create shadow depth image [", i, "]");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
shadowDepthLayout_[i] = VK_IMAGE_LAYOUT_UNDEFINED;
|
2026-02-21 19:41:21 -08:00
|
|
|
}
|
|
|
|
|
|
2026-03-09 22:14:32 -07:00
|
|
|
// --- Create per-frame shadow depth image views ---
|
2026-02-21 19:41:21 -08:00
|
|
|
VkImageViewCreateInfo viewCI{};
|
|
|
|
|
viewCI.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
|
|
|
|
viewCI.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
|
|
|
|
viewCI.format = VK_FORMAT_D32_SFLOAT;
|
|
|
|
|
viewCI.subresourceRange = {VK_IMAGE_ASPECT_DEPTH_BIT, 0, 1, 0, 1};
|
2026-03-09 22:14:32 -07:00
|
|
|
for (uint32_t i = 0; i < MAX_FRAMES; i++) {
|
|
|
|
|
viewCI.image = shadowDepthImage[i];
|
|
|
|
|
if (vkCreateImageView(device, &viewCI, nullptr, &shadowDepthView[i]) != VK_SUCCESS) {
|
|
|
|
|
LOG_ERROR("Failed to create shadow depth image view [", i, "]");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-02-21 19:41:21 -08:00
|
|
|
}
|
|
|
|
|
|
2026-03-09 22:14:32 -07:00
|
|
|
// --- Create shadow sampler (shared — read-only, no per-frame needed) ---
|
2026-02-21 19:41:21 -08:00
|
|
|
VkSamplerCreateInfo sampCI{};
|
|
|
|
|
sampCI.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
|
2026-02-23 04:26:20 -08:00
|
|
|
sampCI.magFilter = VK_FILTER_LINEAR;
|
|
|
|
|
sampCI.minFilter = VK_FILTER_LINEAR;
|
2026-02-21 19:41:21 -08:00
|
|
|
sampCI.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
|
|
|
|
|
sampCI.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
|
|
|
|
|
sampCI.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
|
|
|
|
|
sampCI.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
|
|
|
|
|
sampCI.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
|
|
|
|
|
sampCI.compareEnable = VK_TRUE;
|
2026-02-23 04:26:20 -08:00
|
|
|
sampCI.compareOp = VK_COMPARE_OP_LESS_OR_EQUAL;
|
2026-03-24 11:44:54 -07:00
|
|
|
shadowSampler = vkCtx->getOrCreateSampler(sampCI);
|
|
|
|
|
if (shadowSampler == VK_NULL_HANDLE) {
|
2026-02-21 19:41:21 -08:00
|
|
|
LOG_ERROR("Failed to create shadow sampler");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Create shadow render pass (depth-only) ---
|
|
|
|
|
VkAttachmentDescription depthAtt{};
|
|
|
|
|
depthAtt.format = VK_FORMAT_D32_SFLOAT;
|
|
|
|
|
depthAtt.samples = VK_SAMPLE_COUNT_1_BIT;
|
|
|
|
|
depthAtt.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
|
|
|
|
depthAtt.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
|
|
|
|
depthAtt.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
|
|
|
|
depthAtt.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
2026-02-22 10:23:20 -08:00
|
|
|
depthAtt.initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
2026-02-21 19:41:21 -08:00
|
|
|
depthAtt.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
|
|
|
|
|
|
|
|
|
VkAttachmentReference depthRef{};
|
|
|
|
|
depthRef.attachment = 0;
|
|
|
|
|
depthRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
|
|
|
|
|
|
|
|
|
VkSubpassDescription subpass{};
|
|
|
|
|
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
|
|
|
|
subpass.pDepthStencilAttachment = &depthRef;
|
|
|
|
|
|
|
|
|
|
VkSubpassDependency dep{};
|
|
|
|
|
dep.srcSubpass = VK_SUBPASS_EXTERNAL;
|
|
|
|
|
dep.dstSubpass = 0;
|
|
|
|
|
dep.srcStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
|
|
|
|
|
dep.dstStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
|
|
|
|
|
dep.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
|
|
|
|
dep.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
|
|
|
|
|
|
|
|
|
VkRenderPassCreateInfo rpCI{};
|
|
|
|
|
rpCI.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
|
|
|
|
|
rpCI.attachmentCount = 1;
|
|
|
|
|
rpCI.pAttachments = &depthAtt;
|
|
|
|
|
rpCI.subpassCount = 1;
|
|
|
|
|
rpCI.pSubpasses = &subpass;
|
|
|
|
|
rpCI.dependencyCount = 1;
|
|
|
|
|
rpCI.pDependencies = &dep;
|
|
|
|
|
if (vkCreateRenderPass(device, &rpCI, nullptr, &shadowRenderPass) != VK_SUCCESS) {
|
|
|
|
|
LOG_ERROR("Failed to create shadow render pass");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-09 22:14:32 -07:00
|
|
|
// --- Create per-frame shadow framebuffers ---
|
2026-02-21 19:41:21 -08:00
|
|
|
VkFramebufferCreateInfo fbCI{};
|
|
|
|
|
fbCI.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
|
|
|
|
|
fbCI.renderPass = shadowRenderPass;
|
|
|
|
|
fbCI.attachmentCount = 1;
|
|
|
|
|
fbCI.width = SHADOW_MAP_SIZE;
|
|
|
|
|
fbCI.height = SHADOW_MAP_SIZE;
|
|
|
|
|
fbCI.layers = 1;
|
2026-03-09 22:14:32 -07:00
|
|
|
for (uint32_t i = 0; i < MAX_FRAMES; i++) {
|
|
|
|
|
fbCI.pAttachments = &shadowDepthView[i];
|
|
|
|
|
if (vkCreateFramebuffer(device, &fbCI, nullptr, &shadowFramebuffer[i]) != VK_SUCCESS) {
|
|
|
|
|
LOG_ERROR("Failed to create shadow framebuffer [", i, "]");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-02-21 19:41:21 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Create descriptor set layout for set 0 (per-frame UBO + shadow sampler) ---
|
|
|
|
|
VkDescriptorSetLayoutBinding bindings[2]{};
|
|
|
|
|
bindings[0].binding = 0;
|
|
|
|
|
bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
|
|
|
|
bindings[0].descriptorCount = 1;
|
|
|
|
|
bindings[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
|
|
|
|
|
bindings[1].binding = 1;
|
|
|
|
|
bindings[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
|
|
|
|
bindings[1].descriptorCount = 1;
|
|
|
|
|
bindings[1].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
|
|
|
|
|
|
|
|
|
|
VkDescriptorSetLayoutCreateInfo layoutInfo{};
|
|
|
|
|
layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
|
|
|
|
|
layoutInfo.bindingCount = 2;
|
|
|
|
|
layoutInfo.pBindings = bindings;
|
|
|
|
|
|
|
|
|
|
if (vkCreateDescriptorSetLayout(device, &layoutInfo, nullptr, &perFrameSetLayout) != VK_SUCCESS) {
|
|
|
|
|
LOG_ERROR("Failed to create per-frame descriptor set layout");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-22 22:34:48 -08:00
|
|
|
// --- Create descriptor pool for UBO + image sampler (normal frames + reflection) ---
|
2026-02-21 19:41:21 -08:00
|
|
|
VkDescriptorPoolSize poolSizes[2]{};
|
|
|
|
|
poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
2026-02-22 22:34:48 -08:00
|
|
|
poolSizes[0].descriptorCount = MAX_FRAMES + 1; // +1 for reflection perFrame UBO
|
2026-02-21 19:41:21 -08:00
|
|
|
poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
2026-02-22 22:34:48 -08:00
|
|
|
poolSizes[1].descriptorCount = MAX_FRAMES + 1;
|
2026-02-21 19:41:21 -08:00
|
|
|
|
|
|
|
|
VkDescriptorPoolCreateInfo poolInfo{};
|
|
|
|
|
poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
|
2026-02-22 22:34:48 -08:00
|
|
|
poolInfo.maxSets = MAX_FRAMES + 1; // +1 for reflection descriptor set
|
2026-02-21 19:41:21 -08:00
|
|
|
poolInfo.poolSizeCount = 2;
|
|
|
|
|
poolInfo.pPoolSizes = poolSizes;
|
|
|
|
|
|
|
|
|
|
if (vkCreateDescriptorPool(device, &poolInfo, nullptr, &sceneDescriptorPool) != VK_SUCCESS) {
|
|
|
|
|
LOG_ERROR("Failed to create scene descriptor pool");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Create per-frame UBOs and descriptor sets ---
|
|
|
|
|
for (uint32_t i = 0; i < MAX_FRAMES; i++) {
|
|
|
|
|
// Create mapped UBO
|
|
|
|
|
VkBufferCreateInfo bufInfo{};
|
|
|
|
|
bufInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
|
|
|
|
bufInfo.size = sizeof(GPUPerFrameData);
|
|
|
|
|
bufInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
|
|
|
|
|
|
|
|
|
|
VmaAllocationCreateInfo allocInfo{};
|
|
|
|
|
allocInfo.usage = VMA_MEMORY_USAGE_CPU_TO_GPU;
|
|
|
|
|
allocInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT;
|
|
|
|
|
|
|
|
|
|
VmaAllocationInfo mapInfo{};
|
|
|
|
|
if (vmaCreateBuffer(vkCtx->getAllocator(), &bufInfo, &allocInfo,
|
|
|
|
|
&perFrameUBOs[i], &perFrameUBOAllocs[i], &mapInfo) != VK_SUCCESS) {
|
|
|
|
|
LOG_ERROR("Failed to create per-frame UBO ", i);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
perFrameUBOMapped[i] = mapInfo.pMappedData;
|
|
|
|
|
|
|
|
|
|
// Allocate descriptor set
|
|
|
|
|
VkDescriptorSetAllocateInfo setAlloc{};
|
|
|
|
|
setAlloc.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
|
|
|
|
|
setAlloc.descriptorPool = sceneDescriptorPool;
|
|
|
|
|
setAlloc.descriptorSetCount = 1;
|
|
|
|
|
setAlloc.pSetLayouts = &perFrameSetLayout;
|
|
|
|
|
|
|
|
|
|
if (vkAllocateDescriptorSets(device, &setAlloc, &perFrameDescSets[i]) != VK_SUCCESS) {
|
|
|
|
|
LOG_ERROR("Failed to allocate per-frame descriptor set ", i);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Write binding 0 (UBO) and binding 1 (shadow sampler)
|
|
|
|
|
VkDescriptorBufferInfo descBuf{};
|
|
|
|
|
descBuf.buffer = perFrameUBOs[i];
|
|
|
|
|
descBuf.offset = 0;
|
|
|
|
|
descBuf.range = sizeof(GPUPerFrameData);
|
|
|
|
|
|
|
|
|
|
VkDescriptorImageInfo shadowImgInfo{};
|
|
|
|
|
shadowImgInfo.sampler = shadowSampler;
|
2026-03-09 22:14:32 -07:00
|
|
|
shadowImgInfo.imageView = shadowDepthView[i];
|
2026-02-21 19:41:21 -08:00
|
|
|
shadowImgInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
|
|
|
|
|
|
|
|
|
VkWriteDescriptorSet writes[2]{};
|
|
|
|
|
writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
|
|
|
|
writes[0].dstSet = perFrameDescSets[i];
|
|
|
|
|
writes[0].dstBinding = 0;
|
|
|
|
|
writes[0].descriptorCount = 1;
|
|
|
|
|
writes[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
|
|
|
|
writes[0].pBufferInfo = &descBuf;
|
|
|
|
|
writes[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
|
|
|
|
writes[1].dstSet = perFrameDescSets[i];
|
|
|
|
|
writes[1].dstBinding = 1;
|
|
|
|
|
writes[1].descriptorCount = 1;
|
|
|
|
|
writes[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
|
|
|
|
writes[1].pImageInfo = &shadowImgInfo;
|
|
|
|
|
|
|
|
|
|
vkUpdateDescriptorSets(device, 2, writes, 0, nullptr);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-22 22:34:48 -08:00
|
|
|
// --- Create reflection per-frame UBO and descriptor set ---
|
|
|
|
|
{
|
|
|
|
|
VkBufferCreateInfo bufInfo{};
|
|
|
|
|
bufInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
|
|
|
|
bufInfo.size = sizeof(GPUPerFrameData);
|
|
|
|
|
bufInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
|
|
|
|
|
|
|
|
|
|
VmaAllocationCreateInfo allocInfo{};
|
|
|
|
|
allocInfo.usage = VMA_MEMORY_USAGE_CPU_TO_GPU;
|
|
|
|
|
allocInfo.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT;
|
|
|
|
|
|
|
|
|
|
VmaAllocationInfo mapInfo{};
|
|
|
|
|
if (vmaCreateBuffer(vkCtx->getAllocator(), &bufInfo, &allocInfo,
|
|
|
|
|
&reflPerFrameUBO, &reflPerFrameUBOAlloc, &mapInfo) != VK_SUCCESS) {
|
|
|
|
|
LOG_ERROR("Failed to create reflection per-frame UBO");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
reflPerFrameUBOMapped = mapInfo.pMappedData;
|
|
|
|
|
|
|
|
|
|
VkDescriptorSetAllocateInfo setAlloc{};
|
|
|
|
|
setAlloc.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
|
|
|
|
|
setAlloc.descriptorPool = sceneDescriptorPool;
|
|
|
|
|
setAlloc.descriptorSetCount = 1;
|
|
|
|
|
setAlloc.pSetLayouts = &perFrameSetLayout;
|
|
|
|
|
|
|
|
|
|
if (vkAllocateDescriptorSets(device, &setAlloc, &reflPerFrameDescSet) != VK_SUCCESS) {
|
|
|
|
|
LOG_ERROR("Failed to allocate reflection per-frame descriptor set");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
VkDescriptorBufferInfo descBuf{};
|
|
|
|
|
descBuf.buffer = reflPerFrameUBO;
|
|
|
|
|
descBuf.offset = 0;
|
|
|
|
|
descBuf.range = sizeof(GPUPerFrameData);
|
|
|
|
|
|
|
|
|
|
VkDescriptorImageInfo shadowImgInfo{};
|
|
|
|
|
shadowImgInfo.sampler = shadowSampler;
|
2026-03-09 22:14:32 -07:00
|
|
|
shadowImgInfo.imageView = shadowDepthView[0]; // reflection uses frame 0 shadow view
|
2026-02-22 22:34:48 -08:00
|
|
|
shadowImgInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
|
|
|
|
|
|
|
|
|
VkWriteDescriptorSet writes[2]{};
|
|
|
|
|
writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
|
|
|
|
writes[0].dstSet = reflPerFrameDescSet;
|
|
|
|
|
writes[0].dstBinding = 0;
|
|
|
|
|
writes[0].descriptorCount = 1;
|
|
|
|
|
writes[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
|
|
|
|
|
writes[0].pBufferInfo = &descBuf;
|
|
|
|
|
writes[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
|
|
|
|
writes[1].dstSet = reflPerFrameDescSet;
|
|
|
|
|
writes[1].dstBinding = 1;
|
|
|
|
|
writes[1].descriptorCount = 1;
|
|
|
|
|
writes[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
|
|
|
|
writes[1].pImageInfo = &shadowImgInfo;
|
|
|
|
|
|
|
|
|
|
vkUpdateDescriptorSets(device, 2, writes, 0, nullptr);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
LOG_INFO("Per-frame Vulkan resources created (shadow map ", SHADOW_MAP_SIZE, "x", SHADOW_MAP_SIZE, ")");
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Renderer::destroyPerFrameResources() {
|
|
|
|
|
if (!vkCtx) return;
|
|
|
|
|
vkDeviceWaitIdle(vkCtx->getDevice());
|
|
|
|
|
VkDevice device = vkCtx->getDevice();
|
|
|
|
|
|
|
|
|
|
for (uint32_t i = 0; i < MAX_FRAMES; i++) {
|
|
|
|
|
if (perFrameUBOs[i]) {
|
|
|
|
|
vmaDestroyBuffer(vkCtx->getAllocator(), perFrameUBOs[i], perFrameUBOAllocs[i]);
|
|
|
|
|
perFrameUBOs[i] = VK_NULL_HANDLE;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-22 22:34:48 -08:00
|
|
|
if (reflPerFrameUBO) {
|
|
|
|
|
vmaDestroyBuffer(vkCtx->getAllocator(), reflPerFrameUBO, reflPerFrameUBOAlloc);
|
|
|
|
|
reflPerFrameUBO = VK_NULL_HANDLE;
|
|
|
|
|
reflPerFrameUBOMapped = nullptr;
|
|
|
|
|
}
|
2026-02-21 19:41:21 -08:00
|
|
|
if (sceneDescriptorPool) {
|
|
|
|
|
vkDestroyDescriptorPool(device, sceneDescriptorPool, nullptr);
|
|
|
|
|
sceneDescriptorPool = VK_NULL_HANDLE;
|
|
|
|
|
}
|
|
|
|
|
if (perFrameSetLayout) {
|
|
|
|
|
vkDestroyDescriptorSetLayout(device, perFrameSetLayout, nullptr);
|
|
|
|
|
perFrameSetLayout = VK_NULL_HANDLE;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-09 22:14:32 -07:00
|
|
|
// Destroy per-frame shadow resources
|
|
|
|
|
for (uint32_t i = 0; i < MAX_FRAMES; i++) {
|
|
|
|
|
if (shadowFramebuffer[i]) { vkDestroyFramebuffer(device, shadowFramebuffer[i], nullptr); shadowFramebuffer[i] = VK_NULL_HANDLE; }
|
|
|
|
|
if (shadowDepthView[i]) { vkDestroyImageView(device, shadowDepthView[i], nullptr); shadowDepthView[i] = VK_NULL_HANDLE; }
|
|
|
|
|
if (shadowDepthImage[i]) { vmaDestroyImage(vkCtx->getAllocator(), shadowDepthImage[i], shadowDepthAlloc[i]); shadowDepthImage[i] = VK_NULL_HANDLE; shadowDepthAlloc[i] = VK_NULL_HANDLE; }
|
|
|
|
|
shadowDepthLayout_[i] = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
|
|
|
}
|
2026-02-21 19:41:21 -08:00
|
|
|
if (shadowRenderPass) { vkDestroyRenderPass(device, shadowRenderPass, nullptr); shadowRenderPass = VK_NULL_HANDLE; }
|
2026-03-24 11:44:54 -07:00
|
|
|
shadowSampler = VK_NULL_HANDLE; // Owned by VkContext sampler cache
|
2026-02-21 19:41:21 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Renderer::updatePerFrameUBO() {
|
|
|
|
|
if (!camera) return;
|
|
|
|
|
|
|
|
|
|
currentFrameData.view = camera->getViewMatrix();
|
|
|
|
|
currentFrameData.projection = camera->getProjectionMatrix();
|
|
|
|
|
currentFrameData.viewPos = glm::vec4(camera->getPosition(), 1.0f);
|
|
|
|
|
currentFrameData.fogParams.z = globalTime;
|
|
|
|
|
|
|
|
|
|
// Lighting from LightingManager
|
|
|
|
|
if (lightingManager) {
|
|
|
|
|
const auto& lp = lightingManager->getLightingParams();
|
|
|
|
|
currentFrameData.lightDir = glm::vec4(lp.directionalDir, 0.0f);
|
|
|
|
|
currentFrameData.lightColor = glm::vec4(lp.diffuseColor, 1.0f);
|
|
|
|
|
currentFrameData.ambientColor = glm::vec4(lp.ambientColor, 1.0f);
|
|
|
|
|
currentFrameData.fogColor = glm::vec4(lp.fogColor, 1.0f);
|
|
|
|
|
currentFrameData.fogParams.x = lp.fogStart;
|
|
|
|
|
currentFrameData.fogParams.y = lp.fogEnd;
|
2026-02-23 00:18:32 -08:00
|
|
|
|
|
|
|
|
// Shift fog to blue when camera is significantly underwater (terrain water only).
|
|
|
|
|
if (waterRenderer && camera) {
|
|
|
|
|
glm::vec3 camPos = camera->getPosition();
|
|
|
|
|
auto waterH = waterRenderer->getNearestWaterHeightAt(camPos.x, camPos.y, camPos.z);
|
|
|
|
|
constexpr float MIN_SUBMERSION = 2.0f;
|
|
|
|
|
if (waterH && camPos.z < (*waterH - MIN_SUBMERSION)
|
|
|
|
|
&& !waterRenderer->isWmoWaterAt(camPos.x, camPos.y)) {
|
|
|
|
|
float depth = *waterH - camPos.z - MIN_SUBMERSION;
|
|
|
|
|
float blend = glm::clamp(1.0f - std::exp(-depth * 0.08f), 0.0f, 0.7f);
|
|
|
|
|
glm::vec3 underwaterFog(0.03f, 0.09f, 0.18f);
|
|
|
|
|
glm::vec3 blendedFog = glm::mix(lp.fogColor, underwaterFog, blend);
|
|
|
|
|
currentFrameData.fogColor = glm::vec4(blendedFog, 1.0f);
|
|
|
|
|
currentFrameData.fogParams.x = glm::mix(lp.fogStart, 20.0f, blend);
|
|
|
|
|
currentFrameData.fogParams.y = glm::mix(lp.fogEnd, 200.0f, blend);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-21 19:41:21 -08:00
|
|
|
}
|
|
|
|
|
|
2026-02-23 08:40:16 -08:00
|
|
|
currentFrameData.lightSpaceMatrix = lightSpaceMatrix;
|
|
|
|
|
currentFrameData.shadowParams = glm::vec4(shadowsEnabled ? 1.0f : 0.0f, 0.8f, 0.0f, 0.0f);
|
2026-02-21 19:41:21 -08:00
|
|
|
|
2026-02-22 22:34:48 -08:00
|
|
|
// Player water ripple data: pack player XY into shadowParams.zw, ripple strength into fogParams.w
|
|
|
|
|
if (cameraController) {
|
|
|
|
|
currentFrameData.shadowParams.z = characterPosition.x;
|
|
|
|
|
currentFrameData.shadowParams.w = characterPosition.y;
|
|
|
|
|
bool inWater = cameraController->isSwimming();
|
|
|
|
|
bool moving = cameraController->isMoving();
|
|
|
|
|
currentFrameData.fogParams.w = (inWater && moving) ? 1.0f : 0.0f;
|
|
|
|
|
} else {
|
|
|
|
|
currentFrameData.fogParams.w = 0.0f;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
// Copy to current frame's mapped UBO
|
|
|
|
|
uint32_t frame = vkCtx->getCurrentFrame();
|
|
|
|
|
std::memcpy(perFrameUBOMapped[frame], ¤tFrameData, sizeof(GPUPerFrameData));
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
bool Renderer::initialize(core::Window* win) {
|
|
|
|
|
window = win;
|
2026-02-21 19:41:21 -08:00
|
|
|
vkCtx = win->getVkContext();
|
2026-02-20 20:31:04 -08:00
|
|
|
deferredWorldInitEnabled_ = envFlagEnabled("WOWEE_DEFER_WORLD_SYSTEMS", true);
|
2026-02-21 19:41:21 -08:00
|
|
|
LOG_INFO("Initializing renderer (Vulkan)");
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
|
|
|
// Create camera (in front of Stormwind gate, looking north)
|
|
|
|
|
camera = std::make_unique<Camera>();
|
|
|
|
|
camera->setPosition(glm::vec3(-8900.0f, -170.0f, 150.0f));
|
|
|
|
|
camera->setRotation(0.0f, -5.0f);
|
|
|
|
|
camera->setAspectRatio(window->getAspectRatio());
|
|
|
|
|
camera->setFov(60.0f);
|
|
|
|
|
|
|
|
|
|
// Create camera controller
|
|
|
|
|
cameraController = std::make_unique<CameraController>(camera.get());
|
2026-02-02 23:03:45 -08:00
|
|
|
cameraController->setUseWoWSpeed(true); // Use realistic WoW movement speed
|
2026-02-02 12:24:50 -08:00
|
|
|
cameraController->setMouseSensitivity(0.15f);
|
|
|
|
|
|
|
|
|
|
// Create performance HUD
|
|
|
|
|
performanceHUD = std::make_unique<PerformanceHUD>();
|
|
|
|
|
performanceHUD->setPosition(PerformanceHUD::Position::TOP_LEFT);
|
|
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
// Create per-frame UBO and descriptor sets
|
|
|
|
|
if (!createPerFrameResources()) {
|
|
|
|
|
LOG_ERROR("Failed to create per-frame Vulkan resources");
|
|
|
|
|
return false;
|
2026-02-02 12:24:50 -08:00
|
|
|
}
|
|
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
// Initialize Vulkan sub-renderers (Phase 3)
|
2026-02-02 12:24:50 -08:00
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
// Sky system (owns skybox, starfield, celestial, clouds, lens flare)
|
2026-02-10 19:30:45 -08:00
|
|
|
skySystem = std::make_unique<SkySystem>();
|
2026-02-21 19:41:21 -08:00
|
|
|
if (!skySystem->initialize(vkCtx, perFrameSetLayout)) {
|
|
|
|
|
LOG_ERROR("Failed to initialize sky system");
|
|
|
|
|
return false;
|
2026-02-10 19:30:45 -08:00
|
|
|
}
|
2026-02-21 19:41:21 -08:00
|
|
|
// Expose sub-components via renderer accessors
|
|
|
|
|
skybox = nullptr; // Owned by skySystem; access via skySystem->getSkybox()
|
|
|
|
|
celestial = nullptr;
|
|
|
|
|
starField = nullptr;
|
|
|
|
|
clouds = nullptr;
|
|
|
|
|
lensFlare = nullptr;
|
2026-02-10 19:30:45 -08:00
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
weather = std::make_unique<Weather>();
|
2026-02-21 19:41:21 -08:00
|
|
|
weather->initialize(vkCtx, perFrameSetLayout);
|
2026-02-02 12:24:50 -08:00
|
|
|
|
2026-03-13 09:52:23 -07:00
|
|
|
lightning = std::make_unique<Lightning>();
|
|
|
|
|
lightning->initialize(vkCtx, perFrameSetLayout);
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
swimEffects = std::make_unique<SwimEffects>();
|
2026-02-21 19:41:21 -08:00
|
|
|
swimEffects->initialize(vkCtx, perFrameSetLayout);
|
2026-02-02 12:24:50 -08:00
|
|
|
|
2026-02-09 01:24:17 -08:00
|
|
|
mountDust = std::make_unique<MountDust>();
|
2026-02-21 19:41:21 -08:00
|
|
|
mountDust->initialize(vkCtx, perFrameSetLayout);
|
2026-02-09 01:24:17 -08:00
|
|
|
|
2026-02-19 21:13:13 -08:00
|
|
|
chargeEffect = std::make_unique<ChargeEffect>();
|
2026-02-21 19:41:21 -08:00
|
|
|
chargeEffect->initialize(vkCtx, perFrameSetLayout);
|
2026-02-19 21:13:13 -08:00
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
levelUpEffect = std::make_unique<LevelUpEffect>();
|
2026-02-02 12:24:50 -08:00
|
|
|
|
2026-03-10 19:41:01 -07:00
|
|
|
questMarkerRenderer = std::make_unique<QuestMarkerRenderer>();
|
|
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
LOG_INFO("Vulkan sub-renderers initialized (Phase 3)");
|
2026-02-09 23:41:38 -08:00
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
// LightingManager doesn't use GL — initialize for data-only use
|
|
|
|
|
lightingManager = std::make_unique<LightingManager>();
|
|
|
|
|
[[maybe_unused]] auto* assetManager = core::Application::getInstance().getAssetManager();
|
2026-02-02 12:24:50 -08:00
|
|
|
|
2026-03-09 16:04:52 -07:00
|
|
|
// Create zone manager; enrich music paths from DBC if available
|
2026-02-02 12:24:50 -08:00
|
|
|
zoneManager = std::make_unique<game::ZoneManager>();
|
|
|
|
|
zoneManager->initialize();
|
2026-03-09 16:04:52 -07:00
|
|
|
if (assetManager) {
|
|
|
|
|
zoneManager->enrichFromDBC(assetManager);
|
|
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
|
2026-04-02 00:21:21 +03:00
|
|
|
// Audio is now owned by AudioCoordinator (created by Application).
|
|
|
|
|
// Renderer receives AudioCoordinator* via setAudioCoordinator().
|
2026-02-02 12:24:50 -08:00
|
|
|
|
2026-03-07 22:03:28 -08:00
|
|
|
// Create secondary command buffer resources for multithreaded rendering
|
|
|
|
|
if (!createSecondaryCommandResources()) {
|
|
|
|
|
LOG_WARNING("Failed to create secondary command buffers — falling back to single-threaded rendering");
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 00:21:21 +03:00
|
|
|
// Create PostProcessPipeline (§4.3 — owns FSR/FXAA/FSR2/FSR3/brightness)
|
|
|
|
|
postProcessPipeline_ = std::make_unique<PostProcessPipeline>();
|
|
|
|
|
postProcessPipeline_->initialize(vkCtx);
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
LOG_INFO("Renderer initialized");
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Renderer::shutdown() {
|
2026-03-07 22:03:28 -08:00
|
|
|
destroySecondaryCommandResources();
|
|
|
|
|
|
2026-02-26 13:38:29 -08:00
|
|
|
LOG_WARNING("Renderer::shutdown - terrainManager stopWorkers...");
|
2026-02-02 12:24:50 -08:00
|
|
|
if (terrainManager) {
|
2026-02-26 13:38:29 -08:00
|
|
|
terrainManager->stopWorkers();
|
|
|
|
|
LOG_WARNING("Renderer::shutdown - terrainManager reset...");
|
2026-02-02 12:24:50 -08:00
|
|
|
terrainManager.reset();
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-26 13:38:29 -08:00
|
|
|
LOG_WARNING("Renderer::shutdown - terrainRenderer...");
|
2026-02-02 12:24:50 -08:00
|
|
|
if (terrainRenderer) {
|
|
|
|
|
terrainRenderer->shutdown();
|
|
|
|
|
terrainRenderer.reset();
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-26 13:38:29 -08:00
|
|
|
LOG_WARNING("Renderer::shutdown - waterRenderer...");
|
2026-02-02 12:24:50 -08:00
|
|
|
if (waterRenderer) {
|
|
|
|
|
waterRenderer->shutdown();
|
|
|
|
|
waterRenderer.reset();
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-26 13:38:29 -08:00
|
|
|
LOG_WARNING("Renderer::shutdown - minimap...");
|
2026-02-21 19:41:21 -08:00
|
|
|
if (minimap) {
|
|
|
|
|
minimap->shutdown();
|
|
|
|
|
minimap.reset();
|
2026-02-02 12:24:50 -08:00
|
|
|
}
|
|
|
|
|
|
2026-02-26 13:38:29 -08:00
|
|
|
LOG_WARNING("Renderer::shutdown - worldMap...");
|
2026-02-21 19:41:21 -08:00
|
|
|
if (worldMap) {
|
|
|
|
|
worldMap->shutdown();
|
|
|
|
|
worldMap.reset();
|
2026-02-02 12:24:50 -08:00
|
|
|
}
|
|
|
|
|
|
2026-02-26 13:38:29 -08:00
|
|
|
LOG_WARNING("Renderer::shutdown - skySystem...");
|
2026-02-21 19:41:21 -08:00
|
|
|
if (skySystem) {
|
|
|
|
|
skySystem->shutdown();
|
|
|
|
|
skySystem.reset();
|
2026-02-02 12:24:50 -08:00
|
|
|
}
|
|
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
// Individual sky components are owned by skySystem; just null the aliases
|
|
|
|
|
skybox = nullptr;
|
|
|
|
|
celestial = nullptr;
|
|
|
|
|
starField = nullptr;
|
|
|
|
|
clouds = nullptr;
|
|
|
|
|
lensFlare = nullptr;
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
|
|
|
if (weather) {
|
|
|
|
|
weather.reset();
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-13 09:52:23 -07:00
|
|
|
if (lightning) {
|
|
|
|
|
lightning->shutdown();
|
|
|
|
|
lightning.reset();
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
if (swimEffects) {
|
|
|
|
|
swimEffects->shutdown();
|
|
|
|
|
swimEffects.reset();
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-26 13:38:29 -08:00
|
|
|
LOG_WARNING("Renderer::shutdown - characterRenderer...");
|
2026-02-02 12:24:50 -08:00
|
|
|
if (characterRenderer) {
|
|
|
|
|
characterRenderer->shutdown();
|
|
|
|
|
characterRenderer.reset();
|
|
|
|
|
}
|
|
|
|
|
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
// Shutdown AnimationController before renderers it references (§4.2)
|
|
|
|
|
animationController_.reset();
|
|
|
|
|
|
2026-02-26 13:38:29 -08:00
|
|
|
LOG_WARNING("Renderer::shutdown - wmoRenderer...");
|
2026-02-02 12:24:50 -08:00
|
|
|
if (wmoRenderer) {
|
|
|
|
|
wmoRenderer->shutdown();
|
|
|
|
|
wmoRenderer.reset();
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 00:21:21 +03:00
|
|
|
// Shutdown SpellVisualSystem before M2Renderer (it holds M2Renderer pointer) (§4.4)
|
|
|
|
|
if (spellVisualSystem_) {
|
|
|
|
|
spellVisualSystem_->shutdown();
|
|
|
|
|
spellVisualSystem_.reset();
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-26 13:38:29 -08:00
|
|
|
LOG_WARNING("Renderer::shutdown - m2Renderer...");
|
2026-02-02 12:24:50 -08:00
|
|
|
if (m2Renderer) {
|
|
|
|
|
m2Renderer->shutdown();
|
|
|
|
|
m2Renderer.reset();
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 00:21:21 +03:00
|
|
|
// Audio shutdown is handled by AudioCoordinator (owned by Application).
|
|
|
|
|
audioCoordinator_ = nullptr;
|
2026-02-09 00:40:50 -08:00
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
// Cleanup Vulkan selection circle resources
|
|
|
|
|
if (vkCtx) {
|
|
|
|
|
VkDevice device = vkCtx->getDevice();
|
|
|
|
|
if (selCirclePipeline) { vkDestroyPipeline(device, selCirclePipeline, nullptr); selCirclePipeline = VK_NULL_HANDLE; }
|
|
|
|
|
if (selCirclePipelineLayout) { vkDestroyPipelineLayout(device, selCirclePipelineLayout, nullptr); selCirclePipelineLayout = VK_NULL_HANDLE; }
|
|
|
|
|
if (selCircleVertBuf) { vmaDestroyBuffer(vkCtx->getAllocator(), selCircleVertBuf, selCircleVertAlloc); selCircleVertBuf = VK_NULL_HANDLE; selCircleVertAlloc = VK_NULL_HANDLE; }
|
|
|
|
|
if (selCircleIdxBuf) { vmaDestroyBuffer(vkCtx->getAllocator(), selCircleIdxBuf, selCircleIdxAlloc); selCircleIdxBuf = VK_NULL_HANDLE; selCircleIdxAlloc = VK_NULL_HANDLE; }
|
2026-02-21 20:01:01 -08:00
|
|
|
if (overlayPipeline) { vkDestroyPipeline(device, overlayPipeline, nullptr); overlayPipeline = VK_NULL_HANDLE; }
|
|
|
|
|
if (overlayPipelineLayout) { vkDestroyPipelineLayout(device, overlayPipelineLayout, nullptr); overlayPipelineLayout = VK_NULL_HANDLE; }
|
2026-02-03 20:40:59 -08:00
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
|
2026-04-02 00:21:21 +03:00
|
|
|
// Shutdown post-process pipeline (FSR/FXAA/FSR2 resources) (§4.3)
|
|
|
|
|
if (postProcessPipeline_) {
|
|
|
|
|
postProcessPipeline_->shutdown();
|
|
|
|
|
postProcessPipeline_.reset();
|
|
|
|
|
}
|
2026-02-21 19:41:21 -08:00
|
|
|
destroyPerFrameResources();
|
2026-02-04 15:21:04 -08:00
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
zoneManager.reset();
|
|
|
|
|
|
|
|
|
|
performanceHUD.reset();
|
|
|
|
|
cameraController.reset();
|
|
|
|
|
camera.reset();
|
|
|
|
|
|
|
|
|
|
LOG_INFO("Renderer shutdown");
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-22 05:58:45 -08:00
|
|
|
void Renderer::registerPreview(CharacterPreview* preview) {
|
|
|
|
|
if (!preview) return;
|
|
|
|
|
auto it = std::find(activePreviews_.begin(), activePreviews_.end(), preview);
|
|
|
|
|
if (it == activePreviews_.end()) {
|
|
|
|
|
activePreviews_.push_back(preview);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Renderer::unregisterPreview(CharacterPreview* preview) {
|
|
|
|
|
auto it = std::find(activePreviews_.begin(), activePreviews_.end(), preview);
|
|
|
|
|
if (it != activePreviews_.end()) {
|
|
|
|
|
activePreviews_.erase(it);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-06 19:15:34 -08:00
|
|
|
void Renderer::setWaterRefractionEnabled(bool enabled) {
|
|
|
|
|
if (waterRenderer) waterRenderer->setRefractionEnabled(enabled);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Renderer::isWaterRefractionEnabled() const {
|
|
|
|
|
return waterRenderer && waterRenderer->isRefractionEnabled();
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-22 02:59:24 -08:00
|
|
|
void Renderer::setMsaaSamples(VkSampleCountFlagBits samples) {
|
|
|
|
|
if (!vkCtx) return;
|
|
|
|
|
|
2026-03-08 01:22:15 -08:00
|
|
|
// FSR2 requires non-MSAA render pass — block MSAA changes while FSR2 is active
|
2026-04-02 00:21:21 +03:00
|
|
|
if (postProcessPipeline_ && postProcessPipeline_->isFsr2BlockingMsaa() && samples > VK_SAMPLE_COUNT_1_BIT) return;
|
2026-03-08 01:22:15 -08:00
|
|
|
|
2026-02-22 03:05:55 -08:00
|
|
|
// Clamp to device maximum
|
|
|
|
|
VkSampleCountFlagBits maxSamples = vkCtx->getMaxUsableSampleCount();
|
|
|
|
|
if (samples > maxSamples) samples = maxSamples;
|
|
|
|
|
|
2026-02-22 03:37:47 -08:00
|
|
|
if (samples == vkCtx->getMsaaSamples()) return;
|
|
|
|
|
|
|
|
|
|
// Defer to between frames — cannot destroy render pass/framebuffers mid-frame
|
|
|
|
|
pendingMsaaSamples_ = samples;
|
|
|
|
|
msaaChangePending_ = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Renderer::applyMsaaChange() {
|
|
|
|
|
VkSampleCountFlagBits samples = pendingMsaaSamples_;
|
|
|
|
|
msaaChangePending_ = false;
|
|
|
|
|
|
2026-02-22 02:59:24 -08:00
|
|
|
VkSampleCountFlagBits current = vkCtx->getMsaaSamples();
|
|
|
|
|
if (samples == current) return;
|
|
|
|
|
|
|
|
|
|
LOG_INFO("Changing MSAA from ", static_cast<int>(current), "x to ", static_cast<int>(samples), "x");
|
|
|
|
|
|
2026-02-22 03:05:55 -08:00
|
|
|
// Single GPU wait — all subsequent operations are CPU-side object creation
|
2026-02-22 02:59:24 -08:00
|
|
|
vkDeviceWaitIdle(vkCtx->getDevice());
|
|
|
|
|
|
|
|
|
|
// Set new MSAA and recreate swapchain (render pass, depth, MSAA image, framebuffers)
|
|
|
|
|
vkCtx->setMsaaSamples(samples);
|
2026-02-22 03:05:55 -08:00
|
|
|
if (!vkCtx->recreateSwapchain(window->getWidth(), window->getHeight())) {
|
|
|
|
|
LOG_ERROR("MSAA change failed — reverting to 1x");
|
|
|
|
|
vkCtx->setMsaaSamples(VK_SAMPLE_COUNT_1_BIT);
|
2026-03-27 14:53:29 -07:00
|
|
|
(void)vkCtx->recreateSwapchain(window->getWidth(), window->getHeight());
|
2026-02-22 03:05:55 -08:00
|
|
|
}
|
2026-02-22 02:59:24 -08:00
|
|
|
|
|
|
|
|
// Recreate all sub-renderer pipelines (they embed sample count from render pass)
|
|
|
|
|
if (terrainRenderer) terrainRenderer->recreatePipelines();
|
2026-02-22 22:34:48 -08:00
|
|
|
if (waterRenderer) {
|
|
|
|
|
waterRenderer->recreatePipelines();
|
2026-03-07 22:03:28 -08:00
|
|
|
waterRenderer->destroyWater1xResources(); // no longer used
|
2026-02-22 22:34:48 -08:00
|
|
|
}
|
2026-02-22 02:59:24 -08:00
|
|
|
if (wmoRenderer) wmoRenderer->recreatePipelines();
|
|
|
|
|
if (m2Renderer) m2Renderer->recreatePipelines();
|
|
|
|
|
if (characterRenderer) characterRenderer->recreatePipelines();
|
|
|
|
|
if (questMarkerRenderer) questMarkerRenderer->recreatePipelines();
|
|
|
|
|
if (weather) weather->recreatePipelines();
|
2026-03-13 09:52:23 -07:00
|
|
|
if (lightning) lightning->recreatePipelines();
|
2026-02-22 02:59:24 -08:00
|
|
|
if (swimEffects) swimEffects->recreatePipelines();
|
|
|
|
|
if (mountDust) mountDust->recreatePipelines();
|
|
|
|
|
if (chargeEffect) chargeEffect->recreatePipelines();
|
|
|
|
|
|
|
|
|
|
// Sky system sub-renderers
|
|
|
|
|
if (skySystem) {
|
|
|
|
|
if (auto* sb = skySystem->getSkybox()) sb->recreatePipelines();
|
|
|
|
|
if (auto* sf = skySystem->getStarField()) sf->recreatePipelines();
|
|
|
|
|
if (auto* ce = skySystem->getCelestial()) ce->recreatePipelines();
|
|
|
|
|
if (auto* cl = skySystem->getClouds()) cl->recreatePipelines();
|
|
|
|
|
if (auto* lf = skySystem->getLensFlare()) lf->recreatePipelines();
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-22 03:49:44 -08:00
|
|
|
if (minimap) minimap->recreatePipelines();
|
|
|
|
|
|
2026-03-07 22:03:28 -08:00
|
|
|
// Selection circle + overlay + FSR use lazy init, just destroy them
|
2026-02-22 02:59:24 -08:00
|
|
|
VkDevice device = vkCtx->getDevice();
|
|
|
|
|
if (selCirclePipeline) { vkDestroyPipeline(device, selCirclePipeline, nullptr); selCirclePipeline = VK_NULL_HANDLE; }
|
|
|
|
|
if (overlayPipeline) { vkDestroyPipeline(device, overlayPipeline, nullptr); overlayPipeline = VK_NULL_HANDLE; }
|
2026-04-02 00:21:21 +03:00
|
|
|
if (postProcessPipeline_) postProcessPipeline_->destroyAllResources(); // Will be lazily recreated in beginFrame()
|
2026-02-22 02:59:24 -08:00
|
|
|
|
|
|
|
|
// Reinitialize ImGui Vulkan backend with new MSAA sample count
|
|
|
|
|
ImGui_ImplVulkan_Shutdown();
|
|
|
|
|
ImGui_ImplVulkan_InitInfo initInfo{};
|
|
|
|
|
initInfo.ApiVersion = VK_API_VERSION_1_1;
|
|
|
|
|
initInfo.Instance = vkCtx->getInstance();
|
|
|
|
|
initInfo.PhysicalDevice = vkCtx->getPhysicalDevice();
|
|
|
|
|
initInfo.Device = vkCtx->getDevice();
|
|
|
|
|
initInfo.QueueFamily = vkCtx->getGraphicsQueueFamily();
|
|
|
|
|
initInfo.Queue = vkCtx->getGraphicsQueue();
|
|
|
|
|
initInfo.DescriptorPool = vkCtx->getImGuiDescriptorPool();
|
|
|
|
|
initInfo.MinImageCount = 2;
|
|
|
|
|
initInfo.ImageCount = vkCtx->getSwapchainImageCount();
|
|
|
|
|
initInfo.PipelineInfoMain.RenderPass = vkCtx->getImGuiRenderPass();
|
|
|
|
|
initInfo.PipelineInfoMain.MSAASamples = vkCtx->getMsaaSamples();
|
2026-04-03 20:19:33 -07:00
|
|
|
initInfo.CheckVkResultFn = [](VkResult err) {
|
|
|
|
|
if (err != VK_SUCCESS)
|
|
|
|
|
LOG_ERROR("ImGui Vulkan error: ", static_cast<int>(err));
|
|
|
|
|
};
|
2026-02-22 02:59:24 -08:00
|
|
|
ImGui_ImplVulkan_Init(&initInfo);
|
|
|
|
|
|
|
|
|
|
LOG_INFO("MSAA change complete");
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
void Renderer::beginFrame() {
|
2026-04-03 09:41:34 +03:00
|
|
|
ZoneScopedN("Renderer::beginFrame");
|
2026-02-21 19:41:21 -08:00
|
|
|
if (!vkCtx) return;
|
2026-03-02 08:19:14 -08:00
|
|
|
if (vkCtx->isDeviceLost()) return;
|
2026-02-21 19:41:21 -08:00
|
|
|
|
2026-02-22 03:37:47 -08:00
|
|
|
// Apply deferred MSAA change between frames (before any rendering state is used)
|
|
|
|
|
if (msaaChangePending_) {
|
|
|
|
|
applyMsaaChange();
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 00:21:21 +03:00
|
|
|
// Post-process resource management (§4.3 — delegates to PostProcessPipeline)
|
|
|
|
|
if (postProcessPipeline_) postProcessPipeline_->manageResources();
|
2026-03-12 16:43:48 -07:00
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
// Handle swapchain recreation if needed
|
|
|
|
|
if (vkCtx->isSwapchainDirty()) {
|
2026-03-27 14:53:29 -07:00
|
|
|
(void)vkCtx->recreateSwapchain(window->getWidth(), window->getHeight());
|
2026-02-22 22:42:58 -08:00
|
|
|
// Rebuild water resources that reference swapchain extent/views
|
|
|
|
|
if (waterRenderer) {
|
|
|
|
|
waterRenderer->recreatePipelines();
|
2026-03-07 22:03:28 -08:00
|
|
|
}
|
2026-04-02 00:21:21 +03:00
|
|
|
// Recreate post-process resources for new swapchain dimensions
|
|
|
|
|
if (postProcessPipeline_) postProcessPipeline_->handleSwapchainResize();
|
2026-02-04 15:21:04 -08:00
|
|
|
}
|
|
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
// Acquire swapchain image and begin command buffer
|
|
|
|
|
currentCmd = vkCtx->beginFrame(currentImageIndex);
|
|
|
|
|
if (currentCmd == VK_NULL_HANDLE) {
|
|
|
|
|
// Swapchain out of date, will retry next frame
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 00:21:21 +03:00
|
|
|
// FSR2 jitter pattern (§4.3 — delegates to PostProcessPipeline)
|
|
|
|
|
if (postProcessPipeline_ && camera) postProcessPipeline_->applyJitter(camera.get());
|
2026-03-07 23:13:01 -08:00
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
// Update per-frame UBO with current camera/lighting state
|
|
|
|
|
updatePerFrameUBO();
|
|
|
|
|
|
2026-03-02 09:52:09 -08:00
|
|
|
// GPU crash diagnostic: skip all pre-passes to isolate crash source
|
|
|
|
|
static const bool skipPrePasses = (std::getenv("WOWEE_SKIP_PREPASSES") != nullptr);
|
|
|
|
|
|
|
|
|
|
if (!skipPrePasses) {
|
2026-02-21 19:41:21 -08:00
|
|
|
// --- Off-screen pre-passes (before main render pass) ---
|
|
|
|
|
// Minimap composite (renders 3x3 tile grid into 768x768 render target)
|
|
|
|
|
if (minimap && minimap->isEnabled() && camera) {
|
|
|
|
|
glm::vec3 minimapCenter = camera->getPosition();
|
|
|
|
|
if (cameraController && cameraController->isThirdPerson())
|
|
|
|
|
minimapCenter = characterPosition;
|
|
|
|
|
minimap->compositePass(currentCmd, minimapCenter);
|
|
|
|
|
}
|
|
|
|
|
// World map composite (renders zone tiles into 1024x768 render target)
|
|
|
|
|
if (worldMap) {
|
|
|
|
|
worldMap->compositePass(currentCmd);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-22 05:58:45 -08:00
|
|
|
// Character preview composite passes
|
|
|
|
|
for (auto* preview : activePreviews_) {
|
|
|
|
|
if (preview && preview->isModelLoaded()) {
|
|
|
|
|
preview->compositePass(currentCmd, vkCtx->getCurrentFrame());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
// Shadow pre-pass (before main render pass)
|
2026-03-09 22:14:32 -07:00
|
|
|
if (shadowsEnabled && shadowDepthImage[0] != VK_NULL_HANDLE) {
|
2026-02-21 19:41:21 -08:00
|
|
|
renderShadowPass();
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-22 22:34:48 -08:00
|
|
|
// Water reflection pre-pass (renders scene from mirrored camera into 512x512 texture)
|
|
|
|
|
renderReflectionPass();
|
2026-03-02 09:52:09 -08:00
|
|
|
} // !skipPrePasses
|
2026-02-22 22:34:48 -08:00
|
|
|
|
2026-03-07 22:03:28 -08:00
|
|
|
// --- Begin render pass ---
|
2026-04-02 00:21:21 +03:00
|
|
|
// Select framebuffer: PP off-screen target or swapchain (§4.3 — PostProcessPipeline)
|
2026-02-21 19:41:21 -08:00
|
|
|
VkRenderPassBeginInfo rpInfo{};
|
|
|
|
|
rpInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
|
|
|
|
|
rpInfo.renderPass = vkCtx->getImGuiRenderPass();
|
2026-03-07 22:03:28 -08:00
|
|
|
|
|
|
|
|
VkExtent2D renderExtent;
|
2026-04-02 00:21:21 +03:00
|
|
|
VkFramebuffer ppFB = postProcessPipeline_ ? postProcessPipeline_->getSceneFramebuffer() : VK_NULL_HANDLE;
|
|
|
|
|
if (ppFB != VK_NULL_HANDLE) {
|
|
|
|
|
rpInfo.framebuffer = ppFB;
|
|
|
|
|
renderExtent = postProcessPipeline_->getSceneRenderExtent();
|
2026-03-07 22:03:28 -08:00
|
|
|
} else {
|
|
|
|
|
rpInfo.framebuffer = vkCtx->getSwapchainFramebuffers()[currentImageIndex];
|
|
|
|
|
renderExtent = vkCtx->getSwapchainExtent();
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
rpInfo.renderArea.offset = {0, 0};
|
2026-03-07 22:03:28 -08:00
|
|
|
rpInfo.renderArea.extent = renderExtent;
|
2026-02-21 19:41:21 -08:00
|
|
|
|
2026-03-07 22:03:28 -08:00
|
|
|
// Clear values must match attachment count: 2 (no MSAA), 3 (MSAA), or 4 (MSAA+depth resolve)
|
|
|
|
|
VkClearValue clearValues[4]{};
|
2026-02-21 19:41:21 -08:00
|
|
|
clearValues[0].color = {{0.0f, 0.0f, 0.0f, 1.0f}};
|
|
|
|
|
clearValues[1].depthStencil = {1.0f, 0};
|
2026-03-07 22:03:28 -08:00
|
|
|
clearValues[2].color = {{0.0f, 0.0f, 0.0f, 1.0f}};
|
|
|
|
|
clearValues[3].depthStencil = {1.0f, 0};
|
2026-02-22 03:11:21 -08:00
|
|
|
bool msaaOn = (vkCtx->getMsaaSamples() > VK_SAMPLE_COUNT_1_BIT);
|
2026-03-07 22:03:28 -08:00
|
|
|
if (msaaOn) {
|
|
|
|
|
bool depthRes = (vkCtx->getDepthResolveImageView() != VK_NULL_HANDLE);
|
|
|
|
|
rpInfo.clearValueCount = depthRes ? 4 : 3;
|
|
|
|
|
} else {
|
|
|
|
|
rpInfo.clearValueCount = 2;
|
|
|
|
|
}
|
2026-02-21 19:41:21 -08:00
|
|
|
rpInfo.pClearValues = clearValues;
|
|
|
|
|
|
2026-03-07 22:03:28 -08:00
|
|
|
// Cache render pass state for secondary command buffer inheritance
|
|
|
|
|
activeRenderPass_ = rpInfo.renderPass;
|
|
|
|
|
activeFramebuffer_ = rpInfo.framebuffer;
|
|
|
|
|
activeRenderExtent_ = renderExtent;
|
|
|
|
|
|
|
|
|
|
VkSubpassContents subpassMode = parallelRecordingEnabled_
|
|
|
|
|
? VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS
|
|
|
|
|
: VK_SUBPASS_CONTENTS_INLINE;
|
|
|
|
|
vkCmdBeginRenderPass(currentCmd, &rpInfo, subpassMode);
|
2026-02-21 19:41:21 -08:00
|
|
|
|
2026-03-07 22:03:28 -08:00
|
|
|
if (!parallelRecordingEnabled_) {
|
|
|
|
|
// Fallback: set dynamic viewport and scissor on primary (inline mode)
|
|
|
|
|
VkViewport viewport{};
|
|
|
|
|
viewport.width = static_cast<float>(renderExtent.width);
|
|
|
|
|
viewport.height = static_cast<float>(renderExtent.height);
|
|
|
|
|
viewport.maxDepth = 1.0f;
|
|
|
|
|
vkCmdSetViewport(currentCmd, 0, 1, &viewport);
|
2026-02-21 19:41:21 -08:00
|
|
|
|
2026-03-07 22:03:28 -08:00
|
|
|
VkRect2D scissor{};
|
|
|
|
|
scissor.extent = renderExtent;
|
|
|
|
|
vkCmdSetScissor(currentCmd, 0, 1, &scissor);
|
|
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Renderer::endFrame() {
|
2026-04-03 09:41:34 +03:00
|
|
|
ZoneScopedN("Renderer::endFrame");
|
2026-02-21 19:41:21 -08:00
|
|
|
if (!vkCtx || currentCmd == VK_NULL_HANDLE) return;
|
|
|
|
|
|
2026-03-24 13:05:27 -07:00
|
|
|
// Track whether a post-processing path switched to an INLINE render pass.
|
|
|
|
|
// beginFrame() may have started the scene pass with SECONDARY_COMMAND_BUFFERS;
|
|
|
|
|
// post-proc paths end it and begin a new INLINE pass for the swapchain output.
|
|
|
|
|
endFrameInlineMode_ = false;
|
|
|
|
|
|
2026-04-02 00:21:21 +03:00
|
|
|
// Post-process execution (§4.3 — delegates to PostProcessPipeline)
|
|
|
|
|
if (postProcessPipeline_) {
|
|
|
|
|
endFrameInlineMode_ = postProcessPipeline_->executePostProcessing(
|
|
|
|
|
currentCmd, currentImageIndex, camera.get(), lastDeltaTime_);
|
2026-03-07 22:03:28 -08:00
|
|
|
}
|
|
|
|
|
|
2026-03-24 13:05:27 -07:00
|
|
|
// ImGui rendering — must respect the subpass contents mode of the
|
|
|
|
|
// CURRENT render pass. Post-processing paths (FSR/FXAA) end the scene
|
|
|
|
|
// pass and begin a new INLINE pass; if none ran, we're still inside the
|
|
|
|
|
// scene pass which may be SECONDARY_COMMAND_BUFFERS when parallel recording
|
|
|
|
|
// is active. Track this via endFrameInlineMode_ (set true by any post-proc
|
|
|
|
|
// path that started an INLINE render pass).
|
|
|
|
|
if (parallelRecordingEnabled_ && !endFrameInlineMode_) {
|
|
|
|
|
// Still in the scene pass with SECONDARY_COMMAND_BUFFERS — record
|
|
|
|
|
// ImGui into a secondary command buffer.
|
2026-03-07 22:03:28 -08:00
|
|
|
VkCommandBuffer imguiCmd = beginSecondary(SEC_IMGUI);
|
|
|
|
|
setSecondaryViewportScissor(imguiCmd);
|
|
|
|
|
ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), imguiCmd);
|
|
|
|
|
vkEndCommandBuffer(imguiCmd);
|
|
|
|
|
vkCmdExecuteCommands(currentCmd, 1, &imguiCmd);
|
|
|
|
|
} else {
|
2026-03-24 13:05:27 -07:00
|
|
|
// INLINE render pass (post-process pass or non-parallel mode).
|
2026-03-07 22:03:28 -08:00
|
|
|
ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), currentCmd);
|
|
|
|
|
}
|
2026-02-21 19:41:21 -08:00
|
|
|
|
|
|
|
|
vkCmdEndRenderPass(currentCmd);
|
|
|
|
|
|
2026-03-06 19:15:34 -08:00
|
|
|
uint32_t frame = vkCtx->getCurrentFrame();
|
|
|
|
|
|
|
|
|
|
// Capture scene color/depth into per-frame history images for water refraction
|
|
|
|
|
if (waterRenderer && waterRenderer->isRefractionEnabled() && waterRenderer->hasSurfaces()
|
|
|
|
|
&& currentImageIndex < vkCtx->getSwapchainImages().size()) {
|
|
|
|
|
waterRenderer->captureSceneHistory(
|
|
|
|
|
currentCmd,
|
|
|
|
|
vkCtx->getSwapchainImages()[currentImageIndex],
|
|
|
|
|
vkCtx->getDepthCopySourceImage(),
|
|
|
|
|
vkCtx->getSwapchainExtent(),
|
|
|
|
|
vkCtx->isDepthCopySourceMsaa(),
|
|
|
|
|
frame);
|
|
|
|
|
}
|
2026-02-22 09:34:27 -08:00
|
|
|
|
2026-03-07 22:03:28 -08:00
|
|
|
// Water now renders in the main pass (renderWorld), no separate 1x pass needed.
|
2026-02-22 22:34:48 -08:00
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
// Submit and present
|
|
|
|
|
vkCtx->endFrame(currentCmd, currentImageIndex);
|
|
|
|
|
currentCmd = VK_NULL_HANDLE;
|
2026-02-02 12:24:50 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Renderer::setCharacterFollow(uint32_t instanceId) {
|
|
|
|
|
characterInstanceId = instanceId;
|
|
|
|
|
if (cameraController && instanceId > 0) {
|
|
|
|
|
cameraController->setFollowTarget(&characterPosition);
|
|
|
|
|
}
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
if (animationController_) animationController_->onCharacterFollow(instanceId);
|
2026-02-02 12:24:50 -08:00
|
|
|
}
|
|
|
|
|
|
Fix mount sounds, grey WMO meshes, taxi landing, tree animations, and classic dismount
- Per-family mount sounds (kodo, tallstrider, mechanostrider, etc.) detected from M2 model path
- Skip WMO groups with SHOW_SKYBOX flag or all-untextured batches (grey mesh in Orgrimmar)
- Freeze physics during taxi landing until terrain loads to prevent falling through void
- Disable bone animations on tropical vegetation (palm, bamboo, banana, etc.) to fix wiggling
- Snap player to final taxi waypoint on flight completion
- Extract mount aura spell ID from classic UNIT_FIELD_AURAS for CMSG_CANCEL_AURA dismount
- Increase /unstuck forward nudge to 5 units
2026-02-14 21:04:20 -08:00
|
|
|
void Renderer::setMounted(uint32_t mountInstId, uint32_t mountDisplayId, float heightOffset, const std::string& modelPath) {
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
if (animationController_) animationController_->setMounted(mountInstId, mountDisplayId, heightOffset, modelPath);
|
2026-02-07 17:59:40 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Renderer::clearMount() {
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
if (animationController_) animationController_->clearMount();
|
2026-02-07 17:59:40 -08:00
|
|
|
}
|
|
|
|
|
|
2026-02-05 14:01:26 -08:00
|
|
|
|
|
|
|
|
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
void Renderer::playEmote(const std::string& emoteName) {
|
|
|
|
|
if (animationController_) animationController_->playEmote(emoteName);
|
2026-02-05 14:01:26 -08:00
|
|
|
}
|
|
|
|
|
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
void Renderer::cancelEmote() {
|
|
|
|
|
if (animationController_) animationController_->cancelEmote();
|
|
|
|
|
}
|
2026-02-05 14:01:26 -08:00
|
|
|
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
bool Renderer::isEmoteActive() const {
|
|
|
|
|
return animationController_ && animationController_->isEmoteActive();
|
|
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
void Renderer::setInCombat(bool combat) {
|
|
|
|
|
if (animationController_) animationController_->setInCombat(combat);
|
|
|
|
|
}
|
2026-02-19 21:13:13 -08:00
|
|
|
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
void Renderer::setEquippedWeaponType(uint32_t inventoryType) {
|
|
|
|
|
if (animationController_) animationController_->setEquippedWeaponType(inventoryType);
|
|
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
void Renderer::setCharging(bool c) {
|
|
|
|
|
if (animationController_) animationController_->setCharging(c);
|
|
|
|
|
}
|
2026-02-03 19:29:11 -08:00
|
|
|
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
bool Renderer::isCharging() const {
|
|
|
|
|
return animationController_ && animationController_->isCharging();
|
|
|
|
|
}
|
2026-02-03 19:29:11 -08:00
|
|
|
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
void Renderer::setTaxiFlight(bool taxi) {
|
|
|
|
|
if (animationController_) animationController_->setTaxiFlight(taxi);
|
2026-02-02 12:24:50 -08:00
|
|
|
}
|
|
|
|
|
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
void Renderer::setMountPitchRoll(float pitch, float roll) {
|
|
|
|
|
if (animationController_) animationController_->setMountPitchRoll(pitch, roll);
|
2026-02-02 12:24:50 -08:00
|
|
|
}
|
|
|
|
|
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
bool Renderer::isMounted() const {
|
|
|
|
|
return animationController_ && animationController_->isMounted();
|
2026-02-02 12:24:50 -08:00
|
|
|
}
|
|
|
|
|
|
2026-03-18 10:47:34 -07:00
|
|
|
bool Renderer::captureScreenshot(const std::string& outputPath) {
|
|
|
|
|
if (!vkCtx) return false;
|
|
|
|
|
|
|
|
|
|
VkDevice device = vkCtx->getDevice();
|
|
|
|
|
VmaAllocator alloc = vkCtx->getAllocator();
|
|
|
|
|
VkExtent2D extent = vkCtx->getSwapchainExtent();
|
|
|
|
|
const auto& images = vkCtx->getSwapchainImages();
|
|
|
|
|
|
|
|
|
|
if (images.empty() || currentImageIndex >= images.size()) return false;
|
|
|
|
|
|
|
|
|
|
VkImage srcImage = images[currentImageIndex];
|
|
|
|
|
uint32_t w = extent.width;
|
|
|
|
|
uint32_t h = extent.height;
|
|
|
|
|
VkDeviceSize bufSize = static_cast<VkDeviceSize>(w) * h * 4;
|
|
|
|
|
|
|
|
|
|
// Stall GPU so the swapchain image is idle
|
|
|
|
|
vkDeviceWaitIdle(device);
|
|
|
|
|
|
|
|
|
|
// Create staging buffer
|
|
|
|
|
VkBufferCreateInfo bufInfo{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
|
|
|
|
|
bufInfo.size = bufSize;
|
|
|
|
|
bufInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
|
|
|
|
|
|
|
|
|
|
VmaAllocationCreateInfo allocCI{};
|
|
|
|
|
allocCI.usage = VMA_MEMORY_USAGE_CPU_ONLY;
|
|
|
|
|
|
|
|
|
|
VkBuffer stagingBuf = VK_NULL_HANDLE;
|
|
|
|
|
VmaAllocation stagingAlloc = VK_NULL_HANDLE;
|
|
|
|
|
if (vmaCreateBuffer(alloc, &bufInfo, &allocCI, &stagingBuf, &stagingAlloc, nullptr) != VK_SUCCESS) {
|
|
|
|
|
LOG_WARNING("Screenshot: failed to create staging buffer");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Record copy commands
|
|
|
|
|
VkCommandBuffer cmd = vkCtx->beginSingleTimeCommands();
|
|
|
|
|
|
|
|
|
|
// Transition swapchain image: PRESENT_SRC → TRANSFER_SRC
|
|
|
|
|
VkImageMemoryBarrier toTransfer{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
|
|
|
|
|
toTransfer.srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
|
|
|
|
|
toTransfer.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
|
|
|
|
toTransfer.oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
|
|
|
|
toTransfer.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
|
|
|
|
toTransfer.image = srcImage;
|
|
|
|
|
toTransfer.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
|
|
|
|
|
vkCmdPipelineBarrier(cmd,
|
|
|
|
|
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
|
|
|
|
|
0, 0, nullptr, 0, nullptr, 1, &toTransfer);
|
|
|
|
|
|
|
|
|
|
// Copy image to buffer
|
|
|
|
|
VkBufferImageCopy region{};
|
|
|
|
|
region.imageSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1};
|
|
|
|
|
region.imageExtent = {w, h, 1};
|
|
|
|
|
vkCmdCopyImageToBuffer(cmd, srcImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
|
|
|
|
|
stagingBuf, 1, ®ion);
|
|
|
|
|
|
|
|
|
|
// Transition back: TRANSFER_SRC → PRESENT_SRC
|
|
|
|
|
VkImageMemoryBarrier toPresent = toTransfer;
|
|
|
|
|
toPresent.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
|
|
|
|
toPresent.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
|
|
|
|
|
toPresent.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
|
|
|
|
toPresent.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
|
|
|
|
vkCmdPipelineBarrier(cmd,
|
|
|
|
|
VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
|
|
|
|
|
0, 0, nullptr, 0, nullptr, 1, &toPresent);
|
|
|
|
|
|
|
|
|
|
vkCtx->endSingleTimeCommands(cmd);
|
|
|
|
|
|
|
|
|
|
// Map and convert BGRA → RGBA
|
|
|
|
|
void* mapped = nullptr;
|
|
|
|
|
vmaMapMemory(alloc, stagingAlloc, &mapped);
|
|
|
|
|
auto* pixels = static_cast<uint8_t*>(mapped);
|
|
|
|
|
for (uint32_t i = 0; i < w * h; ++i) {
|
|
|
|
|
std::swap(pixels[i * 4 + 0], pixels[i * 4 + 2]); // B ↔ R
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ensure output directory exists
|
|
|
|
|
std::filesystem::path outPath(outputPath);
|
|
|
|
|
if (outPath.has_parent_path())
|
|
|
|
|
std::filesystem::create_directories(outPath.parent_path());
|
|
|
|
|
|
|
|
|
|
int ok = stbi_write_png(outputPath.c_str(),
|
|
|
|
|
static_cast<int>(w), static_cast<int>(h),
|
|
|
|
|
4, pixels, static_cast<int>(w * 4));
|
|
|
|
|
|
|
|
|
|
vmaUnmapMemory(alloc, stagingAlloc);
|
|
|
|
|
vmaDestroyBuffer(alloc, stagingBuf, stagingAlloc);
|
|
|
|
|
|
|
|
|
|
if (ok) {
|
|
|
|
|
LOG_INFO("Screenshot saved: ", outputPath);
|
|
|
|
|
} else {
|
|
|
|
|
LOG_WARNING("Screenshot: stbi_write_png failed for ", outputPath);
|
|
|
|
|
}
|
|
|
|
|
return ok != 0;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-19 20:36:25 -08:00
|
|
|
void Renderer::triggerLevelUpEffect(const glm::vec3& position) {
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
if (animationController_) animationController_->triggerLevelUpEffect(position);
|
2026-02-19 20:36:25 -08:00
|
|
|
}
|
|
|
|
|
|
2026-02-19 21:13:13 -08:00
|
|
|
void Renderer::startChargeEffect(const glm::vec3& position, const glm::vec3& direction) {
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
if (animationController_) animationController_->startChargeEffect(position, direction);
|
2026-02-19 21:13:13 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Renderer::emitChargeEffect(const glm::vec3& position, const glm::vec3& direction) {
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
if (animationController_) animationController_->emitChargeEffect(position, direction);
|
2026-02-19 21:13:13 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Renderer::stopChargeEffect() {
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
if (animationController_) animationController_->stopChargeEffect();
|
2026-02-19 21:13:13 -08:00
|
|
|
}
|
|
|
|
|
|
2026-04-02 00:21:21 +03:00
|
|
|
// ─── Spell Visual Effects — delegated to SpellVisualSystem (§4.4) ────────────
|
2026-03-17 18:23:05 -07:00
|
|
|
|
2026-03-17 18:30:11 -07:00
|
|
|
void Renderer::playSpellVisual(uint32_t visualId, const glm::vec3& worldPosition,
|
|
|
|
|
bool useImpactKit) {
|
2026-04-02 00:21:21 +03:00
|
|
|
if (spellVisualSystem_) spellVisualSystem_->playSpellVisual(visualId, worldPosition, useImpactKit);
|
2026-03-17 18:23:05 -07:00
|
|
|
}
|
|
|
|
|
|
2026-02-05 14:01:26 -08:00
|
|
|
void Renderer::triggerMeleeSwing() {
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
if (animationController_) animationController_->triggerMeleeSwing();
|
2026-02-05 14:01:26 -08:00
|
|
|
}
|
|
|
|
|
|
2026-02-07 20:02:14 -08:00
|
|
|
std::string Renderer::getEmoteText(const std::string& emoteName, const std::string* targetName) {
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
return AnimationController::getEmoteText(emoteName, targetName);
|
2026-02-02 12:24:50 -08:00
|
|
|
}
|
|
|
|
|
|
2026-02-14 14:30:09 -08:00
|
|
|
uint32_t Renderer::getEmoteDbcId(const std::string& emoteName) {
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
return AnimationController::getEmoteDbcId(emoteName);
|
2026-02-14 14:30:09 -08:00
|
|
|
}
|
|
|
|
|
|
2026-02-14 15:11:43 -08:00
|
|
|
std::string Renderer::getEmoteTextByDbcId(uint32_t dbcId, const std::string& senderName,
|
|
|
|
|
const std::string* targetName) {
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
return AnimationController::getEmoteTextByDbcId(dbcId, senderName, targetName);
|
2026-02-14 15:11:43 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint32_t Renderer::getEmoteAnimByDbcId(uint32_t dbcId) {
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
return AnimationController::getEmoteAnimByDbcId(dbcId);
|
2026-02-14 15:11:43 -08:00
|
|
|
}
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
void Renderer::setTargetPosition(const glm::vec3* pos) {
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
if (animationController_) animationController_->setTargetPosition(pos);
|
2026-02-02 12:24:50 -08:00
|
|
|
}
|
|
|
|
|
|
2026-03-14 09:19:16 -07:00
|
|
|
void Renderer::resetCombatVisualState() {
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
if (animationController_) animationController_->resetCombatVisualState();
|
2026-04-02 00:21:21 +03:00
|
|
|
if (spellVisualSystem_) spellVisualSystem_->reset();
|
2026-03-14 09:19:16 -07:00
|
|
|
}
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
bool Renderer::isMoving() const {
|
|
|
|
|
return cameraController && cameraController->isMoving();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Renderer::update(float deltaTime) {
|
2026-04-03 09:41:34 +03:00
|
|
|
ZoneScopedN("Renderer::update");
|
2026-02-21 19:41:21 -08:00
|
|
|
globalTime += deltaTime;
|
2026-02-11 22:27:02 -08:00
|
|
|
if (musicSwitchCooldown_ > 0.0f) {
|
|
|
|
|
musicSwitchCooldown_ = std::max(0.0f, musicSwitchCooldown_ - deltaTime);
|
|
|
|
|
}
|
2026-02-20 20:31:04 -08:00
|
|
|
runDeferredWorldInitStep(deltaTime);
|
2026-02-11 22:27:02 -08:00
|
|
|
|
2026-02-03 16:21:48 -08:00
|
|
|
auto updateStart = std::chrono::steady_clock::now();
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
lastDeltaTime_ = deltaTime;
|
2026-02-10 19:30:45 -08:00
|
|
|
|
2026-02-03 16:21:48 -08:00
|
|
|
if (wmoRenderer) wmoRenderer->resetQueryStats();
|
|
|
|
|
if (m2Renderer) m2Renderer->resetQueryStats();
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
if (cameraController) {
|
2026-02-03 16:21:48 -08:00
|
|
|
auto cameraStart = std::chrono::steady_clock::now();
|
2026-02-02 12:24:50 -08:00
|
|
|
cameraController->update(deltaTime);
|
2026-02-03 16:21:48 -08:00
|
|
|
auto cameraEnd = std::chrono::steady_clock::now();
|
|
|
|
|
lastCameraUpdateMs = std::chrono::duration<double, std::milli>(cameraEnd - cameraStart).count();
|
2026-03-07 18:43:13 -08:00
|
|
|
if (lastCameraUpdateMs > 50.0) {
|
2026-03-07 15:46:56 -08:00
|
|
|
LOG_WARNING("SLOW cameraController->update: ", lastCameraUpdateMs, "ms");
|
|
|
|
|
}
|
2026-02-09 00:40:50 -08:00
|
|
|
|
|
|
|
|
// Update 3D audio listener position/orientation to match camera
|
|
|
|
|
if (camera) {
|
|
|
|
|
audio::AudioEngine::instance().setListenerPosition(camera->getPosition());
|
|
|
|
|
audio::AudioEngine::instance().setListenerOrientation(camera->getForward(), camera->getUp());
|
|
|
|
|
}
|
2026-02-03 16:21:48 -08:00
|
|
|
} else {
|
|
|
|
|
lastCameraUpdateMs = 0.0;
|
2026-02-02 12:24:50 -08:00
|
|
|
}
|
|
|
|
|
|
2026-02-11 22:27:02 -08:00
|
|
|
// Visibility hardening: ensure player instance cannot stay hidden after
|
|
|
|
|
// taxi/camera transitions, but preserve first-person self-hide.
|
|
|
|
|
if (characterRenderer && characterInstanceId > 0 && cameraController) {
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
if ((cameraController->isThirdPerson() && !cameraController->isFirstPersonView()) || (animationController_ && animationController_->isTaxiFlight())) {
|
2026-02-11 22:27:02 -08:00
|
|
|
characterRenderer->setInstanceVisible(characterInstanceId, true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-10 13:48:50 -08:00
|
|
|
// Update lighting system
|
|
|
|
|
if (lightingManager) {
|
2026-02-17 17:59:41 -08:00
|
|
|
const auto* gh = core::Application::getInstance().getGameHandler();
|
|
|
|
|
uint32_t mapId = gh ? gh->getCurrentMapId() : 0;
|
|
|
|
|
float gameTime = gh ? gh->getGameTime() : -1.0f;
|
|
|
|
|
bool isRaining = gh ? gh->isRaining() : false;
|
|
|
|
|
bool isUnderwater = cameraController ? cameraController->isSwimming() : false;
|
2026-02-10 13:48:50 -08:00
|
|
|
|
|
|
|
|
lightingManager->update(characterPosition, mapId, gameTime, isRaining, isUnderwater);
|
2026-02-17 17:59:41 -08:00
|
|
|
|
|
|
|
|
// Sync weather visual renderer with game state
|
|
|
|
|
if (weather && gh) {
|
|
|
|
|
uint32_t wType = gh->getWeatherType();
|
|
|
|
|
float wInt = gh->getWeatherIntensity();
|
2026-02-22 23:20:13 -08:00
|
|
|
if (wType != 0) {
|
|
|
|
|
// Server-driven weather (SMSG_WEATHER) — authoritative
|
|
|
|
|
if (wType == 1) weather->setWeatherType(Weather::Type::RAIN);
|
|
|
|
|
else if (wType == 2) weather->setWeatherType(Weather::Type::SNOW);
|
2026-03-20 15:56:58 -07:00
|
|
|
else if (wType == 3) weather->setWeatherType(Weather::Type::STORM);
|
2026-02-22 23:20:13 -08:00
|
|
|
else weather->setWeatherType(Weather::Type::NONE);
|
|
|
|
|
weather->setIntensity(wInt);
|
|
|
|
|
} else {
|
|
|
|
|
// No server weather — use zone-based weather configuration
|
|
|
|
|
weather->updateZoneWeather(currentZoneId, deltaTime);
|
|
|
|
|
}
|
|
|
|
|
weather->setEnabled(true);
|
2026-03-13 09:52:23 -07:00
|
|
|
|
2026-03-16 16:46:29 -07:00
|
|
|
// Lightning flash disabled
|
2026-03-13 09:52:23 -07:00
|
|
|
if (lightning) {
|
2026-03-16 16:46:29 -07:00
|
|
|
lightning->setEnabled(false);
|
2026-03-13 09:52:23 -07:00
|
|
|
}
|
2026-02-22 23:20:13 -08:00
|
|
|
} else if (weather) {
|
|
|
|
|
// No game handler (single-player without network) — zone weather only
|
|
|
|
|
weather->updateZoneWeather(currentZoneId, deltaTime);
|
|
|
|
|
weather->setEnabled(true);
|
2026-02-17 17:59:41 -08:00
|
|
|
}
|
2026-02-10 13:48:50 -08:00
|
|
|
}
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
// Sync character model position/rotation and animation with follow target
|
2026-02-21 02:28:47 -08:00
|
|
|
if (characterInstanceId > 0 && characterRenderer && cameraController) {
|
2026-02-02 12:24:50 -08:00
|
|
|
characterRenderer->setInstancePosition(characterInstanceId, characterPosition);
|
|
|
|
|
|
2026-02-03 19:49:56 -08:00
|
|
|
// Movement-facing comes from camera controller and is decoupled from LMB orbit.
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
bool taxiFlight = animationController_ && animationController_->isTaxiFlight();
|
|
|
|
|
if (taxiFlight) {
|
2026-02-08 22:00:33 -08:00
|
|
|
characterYaw = cameraController->getFacingYaw();
|
|
|
|
|
} else if (cameraController->isMoving() || cameraController->isRightMouseHeld()) {
|
2026-02-03 19:49:56 -08:00
|
|
|
characterYaw = cameraController->getFacingYaw();
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
} else if (animationController_ && animationController_->isInCombat() &&
|
|
|
|
|
animationController_->getTargetPosition() && !animationController_->isEmoteActive() && !isMounted()) {
|
|
|
|
|
glm::vec3 toTarget = *animationController_->getTargetPosition() - characterPosition;
|
2026-03-27 16:33:16 -07:00
|
|
|
if (toTarget.x * toTarget.x + toTarget.y * toTarget.y > 0.01f) {
|
2026-02-02 12:24:50 -08:00
|
|
|
float targetYaw = glm::degrees(std::atan2(toTarget.y, toTarget.x));
|
|
|
|
|
float diff = targetYaw - characterYaw;
|
|
|
|
|
while (diff > 180.0f) diff -= 360.0f;
|
|
|
|
|
while (diff < -180.0f) diff += 360.0f;
|
|
|
|
|
float rotSpeed = 360.0f * deltaTime;
|
|
|
|
|
if (std::abs(diff) < rotSpeed) {
|
|
|
|
|
characterYaw = targetYaw;
|
|
|
|
|
} else {
|
|
|
|
|
characterYaw += (diff > 0 ? rotSpeed : -rotSpeed);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
float yawRad = glm::radians(characterYaw);
|
|
|
|
|
characterRenderer->setInstanceRotation(characterInstanceId, glm::vec3(0.0f, 0.0f, yawRad));
|
|
|
|
|
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
// Update animation based on movement state (delegated to AnimationController §4.2)
|
|
|
|
|
if (animationController_) {
|
|
|
|
|
animationController_->updateMeleeTimers(deltaTime);
|
|
|
|
|
animationController_->setDeltaTime(deltaTime);
|
|
|
|
|
animationController_->updateCharacterAnimation();
|
|
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update terrain streaming
|
|
|
|
|
if (terrainManager && camera) {
|
2026-03-07 13:44:09 -08:00
|
|
|
auto terrStart = std::chrono::steady_clock::now();
|
2026-02-02 12:24:50 -08:00
|
|
|
terrainManager->update(*camera, deltaTime);
|
2026-03-07 13:44:09 -08:00
|
|
|
float terrMs = std::chrono::duration<float, std::milli>(
|
|
|
|
|
std::chrono::steady_clock::now() - terrStart).count();
|
2026-03-07 18:43:13 -08:00
|
|
|
if (terrMs > 50.0f) {
|
2026-03-07 13:44:09 -08:00
|
|
|
LOG_WARNING("SLOW terrainManager->update: ", terrMs, "ms");
|
|
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
}
|
|
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
// Update sky system (skybox time, star twinkle, clouds, celestial moon phases)
|
|
|
|
|
if (skySystem) {
|
|
|
|
|
skySystem->update(deltaTime);
|
2026-02-02 12:24:50 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update weather particles
|
|
|
|
|
if (weather && camera) {
|
|
|
|
|
weather->update(*camera, deltaTime);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-13 09:52:23 -07:00
|
|
|
// Update lightning (storm / heavy rain)
|
|
|
|
|
if (lightning && camera && lightning->isEnabled()) {
|
|
|
|
|
lightning->update(deltaTime, *camera);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
// Update swim effects
|
|
|
|
|
if (swimEffects && camera && cameraController && waterRenderer) {
|
|
|
|
|
swimEffects->update(*camera, *cameraController, *waterRenderer, deltaTime);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-09 01:24:17 -08:00
|
|
|
// Update mount dust effects
|
|
|
|
|
if (mountDust) {
|
|
|
|
|
mountDust->update(deltaTime);
|
|
|
|
|
|
|
|
|
|
// Spawn dust when mounted and moving on ground
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
if (isMounted() && camera && cameraController && !(animationController_ && animationController_->isTaxiFlight())) {
|
2026-02-09 01:24:17 -08:00
|
|
|
bool isMoving = cameraController->isMoving();
|
|
|
|
|
bool onGround = cameraController->isGrounded();
|
|
|
|
|
|
|
|
|
|
if (isMoving && onGround) {
|
|
|
|
|
// Calculate velocity from camera direction and speed
|
|
|
|
|
glm::vec3 forward = camera->getForward();
|
|
|
|
|
float speed = cameraController->getMovementSpeed();
|
|
|
|
|
glm::vec3 velocity = forward * speed;
|
|
|
|
|
velocity.z = 0.0f; // Ignore vertical component
|
|
|
|
|
|
|
|
|
|
// Spawn dust at mount's feet (slightly below character position)
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
float mho = animationController_ ? animationController_->getMountHeightOffset() : 0.0f;
|
|
|
|
|
glm::vec3 dustPos = characterPosition - glm::vec3(0.0f, 0.0f, mho * 0.8f);
|
2026-02-09 01:24:17 -08:00
|
|
|
mountDust->spawnDust(dustPos, velocity, isMoving);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-19 20:36:25 -08:00
|
|
|
// Update level-up effect
|
|
|
|
|
if (levelUpEffect) {
|
|
|
|
|
levelUpEffect->update(deltaTime);
|
|
|
|
|
}
|
2026-02-19 21:13:13 -08:00
|
|
|
// Update charge effect
|
|
|
|
|
if (chargeEffect) {
|
|
|
|
|
chargeEffect->update(deltaTime);
|
|
|
|
|
}
|
2026-04-02 00:21:21 +03:00
|
|
|
// Update transient spell visual instances (delegated to SpellVisualSystem §4.4)
|
|
|
|
|
if (spellVisualSystem_) spellVisualSystem_->update(deltaTime);
|
2026-02-19 20:36:25 -08:00
|
|
|
|
2026-02-09 01:24:17 -08:00
|
|
|
|
2026-03-07 22:29:06 -08:00
|
|
|
// Launch M2 doodad animation on background thread (overlaps with character animation + audio)
|
|
|
|
|
std::future<void> m2AnimFuture;
|
|
|
|
|
bool m2AnimLaunched = false;
|
|
|
|
|
if (m2Renderer && camera) {
|
|
|
|
|
float m2DeltaTime = deltaTime;
|
|
|
|
|
glm::vec3 m2CamPos = camera->getPosition();
|
|
|
|
|
glm::mat4 m2ViewProj = camera->getProjectionMatrix() * camera->getViewMatrix();
|
|
|
|
|
m2AnimFuture = std::async(std::launch::async,
|
|
|
|
|
[this, m2DeltaTime, m2CamPos, m2ViewProj]() {
|
|
|
|
|
m2Renderer->update(m2DeltaTime, m2CamPos, m2ViewProj);
|
|
|
|
|
});
|
|
|
|
|
m2AnimLaunched = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update character animations (runs in parallel with M2 animation above)
|
2026-02-10 19:30:45 -08:00
|
|
|
if (characterRenderer && camera) {
|
|
|
|
|
characterRenderer->update(deltaTime, camera->getPosition());
|
2026-02-02 12:24:50 -08:00
|
|
|
}
|
|
|
|
|
|
2026-02-09 00:40:50 -08:00
|
|
|
// Update AudioEngine (cleanup finished sounds, etc.)
|
|
|
|
|
audio::AudioEngine::instance().update(deltaTime);
|
|
|
|
|
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
// Footsteps: delegated to AnimationController (§4.2)
|
|
|
|
|
if (animationController_) animationController_->updateFootsteps(deltaTime);
|
2026-02-03 19:49:56 -08:00
|
|
|
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
// Activity SFX + mount ambient sounds: delegated to AnimationController (§4.2)
|
|
|
|
|
if (animationController_) animationController_->updateSfxState(deltaTime);
|
2026-02-09 01:19:35 -08:00
|
|
|
|
2026-02-25 10:22:05 -08:00
|
|
|
const bool canQueryWmo = (camera && wmoRenderer);
|
|
|
|
|
const glm::vec3 camPos = camera ? camera->getPosition() : glm::vec3(0.0f);
|
|
|
|
|
uint32_t insideWmoId = 0;
|
|
|
|
|
const bool insideWmo = canQueryWmo &&
|
|
|
|
|
wmoRenderer->isInsideWMO(camPos.x, camPos.y, camPos.z, &insideWmoId);
|
2026-03-20 06:29:33 -07:00
|
|
|
playerIndoors_ = insideWmo;
|
2026-02-25 10:22:05 -08:00
|
|
|
|
2026-02-09 14:50:14 -08:00
|
|
|
// Ambient environmental sounds: fireplaces, water, birds, etc.
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
if (audioCoordinator_->getAmbientSoundManager() && camera && wmoRenderer && cameraController) {
|
2026-02-25 10:22:05 -08:00
|
|
|
bool isIndoor = insideWmo;
|
2026-02-09 14:50:14 -08:00
|
|
|
bool isSwimming = cameraController->isSwimming();
|
|
|
|
|
|
2026-03-30 14:10:32 -07:00
|
|
|
// Detect blacksmith buildings to play ambient forge/anvil sounds.
|
|
|
|
|
// 96048 is the WMO group ID for the Goldshire blacksmith interior.
|
|
|
|
|
// TODO: extend to other smithy WMO IDs (Ironforge, Orgrimmar, etc.)
|
2026-02-25 10:22:05 -08:00
|
|
|
bool isBlacksmith = (insideWmoId == 96048);
|
2026-02-09 15:21:07 -08:00
|
|
|
|
Add comprehensive weather, water, and zone ambient audio systems
Implemented three new ambient audio systems with automatic day/night transitions:
Weather ambience:
- Rain sounds (light/medium/heavy intensity based on weather system)
- Snow sounds (light/medium/heavy intensity)
- Automatically syncs with visual weather system in renderer
- Different loop intervals based on intensity (18-30s)
- Disabled indoors
Water ambience:
- Underwater swimming sounds (18s loop)
- Ocean surface sounds
- State tracking for entering/exiting water
Zone ambience:
- Forest (normal and snow variants)
- Beach sounds
- Grasslands
- Jungle
- Marsh/swamp
- Desert (canyon and plains variants)
- All zones have separate day/night sound files
- 30s loop interval for subtle background atmosphere
- Disabled indoors
Technical details:
- Added WeatherType enum (NONE, RAIN/SNOW LIGHT/MEDIUM/HEAVY)
- Added ZoneType enum (NONE, FOREST_NORMAL, FOREST_SNOW, BEACH, GRASSLANDS, JUNGLE, MARSH, DESERT_CANYON, DESERT_PLAINS)
- Loads 26 new sound files from Sound\Ambience\Weather and Sound\Ambience\ZoneAmbience
- Weather intensity thresholds: <0.33 = light, 0.33-0.66 = medium, >0.66 = heavy
- Renderer automatically converts Weather::Type + intensity to AmbientSoundManager::WeatherType
- All ambience respects volumeScale_ and indoor state
- State change logging for debugging transitions
2026-02-09 16:12:06 -08:00
|
|
|
// Sync weather audio with visual weather system
|
|
|
|
|
if (weather) {
|
|
|
|
|
auto weatherType = weather->getWeatherType();
|
|
|
|
|
float intensity = weather->getIntensity();
|
|
|
|
|
|
|
|
|
|
audio::AmbientSoundManager::WeatherType audioWeatherType = audio::AmbientSoundManager::WeatherType::NONE;
|
|
|
|
|
|
|
|
|
|
if (weatherType == Weather::Type::RAIN) {
|
|
|
|
|
if (intensity < 0.33f) {
|
|
|
|
|
audioWeatherType = audio::AmbientSoundManager::WeatherType::RAIN_LIGHT;
|
|
|
|
|
} else if (intensity < 0.66f) {
|
|
|
|
|
audioWeatherType = audio::AmbientSoundManager::WeatherType::RAIN_MEDIUM;
|
|
|
|
|
} else {
|
|
|
|
|
audioWeatherType = audio::AmbientSoundManager::WeatherType::RAIN_HEAVY;
|
|
|
|
|
}
|
|
|
|
|
} else if (weatherType == Weather::Type::SNOW) {
|
|
|
|
|
if (intensity < 0.33f) {
|
|
|
|
|
audioWeatherType = audio::AmbientSoundManager::WeatherType::SNOW_LIGHT;
|
|
|
|
|
} else if (intensity < 0.66f) {
|
|
|
|
|
audioWeatherType = audio::AmbientSoundManager::WeatherType::SNOW_MEDIUM;
|
|
|
|
|
} else {
|
|
|
|
|
audioWeatherType = audio::AmbientSoundManager::WeatherType::SNOW_HEAVY;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
audioCoordinator_->getAmbientSoundManager()->setWeather(audioWeatherType);
|
Add comprehensive weather, water, and zone ambient audio systems
Implemented three new ambient audio systems with automatic day/night transitions:
Weather ambience:
- Rain sounds (light/medium/heavy intensity based on weather system)
- Snow sounds (light/medium/heavy intensity)
- Automatically syncs with visual weather system in renderer
- Different loop intervals based on intensity (18-30s)
- Disabled indoors
Water ambience:
- Underwater swimming sounds (18s loop)
- Ocean surface sounds
- State tracking for entering/exiting water
Zone ambience:
- Forest (normal and snow variants)
- Beach sounds
- Grasslands
- Jungle
- Marsh/swamp
- Desert (canyon and plains variants)
- All zones have separate day/night sound files
- 30s loop interval for subtle background atmosphere
- Disabled indoors
Technical details:
- Added WeatherType enum (NONE, RAIN/SNOW LIGHT/MEDIUM/HEAVY)
- Added ZoneType enum (NONE, FOREST_NORMAL, FOREST_SNOW, BEACH, GRASSLANDS, JUNGLE, MARSH, DESERT_CANYON, DESERT_PLAINS)
- Loads 26 new sound files from Sound\Ambience\Weather and Sound\Ambience\ZoneAmbience
- Weather intensity thresholds: <0.33 = light, 0.33-0.66 = medium, >0.66 = heavy
- Renderer automatically converts Weather::Type + intensity to AmbientSoundManager::WeatherType
- All ambience respects volumeScale_ and indoor state
- State change logging for debugging transitions
2026-02-09 16:12:06 -08:00
|
|
|
}
|
|
|
|
|
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
audioCoordinator_->getAmbientSoundManager()->update(deltaTime, camPos, isIndoor, isSwimming, isBlacksmith);
|
2026-02-09 14:50:14 -08:00
|
|
|
}
|
|
|
|
|
|
2026-03-07 22:29:06 -08:00
|
|
|
// Wait for M2 doodad animation to finish (was launched earlier in parallel with character anim)
|
|
|
|
|
if (m2AnimLaunched) {
|
2026-03-29 21:26:01 -07:00
|
|
|
try { m2AnimFuture.get(); }
|
|
|
|
|
catch (const std::exception& e) { LOG_ERROR("M2 animation worker: ", e.what()); }
|
2026-02-02 23:10:19 -08:00
|
|
|
}
|
|
|
|
|
|
Add original music to login rotation and zone playlists
11 original tracks in assets/Original Music/ now play on the login
screen and in thematically matched zones across Eastern Kingdoms and
Kalimdor. Added crossfadeToFile to MusicManager for local file
playback during zone transitions. New zones: Tirisfal, Undercity,
Barrens, STV, Duskwood, Burning Steppes, Searing Gorge, Ironforge,
Loch Modan, Orgrimmar, Durotar, Mulgore, Thunder Bluff, Darkshore,
Teldrassil, Darnassus.
2026-02-15 05:53:27 -08:00
|
|
|
// Helper: play zone music, dispatching local files (file: prefix) vs MPQ paths
|
|
|
|
|
auto playZoneMusic = [&](const std::string& music) {
|
|
|
|
|
if (music.empty()) return;
|
|
|
|
|
if (music.rfind("file:", 0) == 0) {
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
audioCoordinator_->getMusicManager()->crossfadeToFile(music.substr(5));
|
Add original music to login rotation and zone playlists
11 original tracks in assets/Original Music/ now play on the login
screen and in thematically matched zones across Eastern Kingdoms and
Kalimdor. Added crossfadeToFile to MusicManager for local file
playback during zone transitions. New zones: Tirisfal, Undercity,
Barrens, STV, Duskwood, Burning Steppes, Searing Gorge, Ironforge,
Loch Modan, Orgrimmar, Durotar, Mulgore, Thunder Bluff, Darkshore,
Teldrassil, Darnassus.
2026-02-15 05:53:27 -08:00
|
|
|
} else {
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
audioCoordinator_->getMusicManager()->crossfadeTo(music);
|
Add original music to login rotation and zone playlists
11 original tracks in assets/Original Music/ now play on the login
screen and in thematically matched zones across Eastern Kingdoms and
Kalimdor. Added crossfadeToFile to MusicManager for local file
playback during zone transitions. New zones: Tirisfal, Undercity,
Barrens, STV, Duskwood, Burning Steppes, Searing Gorge, Ironforge,
Loch Modan, Orgrimmar, Durotar, Mulgore, Thunder Bluff, Darkshore,
Teldrassil, Darnassus.
2026-02-15 05:53:27 -08:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
// Update zone detection and music
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
if (zoneManager && audioCoordinator_->getMusicManager() && terrainManager && camera) {
|
2026-03-09 16:19:38 -07:00
|
|
|
// Prefer server-authoritative zone ID (from SMSG_INIT_WORLD_STATES);
|
|
|
|
|
// fall back to tile-based lookup for single-player / offline mode.
|
|
|
|
|
const auto* gh = core::Application::getInstance().getGameHandler();
|
|
|
|
|
uint32_t serverZoneId = gh ? gh->getWorldStateZoneId() : 0;
|
2026-02-02 12:24:50 -08:00
|
|
|
auto tile = terrainManager->getCurrentTile();
|
2026-03-09 16:19:38 -07:00
|
|
|
uint32_t zoneId = (serverZoneId != 0) ? serverZoneId : zoneManager->getZoneId(tile.x, tile.y);
|
2026-02-02 12:24:50 -08:00
|
|
|
|
2026-02-09 01:39:12 -08:00
|
|
|
bool insideTavern = false;
|
2026-02-09 15:21:07 -08:00
|
|
|
bool insideBlacksmith = false;
|
2026-02-09 01:39:12 -08:00
|
|
|
std::string tavernMusic;
|
2026-02-02 12:24:50 -08:00
|
|
|
|
2026-02-09 15:21:07 -08:00
|
|
|
// Override with WMO-based detection (e.g., inside Stormwind, taverns, blacksmiths)
|
2026-02-02 12:24:50 -08:00
|
|
|
if (wmoRenderer) {
|
2026-02-25 10:22:05 -08:00
|
|
|
uint32_t wmoModelId = insideWmoId;
|
|
|
|
|
if (insideWmo) {
|
2026-02-02 12:24:50 -08:00
|
|
|
// Check if inside Stormwind WMO (model ID 10047)
|
|
|
|
|
if (wmoModelId == 10047) {
|
|
|
|
|
zoneId = 1519; // Stormwind City
|
|
|
|
|
}
|
2026-02-09 01:39:12 -08:00
|
|
|
|
2026-02-09 15:21:07 -08:00
|
|
|
// Detect taverns/inns/blacksmiths by WMO model ID
|
2026-02-09 01:43:20 -08:00
|
|
|
// Log WMO ID for debugging
|
|
|
|
|
static uint32_t lastLoggedWmoId = 0;
|
|
|
|
|
if (wmoModelId != lastLoggedWmoId) {
|
|
|
|
|
LOG_INFO("Inside WMO model ID: ", wmoModelId);
|
|
|
|
|
lastLoggedWmoId = wmoModelId;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-30 14:10:32 -07:00
|
|
|
// Detect blacksmith WMO for ambient forge sounds
|
|
|
|
|
if (wmoModelId == 96048) { // Goldshire blacksmith interior
|
2026-02-09 15:21:07 -08:00
|
|
|
insideBlacksmith = true;
|
|
|
|
|
LOG_INFO("Detected blacksmith WMO ", wmoModelId);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-09 01:39:12 -08:00
|
|
|
// These IDs represent typical Alliance and Horde inn buildings
|
2026-02-09 15:21:07 -08:00
|
|
|
if (wmoModelId == 191 || // Goldshire inn (old ID)
|
|
|
|
|
wmoModelId == 71414 || // Goldshire inn (actual)
|
2026-02-09 01:39:12 -08:00
|
|
|
wmoModelId == 190 || // Small inn (common)
|
|
|
|
|
wmoModelId == 220 || // Tavern building
|
|
|
|
|
wmoModelId == 221 || // Large tavern
|
|
|
|
|
wmoModelId == 5392 || // Horde inn
|
|
|
|
|
wmoModelId == 5393) { // Another inn variant
|
|
|
|
|
insideTavern = true;
|
2026-02-09 15:21:07 -08:00
|
|
|
// WoW tavern music (cozy ambient tracks) - FIXED PATHS
|
2026-02-09 01:39:12 -08:00
|
|
|
static const std::vector<std::string> tavernTracks = {
|
2026-02-09 15:21:07 -08:00
|
|
|
"Sound\\Music\\ZoneMusic\\TavernAlliance\\TavernAlliance01.mp3",
|
|
|
|
|
"Sound\\Music\\ZoneMusic\\TavernAlliance\\TavernAlliance02.mp3",
|
|
|
|
|
"Sound\\Music\\ZoneMusic\\TavernHuman\\RA_HumanTavern1A.mp3",
|
|
|
|
|
"Sound\\Music\\ZoneMusic\\TavernHuman\\RA_HumanTavern2A.mp3",
|
2026-02-09 01:39:12 -08:00
|
|
|
};
|
2026-03-29 20:27:08 -07:00
|
|
|
// Rotate through tracks so the player doesn't always hear the same one.
|
|
|
|
|
// Post-increment: first visit plays index 0, next plays 1, etc.
|
2026-02-09 01:39:12 -08:00
|
|
|
static int tavernTrackIndex = 0;
|
2026-03-29 20:27:08 -07:00
|
|
|
tavernMusic = tavernTracks[tavernTrackIndex++ % tavernTracks.size()];
|
2026-02-09 15:21:07 -08:00
|
|
|
LOG_INFO("Detected tavern WMO ", wmoModelId, ", playing: ", tavernMusic);
|
2026-02-09 01:39:12 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle tavern music transitions
|
|
|
|
|
if (insideTavern) {
|
|
|
|
|
if (!inTavern_ && !tavernMusic.empty()) {
|
|
|
|
|
inTavern_ = true;
|
|
|
|
|
LOG_INFO("Entered tavern");
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
audioCoordinator_->getMusicManager()->playMusic(tavernMusic, true); // Immediate playback, looping
|
2026-02-11 22:27:02 -08:00
|
|
|
musicSwitchCooldown_ = 6.0f;
|
2026-02-09 01:39:12 -08:00
|
|
|
}
|
|
|
|
|
} else if (inTavern_) {
|
2026-02-09 15:21:07 -08:00
|
|
|
// Exited tavern - restore zone music with crossfade
|
2026-02-09 01:39:12 -08:00
|
|
|
inTavern_ = false;
|
|
|
|
|
LOG_INFO("Exited tavern");
|
|
|
|
|
auto* info = zoneManager->getZoneInfo(currentZoneId);
|
|
|
|
|
if (info) {
|
|
|
|
|
std::string music = zoneManager->getRandomMusic(currentZoneId);
|
|
|
|
|
if (!music.empty()) {
|
Add original music to login rotation and zone playlists
11 original tracks in assets/Original Music/ now play on the login
screen and in thematically matched zones across Eastern Kingdoms and
Kalimdor. Added crossfadeToFile to MusicManager for local file
playback during zone transitions. New zones: Tirisfal, Undercity,
Barrens, STV, Duskwood, Burning Steppes, Searing Gorge, Ironforge,
Loch Modan, Orgrimmar, Durotar, Mulgore, Thunder Bluff, Darkshore,
Teldrassil, Darnassus.
2026-02-15 05:53:27 -08:00
|
|
|
playZoneMusic(music);
|
2026-02-11 22:27:02 -08:00
|
|
|
musicSwitchCooldown_ = 6.0f;
|
2026-02-09 01:39:12 -08:00
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-09 15:21:07 -08:00
|
|
|
// Handle blacksmith music (stop music when entering blacksmith, let ambience play)
|
|
|
|
|
if (insideBlacksmith) {
|
|
|
|
|
if (!inBlacksmith_) {
|
|
|
|
|
inBlacksmith_ = true;
|
|
|
|
|
LOG_INFO("Entered blacksmith - stopping music");
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
audioCoordinator_->getMusicManager()->stopMusic();
|
2026-02-09 15:21:07 -08:00
|
|
|
}
|
|
|
|
|
} else if (inBlacksmith_) {
|
|
|
|
|
// Exited blacksmith - restore zone music with crossfade
|
|
|
|
|
inBlacksmith_ = false;
|
|
|
|
|
LOG_INFO("Exited blacksmith - restoring music");
|
|
|
|
|
auto* info = zoneManager->getZoneInfo(currentZoneId);
|
|
|
|
|
if (info) {
|
|
|
|
|
std::string music = zoneManager->getRandomMusic(currentZoneId);
|
|
|
|
|
if (!music.empty()) {
|
Add original music to login rotation and zone playlists
11 original tracks in assets/Original Music/ now play on the login
screen and in thematically matched zones across Eastern Kingdoms and
Kalimdor. Added crossfadeToFile to MusicManager for local file
playback during zone transitions. New zones: Tirisfal, Undercity,
Barrens, STV, Duskwood, Burning Steppes, Searing Gorge, Ironforge,
Loch Modan, Orgrimmar, Durotar, Mulgore, Thunder Bluff, Darkshore,
Teldrassil, Darnassus.
2026-02-15 05:53:27 -08:00
|
|
|
playZoneMusic(music);
|
2026-02-11 22:27:02 -08:00
|
|
|
musicSwitchCooldown_ = 6.0f;
|
2026-02-09 15:21:07 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle normal zone transitions (only if not in tavern or blacksmith)
|
|
|
|
|
if (!insideTavern && !insideBlacksmith && zoneId != currentZoneId && zoneId != 0) {
|
2026-02-02 12:24:50 -08:00
|
|
|
currentZoneId = zoneId;
|
|
|
|
|
auto* info = zoneManager->getZoneInfo(zoneId);
|
|
|
|
|
if (info) {
|
|
|
|
|
currentZoneName = info->name;
|
|
|
|
|
LOG_INFO("Entered zone: ", info->name);
|
2026-02-11 22:27:02 -08:00
|
|
|
if (musicSwitchCooldown_ <= 0.0f) {
|
|
|
|
|
std::string music = zoneManager->getRandomMusic(zoneId);
|
|
|
|
|
if (!music.empty()) {
|
Add original music to login rotation and zone playlists
11 original tracks in assets/Original Music/ now play on the login
screen and in thematically matched zones across Eastern Kingdoms and
Kalimdor. Added crossfadeToFile to MusicManager for local file
playback during zone transitions. New zones: Tirisfal, Undercity,
Barrens, STV, Duskwood, Burning Steppes, Searing Gorge, Ironforge,
Loch Modan, Orgrimmar, Durotar, Mulgore, Thunder Bluff, Darkshore,
Teldrassil, Darnassus.
2026-02-15 05:53:27 -08:00
|
|
|
playZoneMusic(music);
|
2026-02-11 22:27:02 -08:00
|
|
|
musicSwitchCooldown_ = 6.0f;
|
|
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
}
|
|
|
|
|
}
|
2026-03-09 16:24:12 -07:00
|
|
|
// Update ambient sound manager zone type
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
if (audioCoordinator_->getAmbientSoundManager()) {
|
|
|
|
|
audioCoordinator_->getAmbientSoundManager()->setZoneId(zoneId);
|
2026-03-09 16:24:12 -07:00
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
}
|
|
|
|
|
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
audioCoordinator_->getMusicManager()->update(deltaTime);
|
2026-02-17 05:27:03 -08:00
|
|
|
|
|
|
|
|
// When a track finishes, pick a new random track from the current zone
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
if (!audioCoordinator_->getMusicManager()->isPlaying() && !inTavern_ && !inBlacksmith_ &&
|
2026-02-17 05:27:03 -08:00
|
|
|
currentZoneId != 0 && musicSwitchCooldown_ <= 0.0f) {
|
|
|
|
|
std::string music = zoneManager->getRandomMusic(currentZoneId);
|
|
|
|
|
if (!music.empty()) {
|
|
|
|
|
playZoneMusic(music);
|
|
|
|
|
musicSwitchCooldown_ = 2.0f;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update performance HUD
|
|
|
|
|
if (performanceHUD) {
|
|
|
|
|
performanceHUD->update(deltaTime);
|
|
|
|
|
}
|
2026-02-03 16:21:48 -08:00
|
|
|
|
2026-02-22 07:26:54 -08:00
|
|
|
// Periodic cache hygiene: drop model GPU data no longer referenced by active instances.
|
|
|
|
|
static float modelCleanupTimer = 0.0f;
|
|
|
|
|
modelCleanupTimer += deltaTime;
|
|
|
|
|
if (modelCleanupTimer >= 5.0f) {
|
|
|
|
|
if (wmoRenderer) {
|
|
|
|
|
wmoRenderer->cleanupUnusedModels();
|
|
|
|
|
}
|
|
|
|
|
if (m2Renderer) {
|
|
|
|
|
m2Renderer->cleanupUnusedModels();
|
|
|
|
|
}
|
|
|
|
|
modelCleanupTimer = 0.0f;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-03 16:21:48 -08:00
|
|
|
auto updateEnd = std::chrono::steady_clock::now();
|
|
|
|
|
lastUpdateMs = std::chrono::duration<double, std::milli>(updateEnd - updateStart).count();
|
2026-02-02 12:24:50 -08:00
|
|
|
}
|
|
|
|
|
|
2026-02-20 20:31:04 -08:00
|
|
|
void Renderer::runDeferredWorldInitStep(float deltaTime) {
|
|
|
|
|
if (!deferredWorldInitEnabled_ || !deferredWorldInitPending_ || !cachedAssetManager) return;
|
|
|
|
|
if (deferredWorldInitCooldown_ > 0.0f) {
|
|
|
|
|
deferredWorldInitCooldown_ = std::max(0.0f, deferredWorldInitCooldown_ - deltaTime);
|
|
|
|
|
if (deferredWorldInitCooldown_ > 0.0f) return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (deferredWorldInitStage_) {
|
|
|
|
|
case 0:
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
if (audioCoordinator_->getAmbientSoundManager()) {
|
|
|
|
|
audioCoordinator_->getAmbientSoundManager()->initialize(cachedAssetManager);
|
2026-02-20 20:31:04 -08:00
|
|
|
}
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
if (terrainManager && audioCoordinator_->getAmbientSoundManager()) {
|
|
|
|
|
terrainManager->setAmbientSoundManager(audioCoordinator_->getAmbientSoundManager());
|
2026-02-20 20:31:04 -08:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 1:
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
if (audioCoordinator_->getUiSoundManager()) audioCoordinator_->getUiSoundManager()->initialize(cachedAssetManager);
|
2026-02-20 20:31:04 -08:00
|
|
|
break;
|
|
|
|
|
case 2:
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
if (audioCoordinator_->getCombatSoundManager()) audioCoordinator_->getCombatSoundManager()->initialize(cachedAssetManager);
|
2026-02-20 20:31:04 -08:00
|
|
|
break;
|
|
|
|
|
case 3:
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
if (audioCoordinator_->getSpellSoundManager()) audioCoordinator_->getSpellSoundManager()->initialize(cachedAssetManager);
|
2026-02-20 20:31:04 -08:00
|
|
|
break;
|
|
|
|
|
case 4:
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
if (audioCoordinator_->getMovementSoundManager()) audioCoordinator_->getMovementSoundManager()->initialize(cachedAssetManager);
|
2026-02-20 20:31:04 -08:00
|
|
|
break;
|
|
|
|
|
case 5:
|
2026-02-21 19:41:21 -08:00
|
|
|
if (questMarkerRenderer) questMarkerRenderer->initialize(vkCtx, perFrameSetLayout, cachedAssetManager);
|
2026-02-20 20:31:04 -08:00
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
deferredWorldInitPending_ = false;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
deferredWorldInitStage_++;
|
|
|
|
|
deferredWorldInitCooldown_ = 0.12f;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-06 13:47:03 -08:00
|
|
|
// ============================================================
|
|
|
|
|
// Selection Circle
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
void Renderer::initSelectionCircle() {
|
2026-02-21 19:41:21 -08:00
|
|
|
if (selCirclePipeline != VK_NULL_HANDLE) return;
|
|
|
|
|
if (!vkCtx) return;
|
|
|
|
|
VkDevice device = vkCtx->getDevice();
|
|
|
|
|
|
|
|
|
|
// Load shaders
|
|
|
|
|
VkShaderModule vertShader, fragShader;
|
|
|
|
|
if (!vertShader.loadFromFile(device, "assets/shaders/selection_circle.vert.spv")) {
|
|
|
|
|
LOG_ERROR("initSelectionCircle: failed to load vertex shader");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!fragShader.loadFromFile(device, "assets/shaders/selection_circle.frag.spv")) {
|
|
|
|
|
LOG_ERROR("initSelectionCircle: failed to load fragment shader");
|
|
|
|
|
vertShader.destroy();
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-02-06 13:47:03 -08:00
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
// Pipeline layout: push constants only (mat4 mvp=64 + vec4 color=16), VERTEX|FRAGMENT
|
|
|
|
|
VkPushConstantRange pcRange{};
|
|
|
|
|
pcRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
|
|
|
|
|
pcRange.offset = 0;
|
|
|
|
|
pcRange.size = 80;
|
|
|
|
|
selCirclePipelineLayout = createPipelineLayout(device, {}, {pcRange});
|
2026-02-06 13:47:03 -08:00
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
// Vertex input: binding 0, stride 12, vec3 at location 0
|
|
|
|
|
VkVertexInputBindingDescription vertBind{0, 12, VK_VERTEX_INPUT_RATE_VERTEX};
|
|
|
|
|
VkVertexInputAttributeDescription vertAttr{0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0};
|
|
|
|
|
|
|
|
|
|
// Build disc geometry as TRIANGLE_LIST (replaces GL_TRIANGLE_FAN)
|
|
|
|
|
// N=48 segments: center at origin + ring verts
|
2026-02-06 13:47:03 -08:00
|
|
|
constexpr int SEGMENTS = 48;
|
|
|
|
|
std::vector<float> verts;
|
2026-02-21 19:41:21 -08:00
|
|
|
verts.reserve((SEGMENTS + 1) * 3);
|
|
|
|
|
// Center vertex
|
|
|
|
|
verts.insert(verts.end(), {0.0f, 0.0f, 0.0f});
|
|
|
|
|
// Ring vertices
|
2026-02-20 16:02:34 -08:00
|
|
|
for (int i = 0; i <= SEGMENTS; ++i) {
|
2026-02-06 13:47:03 -08:00
|
|
|
float angle = 2.0f * 3.14159265f * static_cast<float>(i) / static_cast<float>(SEGMENTS);
|
2026-02-21 19:41:21 -08:00
|
|
|
verts.push_back(std::cos(angle));
|
|
|
|
|
verts.push_back(std::sin(angle));
|
2026-02-06 13:47:03 -08:00
|
|
|
verts.push_back(0.0f);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
// Build TRIANGLE_LIST indices: N triangles (center=0, ring[i]=i+1, ring[i+1]=i+2)
|
|
|
|
|
std::vector<uint16_t> indices;
|
|
|
|
|
indices.reserve(SEGMENTS * 3);
|
|
|
|
|
for (int i = 0; i < SEGMENTS; ++i) {
|
|
|
|
|
indices.push_back(0);
|
|
|
|
|
indices.push_back(static_cast<uint16_t>(i + 1));
|
|
|
|
|
indices.push_back(static_cast<uint16_t>(i + 2));
|
|
|
|
|
}
|
|
|
|
|
selCircleVertCount = SEGMENTS * 3; // index count for drawing
|
|
|
|
|
|
|
|
|
|
// Upload vertex buffer
|
|
|
|
|
AllocatedBuffer vbuf = uploadBuffer(*vkCtx, verts.data(),
|
|
|
|
|
verts.size() * sizeof(float), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT);
|
|
|
|
|
selCircleVertBuf = vbuf.buffer;
|
|
|
|
|
selCircleVertAlloc = vbuf.allocation;
|
|
|
|
|
|
|
|
|
|
// Upload index buffer
|
|
|
|
|
AllocatedBuffer ibuf = uploadBuffer(*vkCtx, indices.data(),
|
|
|
|
|
indices.size() * sizeof(uint16_t), VK_BUFFER_USAGE_INDEX_BUFFER_BIT);
|
|
|
|
|
selCircleIdxBuf = ibuf.buffer;
|
|
|
|
|
selCircleIdxAlloc = ibuf.allocation;
|
|
|
|
|
|
|
|
|
|
// Build pipeline: alpha blend, no depth write/test, TRIANGLE_LIST, CULL_NONE
|
|
|
|
|
selCirclePipeline = PipelineBuilder()
|
|
|
|
|
.setShaders(vertShader.stageInfo(VK_SHADER_STAGE_VERTEX_BIT),
|
|
|
|
|
fragShader.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT))
|
|
|
|
|
.setVertexInput({vertBind}, {vertAttr})
|
|
|
|
|
.setTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)
|
|
|
|
|
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
|
|
|
|
.setNoDepthTest()
|
|
|
|
|
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
2026-02-22 02:59:24 -08:00
|
|
|
.setMultisample(vkCtx->getMsaaSamples())
|
2026-02-21 19:41:21 -08:00
|
|
|
.setLayout(selCirclePipelineLayout)
|
|
|
|
|
.setRenderPass(vkCtx->getImGuiRenderPass())
|
|
|
|
|
.setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR})
|
2026-03-24 09:47:03 -07:00
|
|
|
.build(device, vkCtx->getPipelineCache());
|
2026-02-21 19:41:21 -08:00
|
|
|
|
|
|
|
|
vertShader.destroy();
|
|
|
|
|
fragShader.destroy();
|
|
|
|
|
|
|
|
|
|
if (!selCirclePipeline) {
|
|
|
|
|
LOG_ERROR("initSelectionCircle: failed to build pipeline");
|
|
|
|
|
}
|
2026-02-06 13:47:03 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Renderer::setSelectionCircle(const glm::vec3& pos, float radius, const glm::vec3& color) {
|
|
|
|
|
selCirclePos = pos;
|
|
|
|
|
selCircleRadius = radius;
|
|
|
|
|
selCircleColor = color;
|
|
|
|
|
selCircleVisible = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Renderer::clearSelectionCircle() {
|
|
|
|
|
selCircleVisible = false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-07 22:03:28 -08:00
|
|
|
void Renderer::renderSelectionCircle(const glm::mat4& view, const glm::mat4& projection, VkCommandBuffer overrideCmd) {
|
2026-02-06 13:47:03 -08:00
|
|
|
if (!selCircleVisible) return;
|
|
|
|
|
initSelectionCircle();
|
2026-03-07 22:03:28 -08:00
|
|
|
VkCommandBuffer cmd = (overrideCmd != VK_NULL_HANDLE) ? overrideCmd : currentCmd;
|
|
|
|
|
if (selCirclePipeline == VK_NULL_HANDLE || cmd == VK_NULL_HANDLE) return;
|
2026-02-06 13:47:03 -08:00
|
|
|
|
2026-02-20 17:52:45 -08:00
|
|
|
// Keep circle anchored near target foot Z. Accept nearby floor probes only,
|
|
|
|
|
// so distant upper/lower WMO planes don't yank the ring away from feet.
|
|
|
|
|
const float baseZ = selCirclePos.z;
|
|
|
|
|
float floorZ = baseZ;
|
|
|
|
|
auto considerFloor = [&](std::optional<float> sample) {
|
|
|
|
|
if (!sample) return;
|
|
|
|
|
const float h = *sample;
|
|
|
|
|
// Ignore unrelated floors/ceilings far from target feet.
|
|
|
|
|
if (h < baseZ - 1.25f || h > baseZ + 0.85f) return;
|
|
|
|
|
floorZ = std::max(floorZ, h);
|
|
|
|
|
};
|
|
|
|
|
|
2026-02-20 16:02:34 -08:00
|
|
|
if (terrainManager) {
|
2026-02-20 17:52:45 -08:00
|
|
|
considerFloor(terrainManager->getHeightAt(selCirclePos.x, selCirclePos.y));
|
2026-02-20 16:02:34 -08:00
|
|
|
}
|
|
|
|
|
if (wmoRenderer) {
|
2026-02-20 17:52:45 -08:00
|
|
|
considerFloor(wmoRenderer->getFloorHeight(selCirclePos.x, selCirclePos.y, selCirclePos.z + 3.0f));
|
2026-02-20 16:02:34 -08:00
|
|
|
}
|
|
|
|
|
if (m2Renderer) {
|
2026-02-20 17:52:45 -08:00
|
|
|
considerFloor(m2Renderer->getFloorHeight(selCirclePos.x, selCirclePos.y, selCirclePos.z + 2.0f));
|
2026-02-20 16:02:34 -08:00
|
|
|
}
|
|
|
|
|
|
2026-02-06 14:24:38 -08:00
|
|
|
glm::vec3 raisedPos = selCirclePos;
|
2026-02-20 16:02:34 -08:00
|
|
|
raisedPos.z = floorZ + 0.17f;
|
2026-02-06 14:24:38 -08:00
|
|
|
glm::mat4 model = glm::translate(glm::mat4(1.0f), raisedPos);
|
2026-02-06 13:47:03 -08:00
|
|
|
model = glm::scale(model, glm::vec3(selCircleRadius));
|
|
|
|
|
|
|
|
|
|
glm::mat4 mvp = projection * view * model;
|
2026-02-21 19:41:21 -08:00
|
|
|
glm::vec4 color4(selCircleColor, 1.0f);
|
|
|
|
|
|
2026-03-07 22:03:28 -08:00
|
|
|
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, selCirclePipeline);
|
2026-02-21 19:41:21 -08:00
|
|
|
VkDeviceSize offset = 0;
|
2026-03-07 22:03:28 -08:00
|
|
|
vkCmdBindVertexBuffers(cmd, 0, 1, &selCircleVertBuf, &offset);
|
|
|
|
|
vkCmdBindIndexBuffer(cmd, selCircleIdxBuf, 0, VK_INDEX_TYPE_UINT16);
|
2026-02-21 19:41:21 -08:00
|
|
|
// Push mvp (64 bytes) at offset 0
|
2026-03-07 22:03:28 -08:00
|
|
|
vkCmdPushConstants(cmd, selCirclePipelineLayout,
|
2026-02-21 19:41:21 -08:00
|
|
|
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
|
|
|
|
|
0, 64, &mvp[0][0]);
|
|
|
|
|
// Push color (16 bytes) at offset 64
|
2026-03-07 22:03:28 -08:00
|
|
|
vkCmdPushConstants(cmd, selCirclePipelineLayout,
|
2026-02-21 19:41:21 -08:00
|
|
|
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
|
|
|
|
|
64, 16, &color4[0]);
|
2026-03-07 22:03:28 -08:00
|
|
|
vkCmdDrawIndexed(cmd, static_cast<uint32_t>(selCircleVertCount), 1, 0, 0, 0);
|
2026-02-21 19:41:21 -08:00
|
|
|
}
|
2026-02-06 13:47:03 -08:00
|
|
|
|
2026-02-21 20:01:01 -08:00
|
|
|
// ──────────────────────────────────────────────────────────────
|
|
|
|
|
// Fullscreen overlay pipeline (underwater tint, etc.)
|
|
|
|
|
// ──────────────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
void Renderer::initOverlayPipeline() {
|
|
|
|
|
if (!vkCtx) return;
|
|
|
|
|
VkDevice device = vkCtx->getDevice();
|
|
|
|
|
|
|
|
|
|
// Push constant: vec4 color (16 bytes), visible to both stages
|
|
|
|
|
VkPushConstantRange pc{};
|
|
|
|
|
pc.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
|
|
|
|
|
pc.offset = 0;
|
|
|
|
|
pc.size = 16;
|
|
|
|
|
|
|
|
|
|
VkPipelineLayoutCreateInfo plCI{};
|
|
|
|
|
plCI.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
|
|
|
|
|
plCI.pushConstantRangeCount = 1;
|
|
|
|
|
plCI.pPushConstantRanges = &pc;
|
|
|
|
|
vkCreatePipelineLayout(device, &plCI, nullptr, &overlayPipelineLayout);
|
|
|
|
|
|
|
|
|
|
VkShaderModule vertMod, fragMod;
|
|
|
|
|
if (!vertMod.loadFromFile(device, "assets/shaders/postprocess.vert.spv") ||
|
|
|
|
|
!fragMod.loadFromFile(device, "assets/shaders/overlay.frag.spv")) {
|
|
|
|
|
LOG_ERROR("Renderer: failed to load overlay shaders");
|
|
|
|
|
vertMod.destroy(); fragMod.destroy();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
overlayPipeline = PipelineBuilder()
|
|
|
|
|
.setShaders(vertMod.stageInfo(VK_SHADER_STAGE_VERTEX_BIT),
|
|
|
|
|
fragMod.stageInfo(VK_SHADER_STAGE_FRAGMENT_BIT))
|
|
|
|
|
.setVertexInput({}, {}) // fullscreen triangle, no VBOs
|
|
|
|
|
.setTopology(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST)
|
|
|
|
|
.setRasterization(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE)
|
|
|
|
|
.setNoDepthTest()
|
|
|
|
|
.setColorBlendAttachment(PipelineBuilder::blendAlpha())
|
2026-02-22 02:59:24 -08:00
|
|
|
.setMultisample(vkCtx->getMsaaSamples())
|
2026-02-21 20:01:01 -08:00
|
|
|
.setLayout(overlayPipelineLayout)
|
|
|
|
|
.setRenderPass(vkCtx->getImGuiRenderPass())
|
|
|
|
|
.setDynamicStates({VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR})
|
2026-03-24 09:47:03 -07:00
|
|
|
.build(device, vkCtx->getPipelineCache());
|
2026-02-21 20:01:01 -08:00
|
|
|
|
|
|
|
|
vertMod.destroy(); fragMod.destroy();
|
|
|
|
|
|
|
|
|
|
if (overlayPipeline) LOG_INFO("Renderer: overlay pipeline initialized");
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-07 22:03:28 -08:00
|
|
|
void Renderer::renderOverlay(const glm::vec4& color, VkCommandBuffer overrideCmd) {
|
2026-02-21 20:01:01 -08:00
|
|
|
if (!overlayPipeline) initOverlayPipeline();
|
2026-03-07 22:03:28 -08:00
|
|
|
VkCommandBuffer cmd = (overrideCmd != VK_NULL_HANDLE) ? overrideCmd : currentCmd;
|
|
|
|
|
if (!overlayPipeline || cmd == VK_NULL_HANDLE) return;
|
|
|
|
|
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, overlayPipeline);
|
|
|
|
|
vkCmdPushConstants(cmd, overlayPipelineLayout,
|
2026-02-21 20:01:01 -08:00
|
|
|
VK_SHADER_STAGE_FRAGMENT_BIT, 0, 16, &color[0]);
|
2026-03-07 22:03:28 -08:00
|
|
|
vkCmdDraw(cmd, 3, 1, 0, 0); // fullscreen triangle
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-02 00:21:21 +03:00
|
|
|
// ========================= PostProcessPipeline delegation stubs (§4.3) =========================
|
2026-03-07 22:03:28 -08:00
|
|
|
|
2026-04-02 00:21:21 +03:00
|
|
|
PostProcessPipeline* Renderer::getPostProcessPipeline() const {
|
|
|
|
|
return postProcessPipeline_.get();
|
2026-03-07 22:03:28 -08:00
|
|
|
}
|
|
|
|
|
|
2026-04-02 00:21:21 +03:00
|
|
|
void Renderer::setFXAAEnabled(bool enabled) {
|
|
|
|
|
if (postProcessPipeline_) postProcessPipeline_->setFXAAEnabled(enabled);
|
2026-03-07 22:03:28 -08:00
|
|
|
}
|
2026-04-02 00:21:21 +03:00
|
|
|
bool Renderer::isFXAAEnabled() const {
|
|
|
|
|
return postProcessPipeline_ && postProcessPipeline_->isFXAAEnabled();
|
2026-03-07 22:03:28 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Renderer::setFSREnabled(bool enabled) {
|
2026-04-02 00:21:21 +03:00
|
|
|
if (!postProcessPipeline_) return;
|
|
|
|
|
auto req = postProcessPipeline_->setFSREnabled(enabled);
|
|
|
|
|
if (req.requested) {
|
|
|
|
|
pendingMsaaSamples_ = req.samples;
|
|
|
|
|
msaaChangePending_ = true;
|
2026-03-07 22:03:28 -08:00
|
|
|
}
|
|
|
|
|
}
|
2026-04-02 00:21:21 +03:00
|
|
|
bool Renderer::isFSREnabled() const {
|
|
|
|
|
return postProcessPipeline_ && postProcessPipeline_->isFSREnabled();
|
|
|
|
|
}
|
2026-03-07 22:03:28 -08:00
|
|
|
void Renderer::setFSRQuality(float scaleFactor) {
|
2026-04-02 00:21:21 +03:00
|
|
|
if (postProcessPipeline_) postProcessPipeline_->setFSRQuality(scaleFactor);
|
2026-02-21 20:01:01 -08:00
|
|
|
}
|
2026-03-07 22:03:28 -08:00
|
|
|
void Renderer::setFSRSharpness(float sharpness) {
|
2026-04-02 00:21:21 +03:00
|
|
|
if (postProcessPipeline_) postProcessPipeline_->setFSRSharpness(sharpness);
|
2026-03-07 23:13:01 -08:00
|
|
|
}
|
2026-04-02 00:21:21 +03:00
|
|
|
float Renderer::getFSRScaleFactor() const {
|
|
|
|
|
return postProcessPipeline_ ? postProcessPipeline_->getFSRScaleFactor() : 1.0f;
|
2026-03-07 23:13:01 -08:00
|
|
|
}
|
2026-04-02 00:21:21 +03:00
|
|
|
float Renderer::getFSRSharpness() const {
|
|
|
|
|
return postProcessPipeline_ ? postProcessPipeline_->getFSRSharpness() : 0.0f;
|
2026-03-07 23:13:01 -08:00
|
|
|
}
|
|
|
|
|
|
2026-04-02 00:21:21 +03:00
|
|
|
void Renderer::setFSR2Enabled(bool enabled) {
|
|
|
|
|
if (!postProcessPipeline_) return;
|
|
|
|
|
auto req = postProcessPipeline_->setFSR2Enabled(enabled, camera.get());
|
|
|
|
|
if (req.requested) {
|
|
|
|
|
pendingMsaaSamples_ = req.samples;
|
|
|
|
|
msaaChangePending_ = true;
|
2026-03-08 23:13:08 -07:00
|
|
|
}
|
2026-03-07 23:13:01 -08:00
|
|
|
}
|
2026-04-02 00:21:21 +03:00
|
|
|
bool Renderer::isFSR2Enabled() const {
|
|
|
|
|
return postProcessPipeline_ && postProcessPipeline_->isFSR2Enabled();
|
2026-03-07 23:13:01 -08:00
|
|
|
}
|
2026-04-02 00:21:21 +03:00
|
|
|
void Renderer::setFSR2DebugTuning(float jitterSign, float motionVecScaleX, float motionVecScaleY) {
|
|
|
|
|
if (postProcessPipeline_) postProcessPipeline_->setFSR2DebugTuning(jitterSign, motionVecScaleX, motionVecScaleY);
|
2026-03-07 23:13:01 -08:00
|
|
|
}
|
|
|
|
|
|
2026-04-02 00:21:21 +03:00
|
|
|
void Renderer::setAmdFsr3FramegenEnabled(bool enabled) {
|
|
|
|
|
if (postProcessPipeline_) postProcessPipeline_->setAmdFsr3FramegenEnabled(enabled);
|
2026-03-08 19:56:52 -07:00
|
|
|
}
|
2026-04-02 00:21:21 +03:00
|
|
|
bool Renderer::isAmdFsr3FramegenEnabled() const {
|
|
|
|
|
return postProcessPipeline_ && postProcessPipeline_->isAmdFsr3FramegenEnabled();
|
2026-03-08 22:57:35 -07:00
|
|
|
}
|
2026-04-02 00:21:21 +03:00
|
|
|
float Renderer::getFSR2JitterSign() const {
|
|
|
|
|
return postProcessPipeline_ ? postProcessPipeline_->getFSR2JitterSign() : 1.0f;
|
2026-03-07 23:13:01 -08:00
|
|
|
}
|
2026-04-02 00:21:21 +03:00
|
|
|
float Renderer::getFSR2MotionVecScaleX() const {
|
|
|
|
|
return postProcessPipeline_ ? postProcessPipeline_->getFSR2MotionVecScaleX() : 1.0f;
|
2026-03-07 22:03:28 -08:00
|
|
|
}
|
2026-04-02 00:21:21 +03:00
|
|
|
float Renderer::getFSR2MotionVecScaleY() const {
|
|
|
|
|
return postProcessPipeline_ ? postProcessPipeline_->getFSR2MotionVecScaleY() : 1.0f;
|
2026-03-08 20:56:22 -07:00
|
|
|
}
|
2026-04-02 00:21:21 +03:00
|
|
|
bool Renderer::isAmdFsr2SdkAvailable() const {
|
|
|
|
|
return postProcessPipeline_ && postProcessPipeline_->isAmdFsr2SdkAvailable();
|
2026-03-08 22:53:21 -07:00
|
|
|
}
|
2026-04-02 00:21:21 +03:00
|
|
|
bool Renderer::isAmdFsr3FramegenSdkAvailable() const {
|
|
|
|
|
return postProcessPipeline_ && postProcessPipeline_->isAmdFsr3FramegenSdkAvailable();
|
2026-03-12 16:43:48 -07:00
|
|
|
}
|
2026-04-02 00:21:21 +03:00
|
|
|
bool Renderer::isAmdFsr3FramegenRuntimeActive() const {
|
|
|
|
|
return postProcessPipeline_ && postProcessPipeline_->isAmdFsr3FramegenRuntimeActive();
|
2026-03-12 16:43:48 -07:00
|
|
|
}
|
2026-04-02 00:21:21 +03:00
|
|
|
bool Renderer::isAmdFsr3FramegenRuntimeReady() const {
|
|
|
|
|
return postProcessPipeline_ && postProcessPipeline_->isAmdFsr3FramegenRuntimeReady();
|
2026-03-12 16:43:48 -07:00
|
|
|
}
|
2026-04-02 00:21:21 +03:00
|
|
|
const char* Renderer::getAmdFsr3FramegenRuntimePath() const {
|
|
|
|
|
return postProcessPipeline_ ? postProcessPipeline_->getAmdFsr3FramegenRuntimePath() : "";
|
|
|
|
|
}
|
|
|
|
|
const std::string& Renderer::getAmdFsr3FramegenRuntimeError() const {
|
|
|
|
|
static const std::string empty;
|
|
|
|
|
return postProcessPipeline_ ? postProcessPipeline_->getAmdFsr3FramegenRuntimeError() : empty;
|
|
|
|
|
}
|
|
|
|
|
size_t Renderer::getAmdFsr3UpscaleDispatchCount() const {
|
|
|
|
|
return postProcessPipeline_ ? postProcessPipeline_->getAmdFsr3UpscaleDispatchCount() : 0;
|
|
|
|
|
}
|
|
|
|
|
size_t Renderer::getAmdFsr3FramegenDispatchCount() const {
|
|
|
|
|
return postProcessPipeline_ ? postProcessPipeline_->getAmdFsr3FramegenDispatchCount() : 0;
|
|
|
|
|
}
|
|
|
|
|
size_t Renderer::getAmdFsr3FallbackCount() const {
|
|
|
|
|
return postProcessPipeline_ ? postProcessPipeline_->getAmdFsr3FallbackCount() : 0;
|
|
|
|
|
}
|
|
|
|
|
void Renderer::setBrightness(float b) {
|
|
|
|
|
if (postProcessPipeline_) postProcessPipeline_->setBrightness(b);
|
|
|
|
|
}
|
|
|
|
|
float Renderer::getBrightness() const {
|
|
|
|
|
return postProcessPipeline_ ? postProcessPipeline_->getBrightness() : 1.0f;
|
2026-03-12 16:43:48 -07:00
|
|
|
}
|
|
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
void Renderer::renderWorld(game::World* world, game::GameHandler* gameHandler) {
|
2026-04-03 09:41:34 +03:00
|
|
|
ZoneScopedN("Renderer::renderWorld");
|
2026-02-21 20:01:01 -08:00
|
|
|
(void)world;
|
2026-02-06 13:47:03 -08:00
|
|
|
|
2026-03-02 08:58:48 -08:00
|
|
|
// Guard against null command buffer (e.g. after VK_ERROR_DEVICE_LOST)
|
|
|
|
|
if (currentCmd == VK_NULL_HANDLE) return;
|
|
|
|
|
|
2026-03-02 08:06:35 -08:00
|
|
|
// GPU crash diagnostic: skip ALL world rendering to isolate crash source
|
|
|
|
|
static const bool skipAll = (std::getenv("WOWEE_SKIP_ALL_RENDER") != nullptr);
|
|
|
|
|
if (skipAll) return;
|
|
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
auto renderStart = std::chrono::steady_clock::now();
|
|
|
|
|
lastTerrainRenderMs = 0.0;
|
|
|
|
|
lastWMORenderMs = 0.0;
|
|
|
|
|
lastM2RenderMs = 0.0;
|
2026-02-06 13:47:03 -08:00
|
|
|
|
2026-03-13 00:59:36 -07:00
|
|
|
// Cache ghost state for use in overlay and FXAA passes this frame.
|
|
|
|
|
ghostMode_ = (gameHandler && gameHandler->isPlayerGhost());
|
|
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
uint32_t frameIdx = vkCtx->getCurrentFrame();
|
|
|
|
|
VkDescriptorSet perFrameSet = perFrameDescSets[frameIdx];
|
2026-02-21 20:01:01 -08:00
|
|
|
const glm::mat4& view = camera ? camera->getViewMatrix() : glm::mat4(1.0f);
|
|
|
|
|
const glm::mat4& projection = camera ? camera->getProjectionMatrix() : glm::mat4(1.0f);
|
2026-02-06 13:47:03 -08:00
|
|
|
|
2026-03-02 09:52:09 -08:00
|
|
|
// GPU crash diagnostic: skip individual renderers to isolate which one faults
|
|
|
|
|
static const bool skipWMO = (std::getenv("WOWEE_SKIP_WMO") != nullptr);
|
|
|
|
|
static const bool skipChars = (std::getenv("WOWEE_SKIP_CHARS") != nullptr);
|
|
|
|
|
static const bool skipM2 = (std::getenv("WOWEE_SKIP_M2") != nullptr);
|
|
|
|
|
static const bool skipTerrain = (std::getenv("WOWEE_SKIP_TERRAIN") != nullptr);
|
|
|
|
|
static const bool skipSky = (std::getenv("WOWEE_SKIP_SKY") != nullptr);
|
|
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
// Get time of day for sky-related rendering
|
2026-03-27 16:47:30 -07:00
|
|
|
auto* skybox = skySystem ? skySystem->getSkybox() : nullptr;
|
|
|
|
|
float timeOfDay = skybox ? skybox->getTimeOfDay() : 12.0f;
|
2026-02-06 13:47:03 -08:00
|
|
|
|
2026-03-07 22:03:28 -08:00
|
|
|
// ── Multithreaded secondary command buffer recording ──
|
|
|
|
|
// Terrain, WMO, and M2 record on worker threads while main thread handles
|
|
|
|
|
// sky, characters, water, and effects. prepareRender() on main thread first
|
|
|
|
|
// to handle thread-unsafe GPU allocations (descriptor pools, bone SSBOs).
|
|
|
|
|
if (parallelRecordingEnabled_) {
|
|
|
|
|
// --- Pre-compute state + GPU allocations on main thread (not thread-safe) ---
|
|
|
|
|
if (m2Renderer && cameraController) {
|
|
|
|
|
m2Renderer->setInsideInterior(cameraController->isInsideWMO());
|
|
|
|
|
m2Renderer->setOnTaxi(cameraController->isOnTaxi());
|
2026-02-21 19:41:21 -08:00
|
|
|
}
|
2026-03-07 22:03:28 -08:00
|
|
|
if (wmoRenderer) wmoRenderer->prepareRender();
|
|
|
|
|
if (m2Renderer && camera) m2Renderer->prepareRender(frameIdx, *camera);
|
|
|
|
|
if (characterRenderer) characterRenderer->prepareRender(frameIdx);
|
|
|
|
|
|
|
|
|
|
// --- Dispatch worker threads (terrain + WMO + M2) ---
|
|
|
|
|
std::future<double> terrainFuture, wmoFuture, m2Future;
|
|
|
|
|
|
|
|
|
|
if (terrainRenderer && camera && terrainEnabled && !skipTerrain) {
|
|
|
|
|
terrainFuture = std::async(std::launch::async, [&]() -> double {
|
|
|
|
|
auto t0 = std::chrono::steady_clock::now();
|
|
|
|
|
VkCommandBuffer cmd = beginSecondary(SEC_TERRAIN);
|
|
|
|
|
setSecondaryViewportScissor(cmd);
|
|
|
|
|
terrainRenderer->render(cmd, perFrameSet, *camera);
|
|
|
|
|
vkEndCommandBuffer(cmd);
|
|
|
|
|
return std::chrono::duration<double, std::milli>(
|
|
|
|
|
std::chrono::steady_clock::now() - t0).count();
|
|
|
|
|
});
|
2026-02-22 23:20:13 -08:00
|
|
|
}
|
|
|
|
|
|
2026-03-07 22:03:28 -08:00
|
|
|
if (wmoRenderer && camera && !skipWMO) {
|
|
|
|
|
wmoFuture = std::async(std::launch::async, [&]() -> double {
|
|
|
|
|
auto t0 = std::chrono::steady_clock::now();
|
|
|
|
|
VkCommandBuffer cmd = beginSecondary(SEC_WMO);
|
|
|
|
|
setSecondaryViewportScissor(cmd);
|
2026-03-10 14:59:02 -07:00
|
|
|
wmoRenderer->render(cmd, perFrameSet, *camera, &characterPosition);
|
2026-03-07 22:03:28 -08:00
|
|
|
vkEndCommandBuffer(cmd);
|
|
|
|
|
return std::chrono::duration<double, std::milli>(
|
|
|
|
|
std::chrono::steady_clock::now() - t0).count();
|
|
|
|
|
});
|
|
|
|
|
}
|
2026-02-21 19:41:21 -08:00
|
|
|
|
2026-03-07 22:03:28 -08:00
|
|
|
if (m2Renderer && camera && !skipM2) {
|
|
|
|
|
m2Future = std::async(std::launch::async, [&]() -> double {
|
|
|
|
|
auto t0 = std::chrono::steady_clock::now();
|
|
|
|
|
VkCommandBuffer cmd = beginSecondary(SEC_M2);
|
|
|
|
|
setSecondaryViewportScissor(cmd);
|
|
|
|
|
m2Renderer->render(cmd, perFrameSet, *camera);
|
|
|
|
|
m2Renderer->renderSmokeParticles(cmd, perFrameSet);
|
|
|
|
|
m2Renderer->renderM2Particles(cmd, perFrameSet);
|
2026-03-13 01:17:30 -07:00
|
|
|
m2Renderer->renderM2Ribbons(cmd, perFrameSet);
|
2026-03-07 22:03:28 -08:00
|
|
|
vkEndCommandBuffer(cmd);
|
|
|
|
|
return std::chrono::duration<double, std::milli>(
|
|
|
|
|
std::chrono::steady_clock::now() - t0).count();
|
|
|
|
|
});
|
|
|
|
|
}
|
2026-02-21 19:41:21 -08:00
|
|
|
|
2026-03-07 22:03:28 -08:00
|
|
|
// --- Main thread: record sky (SEC_SKY) ---
|
|
|
|
|
{
|
|
|
|
|
VkCommandBuffer cmd = beginSecondary(SEC_SKY);
|
|
|
|
|
setSecondaryViewportScissor(cmd);
|
|
|
|
|
if (skySystem && camera && !skipSky) {
|
|
|
|
|
rendering::SkyParams skyParams;
|
|
|
|
|
skyParams.timeOfDay = timeOfDay;
|
|
|
|
|
skyParams.gameTime = gameHandler ? gameHandler->getGameTime() : -1.0f;
|
|
|
|
|
if (lightingManager) {
|
|
|
|
|
const auto& lighting = lightingManager->getLightingParams();
|
|
|
|
|
skyParams.directionalDir = lighting.directionalDir;
|
|
|
|
|
skyParams.sunColor = lighting.diffuseColor;
|
|
|
|
|
skyParams.skyTopColor = lighting.skyTopColor;
|
|
|
|
|
skyParams.skyMiddleColor = lighting.skyMiddleColor;
|
|
|
|
|
skyParams.skyBand1Color = lighting.skyBand1Color;
|
|
|
|
|
skyParams.skyBand2Color = lighting.skyBand2Color;
|
|
|
|
|
skyParams.cloudDensity = lighting.cloudDensity;
|
|
|
|
|
skyParams.fogDensity = lighting.fogDensity;
|
|
|
|
|
skyParams.horizonGlow = lighting.horizonGlow;
|
|
|
|
|
}
|
|
|
|
|
if (gameHandler) skyParams.weatherIntensity = gameHandler->getWeatherIntensity();
|
|
|
|
|
skyParams.skyboxModelId = 0;
|
|
|
|
|
skyParams.skyboxHasStars = false;
|
|
|
|
|
skySystem->render(cmd, perFrameSet, *camera, skyParams);
|
|
|
|
|
}
|
|
|
|
|
vkEndCommandBuffer(cmd);
|
|
|
|
|
}
|
2026-02-21 20:01:01 -08:00
|
|
|
|
2026-03-07 22:03:28 -08:00
|
|
|
// --- Main thread: record characters + selection circle (SEC_CHARS) ---
|
|
|
|
|
{
|
|
|
|
|
VkCommandBuffer cmd = beginSecondary(SEC_CHARS);
|
|
|
|
|
setSecondaryViewportScissor(cmd);
|
|
|
|
|
renderSelectionCircle(view, projection, cmd);
|
|
|
|
|
if (characterRenderer && camera && !skipChars) {
|
|
|
|
|
characterRenderer->render(cmd, perFrameSet, *camera);
|
|
|
|
|
}
|
|
|
|
|
vkEndCommandBuffer(cmd);
|
|
|
|
|
}
|
2026-02-21 19:41:21 -08:00
|
|
|
|
2026-03-07 22:03:28 -08:00
|
|
|
// --- Wait for workers ---
|
2026-03-29 21:26:01 -07:00
|
|
|
// Guard with try-catch: future::get() re-throws any exception from the
|
|
|
|
|
// async task. Without this, a single bad_alloc in a render worker would
|
|
|
|
|
// propagate as an unhandled exception and terminate the process.
|
|
|
|
|
try { if (terrainFuture.valid()) lastTerrainRenderMs = terrainFuture.get(); }
|
|
|
|
|
catch (const std::exception& e) { LOG_ERROR("Terrain render worker: ", e.what()); }
|
|
|
|
|
try { if (wmoFuture.valid()) lastWMORenderMs = wmoFuture.get(); }
|
|
|
|
|
catch (const std::exception& e) { LOG_ERROR("WMO render worker: ", e.what()); }
|
|
|
|
|
try { if (m2Future.valid()) lastM2RenderMs = m2Future.get(); }
|
|
|
|
|
catch (const std::exception& e) { LOG_ERROR("M2 render worker: ", e.what()); }
|
2026-03-07 22:03:28 -08:00
|
|
|
|
|
|
|
|
// --- Main thread: record post-opaque (SEC_POST) ---
|
|
|
|
|
{
|
|
|
|
|
VkCommandBuffer cmd = beginSecondary(SEC_POST);
|
|
|
|
|
setSecondaryViewportScissor(cmd);
|
|
|
|
|
if (waterRenderer && camera)
|
|
|
|
|
waterRenderer->render(cmd, perFrameSet, *camera, globalTime, false, frameIdx);
|
|
|
|
|
if (weather && camera) weather->render(cmd, perFrameSet);
|
2026-03-13 09:52:23 -07:00
|
|
|
if (lightning && camera && lightning->isEnabled()) lightning->render(cmd, perFrameSet);
|
2026-03-07 22:03:28 -08:00
|
|
|
if (swimEffects && camera) swimEffects->render(cmd, perFrameSet);
|
|
|
|
|
if (mountDust && camera) mountDust->render(cmd, perFrameSet);
|
|
|
|
|
if (chargeEffect && camera) chargeEffect->render(cmd, perFrameSet);
|
|
|
|
|
if (questMarkerRenderer && camera) questMarkerRenderer->render(cmd, perFrameSet, *camera);
|
|
|
|
|
|
|
|
|
|
// Underwater overlay + minimap
|
|
|
|
|
if (overlayPipeline && waterRenderer && camera) {
|
|
|
|
|
glm::vec3 camPos = camera->getPosition();
|
|
|
|
|
auto waterH = waterRenderer->getNearestWaterHeightAt(camPos.x, camPos.y, camPos.z);
|
|
|
|
|
constexpr float MIN_SUBMERSION_OVERLAY = 1.5f;
|
|
|
|
|
if (waterH && camPos.z < (*waterH - MIN_SUBMERSION_OVERLAY)
|
|
|
|
|
&& !waterRenderer->isWmoWaterAt(camPos.x, camPos.y)) {
|
|
|
|
|
float depth = *waterH - camPos.z - MIN_SUBMERSION_OVERLAY;
|
|
|
|
|
bool canal = false;
|
|
|
|
|
if (auto lt = waterRenderer->getWaterTypeAt(camPos.x, camPos.y))
|
|
|
|
|
canal = (*lt == 5 || *lt == 13 || *lt == 17);
|
|
|
|
|
float fogStrength = 1.0f - std::exp(-depth * (canal ? 0.25f : 0.12f));
|
|
|
|
|
fogStrength = glm::clamp(fogStrength, 0.0f, 0.75f);
|
|
|
|
|
glm::vec4 tint = canal
|
|
|
|
|
? glm::vec4(0.01f, 0.04f, 0.10f, fogStrength)
|
|
|
|
|
: glm::vec4(0.03f, 0.09f, 0.18f, fogStrength);
|
|
|
|
|
renderOverlay(tint, cmd);
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-17 10:58:07 -07:00
|
|
|
// Ghost mode desaturation: cold blue-grey overlay when dead/ghost
|
|
|
|
|
if (ghostMode_) {
|
|
|
|
|
renderOverlay(glm::vec4(0.30f, 0.35f, 0.42f, 0.45f), cmd);
|
|
|
|
|
}
|
2026-03-17 09:04:53 -07:00
|
|
|
// Brightness overlay (applied before minimap so it doesn't affect UI)
|
2026-04-02 00:21:21 +03:00
|
|
|
{
|
|
|
|
|
float br = postProcessPipeline_ ? postProcessPipeline_->getBrightness() : 1.0f;
|
|
|
|
|
if (br < 0.99f) {
|
|
|
|
|
renderOverlay(glm::vec4(0.0f, 0.0f, 0.0f, 1.0f - br), cmd);
|
|
|
|
|
} else if (br > 1.01f) {
|
|
|
|
|
float alpha = (br - 1.0f) / 1.0f;
|
|
|
|
|
renderOverlay(glm::vec4(1.0f, 1.0f, 1.0f, alpha), cmd);
|
|
|
|
|
}
|
2026-03-17 09:04:53 -07:00
|
|
|
}
|
2026-03-07 22:03:28 -08:00
|
|
|
if (minimap && minimap->isEnabled() && camera && window) {
|
|
|
|
|
glm::vec3 minimapCenter = camera->getPosition();
|
|
|
|
|
if (cameraController && cameraController->isThirdPerson())
|
|
|
|
|
minimapCenter = characterPosition;
|
|
|
|
|
float minimapPlayerOrientation = 0.0f;
|
|
|
|
|
bool hasMinimapPlayerOrientation = false;
|
|
|
|
|
if (cameraController) {
|
|
|
|
|
float facingRad = glm::radians(characterYaw);
|
|
|
|
|
glm::vec3 facingFwd(std::cos(facingRad), std::sin(facingRad), 0.0f);
|
2026-03-13 02:25:06 -07:00
|
|
|
// atan2(-x,y) = canonical yaw (0=North); negate for shader convention.
|
|
|
|
|
minimapPlayerOrientation = -std::atan2(-facingFwd.x, facingFwd.y);
|
2026-03-07 22:03:28 -08:00
|
|
|
hasMinimapPlayerOrientation = true;
|
|
|
|
|
} else if (gameHandler) {
|
2026-03-13 02:25:06 -07:00
|
|
|
// movementInfo.orientation is canonical yaw: 0=North, π/2=East.
|
|
|
|
|
// Minimap shader: arrowRotation=0 points up (North), positive rotates CW
|
|
|
|
|
// (π/2=West, -π/2=East). Correct mapping: arrowRotation = -canonical_yaw.
|
|
|
|
|
minimapPlayerOrientation = -gameHandler->getMovementInfo().orientation;
|
2026-03-07 22:03:28 -08:00
|
|
|
hasMinimapPlayerOrientation = true;
|
|
|
|
|
}
|
|
|
|
|
minimap->render(cmd, *camera, minimapCenter,
|
|
|
|
|
window->getWidth(), window->getHeight(),
|
|
|
|
|
minimapPlayerOrientation, hasMinimapPlayerOrientation);
|
|
|
|
|
}
|
|
|
|
|
vkEndCommandBuffer(cmd);
|
|
|
|
|
}
|
2026-02-21 20:01:01 -08:00
|
|
|
|
2026-03-07 22:03:28 -08:00
|
|
|
// --- Execute all secondary buffers in correct draw order ---
|
|
|
|
|
VkCommandBuffer validCmds[6];
|
|
|
|
|
uint32_t numCmds = 0;
|
|
|
|
|
validCmds[numCmds++] = secondaryCmds_[SEC_SKY][frameIdx];
|
|
|
|
|
if (terrainRenderer && camera && terrainEnabled && !skipTerrain)
|
|
|
|
|
validCmds[numCmds++] = secondaryCmds_[SEC_TERRAIN][frameIdx];
|
|
|
|
|
if (wmoRenderer && camera && !skipWMO)
|
|
|
|
|
validCmds[numCmds++] = secondaryCmds_[SEC_WMO][frameIdx];
|
|
|
|
|
validCmds[numCmds++] = secondaryCmds_[SEC_CHARS][frameIdx];
|
|
|
|
|
if (m2Renderer && camera && !skipM2)
|
|
|
|
|
validCmds[numCmds++] = secondaryCmds_[SEC_M2][frameIdx];
|
|
|
|
|
validCmds[numCmds++] = secondaryCmds_[SEC_POST][frameIdx];
|
|
|
|
|
|
|
|
|
|
vkCmdExecuteCommands(currentCmd, numCmds, validCmds);
|
2026-02-21 20:01:01 -08:00
|
|
|
|
2026-03-07 22:03:28 -08:00
|
|
|
} else {
|
|
|
|
|
// ── Fallback: single-threaded inline recording (original path) ──
|
|
|
|
|
|
|
|
|
|
if (skySystem && camera && !skipSky) {
|
|
|
|
|
rendering::SkyParams skyParams;
|
|
|
|
|
skyParams.timeOfDay = timeOfDay;
|
|
|
|
|
skyParams.gameTime = gameHandler ? gameHandler->getGameTime() : -1.0f;
|
|
|
|
|
if (lightingManager) {
|
|
|
|
|
const auto& lighting = lightingManager->getLightingParams();
|
|
|
|
|
skyParams.directionalDir = lighting.directionalDir;
|
|
|
|
|
skyParams.sunColor = lighting.diffuseColor;
|
|
|
|
|
skyParams.skyTopColor = lighting.skyTopColor;
|
|
|
|
|
skyParams.skyMiddleColor = lighting.skyMiddleColor;
|
|
|
|
|
skyParams.skyBand1Color = lighting.skyBand1Color;
|
|
|
|
|
skyParams.skyBand2Color = lighting.skyBand2Color;
|
|
|
|
|
skyParams.cloudDensity = lighting.cloudDensity;
|
|
|
|
|
skyParams.fogDensity = lighting.fogDensity;
|
|
|
|
|
skyParams.horizonGlow = lighting.horizonGlow;
|
|
|
|
|
}
|
|
|
|
|
if (gameHandler) skyParams.weatherIntensity = gameHandler->getWeatherIntensity();
|
|
|
|
|
skyParams.skyboxModelId = 0;
|
|
|
|
|
skyParams.skyboxHasStars = false;
|
|
|
|
|
skySystem->render(currentCmd, perFrameSet, *camera, skyParams);
|
2026-02-21 20:01:01 -08:00
|
|
|
}
|
|
|
|
|
|
2026-03-07 22:03:28 -08:00
|
|
|
if (terrainRenderer && camera && terrainEnabled && !skipTerrain) {
|
|
|
|
|
auto terrainStart = std::chrono::steady_clock::now();
|
|
|
|
|
terrainRenderer->render(currentCmd, perFrameSet, *camera);
|
|
|
|
|
lastTerrainRenderMs = std::chrono::duration<double, std::milli>(
|
|
|
|
|
std::chrono::steady_clock::now() - terrainStart).count();
|
|
|
|
|
}
|
2026-02-21 19:41:21 -08:00
|
|
|
|
2026-03-07 22:03:28 -08:00
|
|
|
if (wmoRenderer && camera && !skipWMO) {
|
|
|
|
|
wmoRenderer->prepareRender();
|
|
|
|
|
auto wmoStart = std::chrono::steady_clock::now();
|
2026-03-10 14:59:02 -07:00
|
|
|
wmoRenderer->render(currentCmd, perFrameSet, *camera, &characterPosition);
|
2026-03-07 22:03:28 -08:00
|
|
|
lastWMORenderMs = std::chrono::duration<double, std::milli>(
|
|
|
|
|
std::chrono::steady_clock::now() - wmoStart).count();
|
|
|
|
|
}
|
2026-02-21 19:41:21 -08:00
|
|
|
|
2026-03-07 22:03:28 -08:00
|
|
|
renderSelectionCircle(view, projection);
|
2026-02-21 19:41:21 -08:00
|
|
|
|
2026-03-07 22:03:28 -08:00
|
|
|
if (characterRenderer && camera && !skipChars) {
|
|
|
|
|
characterRenderer->prepareRender(frameIdx);
|
|
|
|
|
characterRenderer->render(currentCmd, perFrameSet, *camera);
|
|
|
|
|
}
|
2026-02-21 19:41:21 -08:00
|
|
|
|
2026-03-07 22:03:28 -08:00
|
|
|
if (m2Renderer && camera && !skipM2) {
|
|
|
|
|
if (cameraController) {
|
|
|
|
|
m2Renderer->setInsideInterior(cameraController->isInsideWMO());
|
|
|
|
|
m2Renderer->setOnTaxi(cameraController->isOnTaxi());
|
|
|
|
|
}
|
|
|
|
|
m2Renderer->prepareRender(frameIdx, *camera);
|
|
|
|
|
auto m2Start = std::chrono::steady_clock::now();
|
|
|
|
|
m2Renderer->render(currentCmd, perFrameSet, *camera);
|
|
|
|
|
m2Renderer->renderSmokeParticles(currentCmd, perFrameSet);
|
|
|
|
|
m2Renderer->renderM2Particles(currentCmd, perFrameSet);
|
2026-03-13 01:17:30 -07:00
|
|
|
m2Renderer->renderM2Ribbons(currentCmd, perFrameSet);
|
2026-03-07 22:03:28 -08:00
|
|
|
lastM2RenderMs = std::chrono::duration<double, std::milli>(
|
|
|
|
|
std::chrono::steady_clock::now() - m2Start).count();
|
|
|
|
|
}
|
2026-02-21 19:41:21 -08:00
|
|
|
|
2026-03-07 22:03:28 -08:00
|
|
|
if (waterRenderer && camera)
|
|
|
|
|
waterRenderer->render(currentCmd, perFrameSet, *camera, globalTime, false, frameIdx);
|
|
|
|
|
if (weather && camera) weather->render(currentCmd, perFrameSet);
|
2026-03-13 09:52:23 -07:00
|
|
|
if (lightning && camera && lightning->isEnabled()) lightning->render(currentCmd, perFrameSet);
|
2026-03-07 22:03:28 -08:00
|
|
|
if (swimEffects && camera) swimEffects->render(currentCmd, perFrameSet);
|
|
|
|
|
if (mountDust && camera) mountDust->render(currentCmd, perFrameSet);
|
|
|
|
|
if (chargeEffect && camera) chargeEffect->render(currentCmd, perFrameSet);
|
|
|
|
|
if (questMarkerRenderer && camera) questMarkerRenderer->render(currentCmd, perFrameSet, *camera);
|
2026-02-21 19:41:21 -08:00
|
|
|
}
|
|
|
|
|
|
2026-03-07 22:03:28 -08:00
|
|
|
// Underwater overlay and minimap — in the fallback path these run inline;
|
|
|
|
|
// in the parallel path they were already recorded into SEC_POST above.
|
|
|
|
|
if (!parallelRecordingEnabled_) {
|
|
|
|
|
if (overlayPipeline && waterRenderer && camera) {
|
|
|
|
|
glm::vec3 camPos = camera->getPosition();
|
|
|
|
|
auto waterH = waterRenderer->getNearestWaterHeightAt(camPos.x, camPos.y, camPos.z);
|
|
|
|
|
constexpr float MIN_SUBMERSION_OVERLAY = 1.5f;
|
|
|
|
|
if (waterH && camPos.z < (*waterH - MIN_SUBMERSION_OVERLAY)
|
|
|
|
|
&& !waterRenderer->isWmoWaterAt(camPos.x, camPos.y)) {
|
|
|
|
|
float depth = *waterH - camPos.z - MIN_SUBMERSION_OVERLAY;
|
|
|
|
|
bool canal = false;
|
|
|
|
|
if (auto lt = waterRenderer->getWaterTypeAt(camPos.x, camPos.y))
|
|
|
|
|
canal = (*lt == 5 || *lt == 13 || *lt == 17);
|
|
|
|
|
float fogStrength = 1.0f - std::exp(-depth * (canal ? 0.25f : 0.12f));
|
|
|
|
|
fogStrength = glm::clamp(fogStrength, 0.0f, 0.75f);
|
|
|
|
|
glm::vec4 tint = canal
|
|
|
|
|
? glm::vec4(0.01f, 0.04f, 0.10f, fogStrength)
|
|
|
|
|
: glm::vec4(0.03f, 0.09f, 0.18f, fogStrength);
|
|
|
|
|
renderOverlay(tint);
|
|
|
|
|
}
|
2026-02-21 20:01:01 -08:00
|
|
|
}
|
2026-03-17 10:58:07 -07:00
|
|
|
// Ghost mode desaturation: cold blue-grey overlay when dead/ghost
|
|
|
|
|
if (ghostMode_) {
|
|
|
|
|
renderOverlay(glm::vec4(0.30f, 0.35f, 0.42f, 0.45f));
|
|
|
|
|
}
|
2026-03-17 09:04:53 -07:00
|
|
|
// Brightness overlay (applied before minimap so it doesn't affect UI)
|
2026-04-02 00:21:21 +03:00
|
|
|
{
|
|
|
|
|
float br = postProcessPipeline_ ? postProcessPipeline_->getBrightness() : 1.0f;
|
|
|
|
|
if (br < 0.99f) {
|
|
|
|
|
renderOverlay(glm::vec4(0.0f, 0.0f, 0.0f, 1.0f - br));
|
|
|
|
|
} else if (br > 1.01f) {
|
|
|
|
|
float alpha = (br - 1.0f) / 1.0f;
|
|
|
|
|
renderOverlay(glm::vec4(1.0f, 1.0f, 1.0f, alpha));
|
|
|
|
|
}
|
2026-03-17 09:04:53 -07:00
|
|
|
}
|
2026-03-07 22:03:28 -08:00
|
|
|
if (minimap && minimap->isEnabled() && camera && window) {
|
|
|
|
|
glm::vec3 minimapCenter = camera->getPosition();
|
|
|
|
|
if (cameraController && cameraController->isThirdPerson())
|
|
|
|
|
minimapCenter = characterPosition;
|
|
|
|
|
float minimapPlayerOrientation = 0.0f;
|
|
|
|
|
bool hasMinimapPlayerOrientation = false;
|
|
|
|
|
if (cameraController) {
|
|
|
|
|
float facingRad = glm::radians(characterYaw);
|
|
|
|
|
glm::vec3 facingFwd(std::cos(facingRad), std::sin(facingRad), 0.0f);
|
2026-03-13 02:25:06 -07:00
|
|
|
// atan2(-x,y) = canonical yaw (0=North); negate for shader convention.
|
|
|
|
|
minimapPlayerOrientation = -std::atan2(-facingFwd.x, facingFwd.y);
|
2026-03-07 22:03:28 -08:00
|
|
|
hasMinimapPlayerOrientation = true;
|
|
|
|
|
} else if (gameHandler) {
|
2026-03-13 02:25:06 -07:00
|
|
|
// movementInfo.orientation is canonical yaw: 0=North, π/2=East.
|
|
|
|
|
// Minimap shader: arrowRotation=0 points up (North), positive rotates CW
|
|
|
|
|
// (π/2=West, -π/2=East). Correct mapping: arrowRotation = -canonical_yaw.
|
|
|
|
|
minimapPlayerOrientation = -gameHandler->getMovementInfo().orientation;
|
2026-03-07 22:03:28 -08:00
|
|
|
hasMinimapPlayerOrientation = true;
|
|
|
|
|
}
|
|
|
|
|
minimap->render(currentCmd, *camera, minimapCenter,
|
|
|
|
|
window->getWidth(), window->getHeight(),
|
|
|
|
|
minimapPlayerOrientation, hasMinimapPlayerOrientation);
|
2026-02-22 08:44:16 -08:00
|
|
|
}
|
2026-02-21 19:41:21 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto renderEnd = std::chrono::steady_clock::now();
|
|
|
|
|
lastRenderMs = std::chrono::duration<double, std::milli>(renderEnd - renderStart).count();
|
2026-02-02 12:24:50 -08:00
|
|
|
}
|
|
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
// initPostProcess(), resizePostProcess(), shutdownPostProcess() removed —
|
|
|
|
|
// post-process pipeline is now handled by Vulkan (Phase 6 cleanup).
|
2026-02-04 15:21:04 -08:00
|
|
|
|
2026-03-02 08:11:36 -08:00
|
|
|
bool Renderer::initializeRenderers(pipeline::AssetManager* assetManager, const std::string& mapName) {
|
2026-02-02 12:24:50 -08:00
|
|
|
if (!assetManager) {
|
|
|
|
|
LOG_ERROR("Asset manager is null");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-02 08:11:36 -08:00
|
|
|
LOG_INFO("Initializing renderers for map: ", mapName);
|
2026-02-02 12:24:50 -08:00
|
|
|
|
|
|
|
|
// Create terrain renderer if not already created
|
|
|
|
|
if (!terrainRenderer) {
|
|
|
|
|
terrainRenderer = std::make_unique<TerrainRenderer>();
|
2026-02-21 19:41:21 -08:00
|
|
|
if (!terrainRenderer->initialize(vkCtx, perFrameSetLayout, assetManager)) {
|
2026-02-02 12:24:50 -08:00
|
|
|
LOG_ERROR("Failed to initialize terrain renderer");
|
|
|
|
|
terrainRenderer.reset();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-03-09 18:34:26 -07:00
|
|
|
if (shadowRenderPass != VK_NULL_HANDLE) {
|
|
|
|
|
terrainRenderer->initializeShadow(shadowRenderPass);
|
|
|
|
|
}
|
|
|
|
|
} else if (!terrainRenderer->hasShadowPipeline() && shadowRenderPass != VK_NULL_HANDLE) {
|
|
|
|
|
terrainRenderer->initializeShadow(shadowRenderPass);
|
2026-02-02 12:24:50 -08:00
|
|
|
}
|
|
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
// Create water renderer if not already created
|
|
|
|
|
if (!waterRenderer) {
|
|
|
|
|
waterRenderer = std::make_unique<WaterRenderer>();
|
|
|
|
|
if (!waterRenderer->initialize(vkCtx, perFrameSetLayout)) {
|
|
|
|
|
LOG_ERROR("Failed to initialize water renderer");
|
|
|
|
|
waterRenderer.reset();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create minimap if not already created
|
|
|
|
|
if (!minimap) {
|
|
|
|
|
minimap = std::make_unique<Minimap>();
|
|
|
|
|
if (!minimap->initialize(vkCtx, perFrameSetLayout)) {
|
|
|
|
|
LOG_ERROR("Failed to initialize minimap");
|
|
|
|
|
minimap.reset();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create world map if not already created
|
|
|
|
|
if (!worldMap) {
|
|
|
|
|
worldMap = std::make_unique<WorldMap>();
|
|
|
|
|
if (!worldMap->initialize(vkCtx, assetManager)) {
|
|
|
|
|
LOG_ERROR("Failed to initialize world map");
|
|
|
|
|
worldMap.reset();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create M2, WMO, and Character renderers
|
|
|
|
|
if (!m2Renderer) {
|
|
|
|
|
m2Renderer = std::make_unique<M2Renderer>();
|
|
|
|
|
m2Renderer->initialize(vkCtx, perFrameSetLayout, assetManager);
|
2026-02-23 07:18:44 -08:00
|
|
|
if (swimEffects) {
|
|
|
|
|
swimEffects->setM2Renderer(m2Renderer.get());
|
|
|
|
|
}
|
2026-04-02 00:21:21 +03:00
|
|
|
// Initialize SpellVisualSystem once M2Renderer is available (§4.4)
|
|
|
|
|
if (!spellVisualSystem_) {
|
|
|
|
|
spellVisualSystem_ = std::make_unique<SpellVisualSystem>();
|
|
|
|
|
spellVisualSystem_->initialize(m2Renderer.get());
|
|
|
|
|
}
|
2026-02-21 19:41:21 -08:00
|
|
|
}
|
|
|
|
|
if (!wmoRenderer) {
|
|
|
|
|
wmoRenderer = std::make_unique<WMORenderer>();
|
|
|
|
|
wmoRenderer->initialize(vkCtx, perFrameSetLayout, assetManager);
|
2026-03-02 08:06:35 -08:00
|
|
|
if (shadowRenderPass != VK_NULL_HANDLE) {
|
|
|
|
|
wmoRenderer->initializeShadow(shadowRenderPass);
|
|
|
|
|
}
|
2026-02-21 19:41:21 -08:00
|
|
|
}
|
|
|
|
|
|
2026-03-02 08:06:35 -08:00
|
|
|
// Initialize shadow pipelines for M2 if not yet done
|
|
|
|
|
if (m2Renderer && shadowRenderPass != VK_NULL_HANDLE && !m2Renderer->hasShadowPipeline()) {
|
2026-02-21 19:41:21 -08:00
|
|
|
m2Renderer->initializeShadow(shadowRenderPass);
|
|
|
|
|
}
|
|
|
|
|
if (!characterRenderer) {
|
|
|
|
|
characterRenderer = std::make_unique<CharacterRenderer>();
|
|
|
|
|
characterRenderer->initialize(vkCtx, perFrameSetLayout, assetManager);
|
2026-03-02 08:06:35 -08:00
|
|
|
if (shadowRenderPass != VK_NULL_HANDLE) {
|
|
|
|
|
characterRenderer->initializeShadow(shadowRenderPass);
|
|
|
|
|
}
|
2026-02-21 19:49:50 -08:00
|
|
|
}
|
2026-02-21 19:41:21 -08:00
|
|
|
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
// Initialize AnimationController (§4.2)
|
|
|
|
|
if (!animationController_) {
|
|
|
|
|
animationController_ = std::make_unique<AnimationController>();
|
|
|
|
|
animationController_->initialize(this);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
// Create and initialize terrain manager
|
|
|
|
|
if (!terrainManager) {
|
|
|
|
|
terrainManager = std::make_unique<TerrainManager>();
|
|
|
|
|
if (!terrainManager->initialize(assetManager, terrainRenderer.get())) {
|
|
|
|
|
LOG_ERROR("Failed to initialize terrain manager");
|
|
|
|
|
terrainManager.reset();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
// Set water renderer for terrain streaming
|
|
|
|
|
if (waterRenderer) {
|
|
|
|
|
terrainManager->setWaterRenderer(waterRenderer.get());
|
|
|
|
|
}
|
|
|
|
|
// Set M2 renderer for doodad loading during streaming
|
|
|
|
|
if (m2Renderer) {
|
|
|
|
|
terrainManager->setM2Renderer(m2Renderer.get());
|
|
|
|
|
}
|
|
|
|
|
// Set WMO renderer for building loading during streaming
|
|
|
|
|
if (wmoRenderer) {
|
|
|
|
|
terrainManager->setWMORenderer(wmoRenderer.get());
|
|
|
|
|
}
|
2026-02-09 14:50:14 -08:00
|
|
|
// Set ambient sound manager for environmental audio emitters
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
if (audioCoordinator_->getAmbientSoundManager()) {
|
|
|
|
|
terrainManager->setAmbientSoundManager(audioCoordinator_->getAmbientSoundManager());
|
2026-02-09 14:50:14 -08:00
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
// Pass asset manager to character renderer for texture loading
|
|
|
|
|
if (characterRenderer) {
|
|
|
|
|
characterRenderer->setAssetManager(assetManager);
|
|
|
|
|
}
|
2026-02-04 20:06:27 -08:00
|
|
|
// Wire asset manager to minimap for tile texture loading
|
2026-02-02 12:24:50 -08:00
|
|
|
if (minimap) {
|
2026-02-04 20:06:27 -08:00
|
|
|
minimap->setAssetManager(assetManager);
|
2026-02-02 12:24:50 -08:00
|
|
|
}
|
|
|
|
|
// Wire terrain manager, WMO renderer, and water renderer to camera controller
|
|
|
|
|
if (cameraController) {
|
|
|
|
|
cameraController->setTerrainManager(terrainManager.get());
|
|
|
|
|
if (wmoRenderer) {
|
|
|
|
|
cameraController->setWMORenderer(wmoRenderer.get());
|
|
|
|
|
}
|
2026-02-02 23:03:45 -08:00
|
|
|
if (m2Renderer) {
|
|
|
|
|
cameraController->setM2Renderer(m2Renderer.get());
|
|
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
if (waterRenderer) {
|
|
|
|
|
cameraController->setWaterRenderer(waterRenderer.get());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-02 08:11:36 -08:00
|
|
|
// Set map name on sub-renderers
|
|
|
|
|
if (terrainManager) terrainManager->setMapName(mapName);
|
|
|
|
|
if (minimap) minimap->setMapName(mapName);
|
|
|
|
|
if (worldMap) worldMap->setMapName(mapName);
|
2026-02-02 12:24:50 -08:00
|
|
|
|
2026-03-02 08:11:36 -08:00
|
|
|
// Initialize audio managers
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
if (audioCoordinator_->getMusicManager() && assetManager && !cachedAssetManager) {
|
2026-02-17 18:52:19 -08:00
|
|
|
audio::AudioEngine::instance().setAssetManager(assetManager);
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
audioCoordinator_->getMusicManager()->initialize(assetManager);
|
|
|
|
|
if (audioCoordinator_->getFootstepManager()) {
|
|
|
|
|
audioCoordinator_->getFootstepManager()->initialize(assetManager);
|
2026-02-03 14:55:32 -08:00
|
|
|
}
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
if (audioCoordinator_->getActivitySoundManager()) {
|
|
|
|
|
audioCoordinator_->getActivitySoundManager()->initialize(assetManager);
|
2026-02-03 19:49:56 -08:00
|
|
|
}
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
if (audioCoordinator_->getMountSoundManager()) {
|
|
|
|
|
audioCoordinator_->getMountSoundManager()->initialize(assetManager);
|
2026-02-09 01:50:42 -08:00
|
|
|
}
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
if (audioCoordinator_->getNpcVoiceManager()) {
|
|
|
|
|
audioCoordinator_->getNpcVoiceManager()->initialize(assetManager);
|
2026-02-09 01:50:42 -08:00
|
|
|
}
|
2026-02-20 20:31:04 -08:00
|
|
|
if (!deferredWorldInitEnabled_) {
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
if (audioCoordinator_->getAmbientSoundManager()) {
|
|
|
|
|
audioCoordinator_->getAmbientSoundManager()->initialize(assetManager);
|
2026-02-20 20:31:04 -08:00
|
|
|
}
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
if (audioCoordinator_->getUiSoundManager()) {
|
|
|
|
|
audioCoordinator_->getUiSoundManager()->initialize(assetManager);
|
2026-02-20 20:31:04 -08:00
|
|
|
}
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
if (audioCoordinator_->getCombatSoundManager()) {
|
|
|
|
|
audioCoordinator_->getCombatSoundManager()->initialize(assetManager);
|
2026-02-20 20:31:04 -08:00
|
|
|
}
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
if (audioCoordinator_->getSpellSoundManager()) {
|
|
|
|
|
audioCoordinator_->getSpellSoundManager()->initialize(assetManager);
|
2026-02-20 20:31:04 -08:00
|
|
|
}
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
if (audioCoordinator_->getMovementSoundManager()) {
|
|
|
|
|
audioCoordinator_->getMovementSoundManager()->initialize(assetManager);
|
2026-02-20 20:31:04 -08:00
|
|
|
}
|
|
|
|
|
if (questMarkerRenderer) {
|
2026-02-21 19:41:21 -08:00
|
|
|
questMarkerRenderer->initialize(vkCtx, perFrameSetLayout, assetManager);
|
2026-02-20 20:31:04 -08:00
|
|
|
}
|
2026-02-11 18:25:04 -08:00
|
|
|
|
2026-02-20 20:31:04 -08:00
|
|
|
if (envFlagEnabled("WOWEE_PREWARM_ZONE_MUSIC", false)) {
|
|
|
|
|
if (zoneManager) {
|
|
|
|
|
for (const auto& musicPath : zoneManager->getAllMusicPaths()) {
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
audioCoordinator_->getMusicManager()->preloadMusic(musicPath);
|
2026-02-20 20:31:04 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
static const std::vector<std::string> tavernTracks = {
|
|
|
|
|
"Sound\\Music\\ZoneMusic\\TavernAlliance\\TavernAlliance01.mp3",
|
|
|
|
|
"Sound\\Music\\ZoneMusic\\TavernAlliance\\TavernAlliance02.mp3",
|
|
|
|
|
"Sound\\Music\\ZoneMusic\\TavernHuman\\RA_HumanTavern1A.mp3",
|
|
|
|
|
"Sound\\Music\\ZoneMusic\\TavernHuman\\RA_HumanTavern2A.mp3",
|
|
|
|
|
};
|
|
|
|
|
for (const auto& musicPath : tavernTracks) {
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
audioCoordinator_->getMusicManager()->preloadMusic(musicPath);
|
2026-02-20 20:31:04 -08:00
|
|
|
}
|
2026-02-11 18:25:04 -08:00
|
|
|
}
|
2026-02-20 20:31:04 -08:00
|
|
|
} else {
|
|
|
|
|
deferredWorldInitPending_ = true;
|
|
|
|
|
deferredWorldInitStage_ = 0;
|
|
|
|
|
deferredWorldInitCooldown_ = 0.25f;
|
2026-02-11 18:25:04 -08:00
|
|
|
}
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
cachedAssetManager = assetManager;
|
2026-03-09 16:18:08 -07:00
|
|
|
|
|
|
|
|
// Enrich zone music from DBC if not already done (e.g. asset manager was null at init).
|
|
|
|
|
if (zoneManager && assetManager) {
|
|
|
|
|
zoneManager->enrichFromDBC(assetManager);
|
|
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
}
|
|
|
|
|
|
2026-03-02 08:11:36 -08:00
|
|
|
// Snap camera to ground
|
2026-02-02 12:24:50 -08:00
|
|
|
if (cameraController) {
|
|
|
|
|
cameraController->reset();
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-02 08:11:36 -08:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Renderer::loadTestTerrain(pipeline::AssetManager* assetManager, const std::string& adtPath) {
|
|
|
|
|
if (!assetManager) {
|
|
|
|
|
LOG_ERROR("Asset manager is null");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOG_INFO("Loading test terrain: ", adtPath);
|
|
|
|
|
|
|
|
|
|
// Extract map name from ADT path for renderer initialization
|
|
|
|
|
std::string mapName;
|
|
|
|
|
{
|
|
|
|
|
size_t lastSep = adtPath.find_last_of("\\/");
|
|
|
|
|
if (lastSep != std::string::npos) {
|
|
|
|
|
std::string filename = adtPath.substr(lastSep + 1);
|
|
|
|
|
size_t firstUnderscore = filename.find('_');
|
|
|
|
|
mapName = filename.substr(0, firstUnderscore != std::string::npos ? firstUnderscore : filename.size());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Initialize all sub-renderers
|
|
|
|
|
if (!initializeRenderers(assetManager, mapName)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Parse tile coordinates from ADT path
|
|
|
|
|
// Format: World\Maps\{MapName}\{MapName}_{X}_{Y}.adt
|
|
|
|
|
int tileX = 32, tileY = 49; // defaults
|
|
|
|
|
{
|
|
|
|
|
size_t lastSep = adtPath.find_last_of("\\/");
|
|
|
|
|
if (lastSep != std::string::npos) {
|
|
|
|
|
std::string filename = adtPath.substr(lastSep + 1);
|
|
|
|
|
size_t firstUnderscore = filename.find('_');
|
|
|
|
|
if (firstUnderscore != std::string::npos) {
|
|
|
|
|
size_t secondUnderscore = filename.find('_', firstUnderscore + 1);
|
|
|
|
|
if (secondUnderscore != std::string::npos) {
|
|
|
|
|
size_t dot = filename.find('.', secondUnderscore);
|
|
|
|
|
if (dot != std::string::npos) {
|
2026-03-27 10:14:49 -07:00
|
|
|
try {
|
|
|
|
|
tileX = std::stoi(filename.substr(firstUnderscore + 1, secondUnderscore - firstUnderscore - 1));
|
|
|
|
|
tileY = std::stoi(filename.substr(secondUnderscore + 1, dot - secondUnderscore - 1));
|
|
|
|
|
} catch (...) {
|
|
|
|
|
LOG_WARNING("Failed to parse tile coords from: ", filename);
|
|
|
|
|
}
|
2026-03-02 08:11:36 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOG_INFO("Enqueuing initial tile [", tileX, ",", tileY, "] via terrain manager");
|
|
|
|
|
|
|
|
|
|
// Enqueue the initial tile for async loading (avoids long sync stalls)
|
|
|
|
|
if (!terrainManager->enqueueTile(tileX, tileY)) {
|
|
|
|
|
LOG_ERROR("Failed to enqueue initial tile [", tileX, ",", tileY, "]");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
terrainLoaded = true;
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
LOG_INFO("Test terrain loaded successfully!");
|
|
|
|
|
LOG_INFO(" Chunks: ", terrainRenderer->getChunkCount());
|
|
|
|
|
LOG_INFO(" Triangles: ", terrainRenderer->getTriangleCount());
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Renderer::setWireframeMode(bool enabled) {
|
|
|
|
|
if (terrainRenderer) {
|
|
|
|
|
terrainRenderer->setWireframe(enabled);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Renderer::loadTerrainArea(const std::string& mapName, int centerX, int centerY, int radius) {
|
|
|
|
|
// Create terrain renderer if not already created
|
|
|
|
|
if (!terrainRenderer) {
|
|
|
|
|
LOG_ERROR("Terrain renderer not initialized");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create terrain manager if not already created
|
|
|
|
|
if (!terrainManager) {
|
|
|
|
|
terrainManager = std::make_unique<TerrainManager>();
|
|
|
|
|
// Wire terrain manager to camera controller for grounding
|
|
|
|
|
if (cameraController) {
|
|
|
|
|
cameraController->setTerrainManager(terrainManager.get());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOG_INFO("Loading terrain area: ", mapName, " [", centerX, ",", centerY, "] radius=", radius);
|
|
|
|
|
|
|
|
|
|
terrainManager->setMapName(mapName);
|
|
|
|
|
terrainManager->setLoadRadius(radius);
|
|
|
|
|
terrainManager->setUnloadRadius(radius + 1);
|
|
|
|
|
|
|
|
|
|
// Load tiles in radius
|
|
|
|
|
for (int dy = -radius; dy <= radius; dy++) {
|
|
|
|
|
for (int dx = -radius; dx <= radius; dx++) {
|
|
|
|
|
int tileX = centerX + dx;
|
|
|
|
|
int tileY = centerY + dy;
|
|
|
|
|
|
|
|
|
|
if (tileX >= 0 && tileX <= 63 && tileY >= 0 && tileY <= 63) {
|
|
|
|
|
terrainManager->loadTile(tileX, tileY);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
terrainLoaded = true;
|
|
|
|
|
|
2026-02-09 01:48:19 -08:00
|
|
|
// Get asset manager from Application if not cached yet
|
|
|
|
|
if (!cachedAssetManager) {
|
|
|
|
|
cachedAssetManager = core::Application::getInstance().getAssetManager();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Initialize music manager with asset manager
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
if (audioCoordinator_->getMusicManager() && cachedAssetManager) {
|
|
|
|
|
if (!audioCoordinator_->getMusicManager()->isInitialized()) {
|
|
|
|
|
audioCoordinator_->getMusicManager()->initialize(cachedAssetManager);
|
2026-02-03 14:55:32 -08:00
|
|
|
}
|
|
|
|
|
}
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
if (audioCoordinator_->getFootstepManager() && cachedAssetManager) {
|
|
|
|
|
if (!audioCoordinator_->getFootstepManager()->isInitialized()) {
|
|
|
|
|
audioCoordinator_->getFootstepManager()->initialize(cachedAssetManager);
|
2026-02-03 19:49:56 -08:00
|
|
|
}
|
|
|
|
|
}
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
if (audioCoordinator_->getActivitySoundManager() && cachedAssetManager) {
|
|
|
|
|
if (!audioCoordinator_->getActivitySoundManager()->isInitialized()) {
|
|
|
|
|
audioCoordinator_->getActivitySoundManager()->initialize(cachedAssetManager);
|
2026-02-02 12:24:50 -08:00
|
|
|
}
|
|
|
|
|
}
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
if (audioCoordinator_->getMountSoundManager() && cachedAssetManager) {
|
|
|
|
|
audioCoordinator_->getMountSoundManager()->initialize(cachedAssetManager);
|
2026-02-09 01:29:44 -08:00
|
|
|
}
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
if (audioCoordinator_->getNpcVoiceManager() && cachedAssetManager) {
|
|
|
|
|
audioCoordinator_->getNpcVoiceManager()->initialize(cachedAssetManager);
|
2026-02-09 01:29:44 -08:00
|
|
|
}
|
2026-02-20 20:31:04 -08:00
|
|
|
if (!deferredWorldInitEnabled_) {
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
if (audioCoordinator_->getAmbientSoundManager() && cachedAssetManager) {
|
|
|
|
|
audioCoordinator_->getAmbientSoundManager()->initialize(cachedAssetManager);
|
2026-02-20 20:31:04 -08:00
|
|
|
}
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
if (audioCoordinator_->getUiSoundManager() && cachedAssetManager) {
|
|
|
|
|
audioCoordinator_->getUiSoundManager()->initialize(cachedAssetManager);
|
2026-02-20 20:31:04 -08:00
|
|
|
}
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
if (audioCoordinator_->getCombatSoundManager() && cachedAssetManager) {
|
|
|
|
|
audioCoordinator_->getCombatSoundManager()->initialize(cachedAssetManager);
|
2026-02-20 20:31:04 -08:00
|
|
|
}
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
if (audioCoordinator_->getSpellSoundManager() && cachedAssetManager) {
|
|
|
|
|
audioCoordinator_->getSpellSoundManager()->initialize(cachedAssetManager);
|
2026-02-20 20:31:04 -08:00
|
|
|
}
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
if (audioCoordinator_->getMovementSoundManager() && cachedAssetManager) {
|
|
|
|
|
audioCoordinator_->getMovementSoundManager()->initialize(cachedAssetManager);
|
2026-02-20 20:31:04 -08:00
|
|
|
}
|
|
|
|
|
if (questMarkerRenderer && cachedAssetManager) {
|
2026-02-21 19:41:21 -08:00
|
|
|
questMarkerRenderer->initialize(vkCtx, perFrameSetLayout, cachedAssetManager);
|
2026-02-20 20:31:04 -08:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
deferredWorldInitPending_ = true;
|
|
|
|
|
deferredWorldInitStage_ = 0;
|
|
|
|
|
deferredWorldInitCooldown_ = 0.1f;
|
2026-02-09 23:41:38 -08:00
|
|
|
}
|
2026-02-09 14:50:14 -08:00
|
|
|
|
|
|
|
|
// Wire ambient sound manager to terrain manager for emitter registration
|
chore(renderer): extract AnimationController and remove audio pass-throughs
Extract ~1,500 lines of character animation state from Renderer into a dedicated
AnimationController class, and complete the AudioCoordinator migration by removing
all 10 audio pass-through getters from Renderer.
AnimationController:
- New: include/rendering/animation_controller.hpp (182 lines)
- New: src/rendering/animation_controller.cpp (1,703 lines)
- Moves: locomotion state machine (50+ members), mount animation (40+ members),
emote system, footstep triggering, surface detection, melee combat animations
- Renderer holds std::unique_ptr<AnimationController> and delegates completely
- AnimationController accesses audio via renderer_->getAudioCoordinator()
Audio caller migration:
- Migrate ~60 external callers from renderer->getXManager() to AudioCoordinator
directly, grouped by access pattern:
- UIServices: settings_panel, game_screen, toast_manager, chat_panel,
combat_ui, window_manager
- GameServices: game_handler, spell_handler, inventory_handler, quest_handler,
social_handler, combat_handler
- Application singleton: application.cpp, auth_screen.cpp, lua_engine.cpp
- Remove 10 pass-through getter definitions from renderer.cpp
- Remove 10 pass-through getter declarations from renderer.hpp
- Remove individual audio manager forward declarations from renderer.hpp
- Redirect 69 internal renderer.cpp audio calls to audioCoordinator_ directly
- game_handler.cpp: withSoundManager template uses services_.audioCoordinator;
MFP changed from &Renderer::getUiSoundManager to &AudioCoordinator::getUiSoundManager
- GameServices struct: add AudioCoordinator* audioCoordinator member
- settings_panel: applyAudioVolumes(Renderer*) -> applyAudioVolumes(AudioCoordinator*)
2026-04-02 13:06:31 +03:00
|
|
|
if (terrainManager && audioCoordinator_->getAmbientSoundManager()) {
|
|
|
|
|
terrainManager->setAmbientSoundManager(audioCoordinator_->getAmbientSoundManager());
|
2026-02-09 14:50:14 -08:00
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
|
2026-02-02 23:03:45 -08:00
|
|
|
// Wire WMO, M2, and water renderer to camera controller
|
2026-02-02 12:24:50 -08:00
|
|
|
if (cameraController && wmoRenderer) {
|
|
|
|
|
cameraController->setWMORenderer(wmoRenderer.get());
|
|
|
|
|
}
|
2026-02-02 23:03:45 -08:00
|
|
|
if (cameraController && m2Renderer) {
|
|
|
|
|
cameraController->setM2Renderer(m2Renderer.get());
|
|
|
|
|
}
|
2026-02-02 12:24:50 -08:00
|
|
|
if (cameraController && waterRenderer) {
|
|
|
|
|
cameraController->setWaterRenderer(waterRenderer.get());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Snap camera to ground now that terrain is loaded
|
|
|
|
|
if (cameraController) {
|
|
|
|
|
cameraController->reset();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOG_INFO("Terrain area loaded: ", terrainManager->getLoadedTileCount(), " tiles");
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Renderer::setTerrainStreaming(bool enabled) {
|
|
|
|
|
if (terrainManager) {
|
|
|
|
|
terrainManager->setStreamingEnabled(enabled);
|
|
|
|
|
LOG_INFO("Terrain streaming: ", enabled ? "ON" : "OFF");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Renderer::renderHUD() {
|
2026-03-02 08:58:48 -08:00
|
|
|
if (currentCmd == VK_NULL_HANDLE) return;
|
2026-02-02 12:24:50 -08:00
|
|
|
if (performanceHUD && camera) {
|
|
|
|
|
performanceHUD->render(this, camera.get());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-04 16:08:35 -08:00
|
|
|
// ──────────────────────────────────────────────────────
|
|
|
|
|
// Shadow mapping helpers
|
|
|
|
|
// ──────────────────────────────────────────────────────
|
|
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
// initShadowMap() and compileShadowShader() removed — shadow resources now created
|
|
|
|
|
// in createPerFrameResources() as part of the Vulkan shadow infrastructure.
|
2026-02-04 16:08:35 -08:00
|
|
|
|
|
|
|
|
glm::mat4 Renderer::computeLightSpaceMatrix() {
|
2026-03-06 20:38:58 -08:00
|
|
|
const float kShadowHalfExtent = shadowDistance_;
|
|
|
|
|
const float kShadowLightDistance = shadowDistance_ * 3.0f;
|
2026-02-04 16:30:24 -08:00
|
|
|
constexpr float kShadowNearPlane = 1.0f;
|
2026-03-06 20:38:58 -08:00
|
|
|
const float kShadowFarPlane = shadowDistance_ * 6.5f;
|
2026-02-04 16:30:24 -08:00
|
|
|
|
2026-02-22 09:47:39 -08:00
|
|
|
// Use active lighting direction so shadow projection matches main shading.
|
|
|
|
|
// Fragment shaders derive lighting with `ldir = normalize(-lightDir.xyz)`,
|
|
|
|
|
// therefore shadow rays must use -directionalDir to stay aligned.
|
2026-02-04 16:08:35 -08:00
|
|
|
glm::vec3 sunDir = glm::normalize(glm::vec3(-0.3f, -0.7f, -0.6f));
|
2026-02-21 03:21:08 -08:00
|
|
|
if (lightingManager) {
|
|
|
|
|
const auto& lighting = lightingManager->getLightingParams();
|
2026-03-27 16:33:16 -07:00
|
|
|
float ldirLenSq = glm::dot(lighting.directionalDir, lighting.directionalDir);
|
|
|
|
|
if (ldirLenSq > 1e-6f) {
|
|
|
|
|
sunDir = -lighting.directionalDir * glm::inversesqrt(ldirLenSq);
|
2026-02-21 03:21:08 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Shadow camera expects light rays pointing downward in render space (Z up).
|
|
|
|
|
// Some profiles/opcode paths provide the opposite convention; normalize here.
|
|
|
|
|
if (sunDir.z > 0.0f) {
|
|
|
|
|
sunDir = -sunDir;
|
|
|
|
|
}
|
|
|
|
|
// Keep a minimum downward component so the frustum doesn't collapse at grazing angles.
|
|
|
|
|
if (sunDir.z > -0.08f) {
|
|
|
|
|
sunDir.z = -0.08f;
|
|
|
|
|
sunDir = glm::normalize(sunDir);
|
|
|
|
|
}
|
2026-02-04 16:08:35 -08:00
|
|
|
|
2026-02-23 08:47:38 -08:00
|
|
|
// Shadow center follows the player directly; texel snapping below
|
|
|
|
|
// prevents shimmer without needing to freeze the projection.
|
2026-02-04 16:22:18 -08:00
|
|
|
glm::vec3 desiredCenter = characterPosition;
|
|
|
|
|
if (!shadowCenterInitialized) {
|
2026-02-23 05:45:22 -08:00
|
|
|
if (glm::dot(desiredCenter, desiredCenter) < 1.0f) {
|
|
|
|
|
return glm::mat4(0.0f);
|
|
|
|
|
}
|
2026-02-04 16:22:18 -08:00
|
|
|
shadowCenterInitialized = true;
|
|
|
|
|
}
|
2026-02-23 08:47:38 -08:00
|
|
|
shadowCenter = desiredCenter;
|
2026-02-04 16:22:18 -08:00
|
|
|
glm::vec3 center = shadowCenter;
|
2026-02-04 16:08:35 -08:00
|
|
|
|
2026-03-07 22:29:06 -08:00
|
|
|
// Snap shadow frustum to texel grid so the projection is perfectly stable
|
|
|
|
|
// while moving. We compute the light's right/up axes from the sun direction
|
|
|
|
|
// (these are constant per frame regardless of center) and snap center along
|
|
|
|
|
// them before building the view matrix.
|
2026-02-04 16:30:24 -08:00
|
|
|
float halfExtent = kShadowHalfExtent;
|
2026-02-04 16:08:35 -08:00
|
|
|
float texelWorld = (2.0f * halfExtent) / static_cast<float>(SHADOW_MAP_SIZE);
|
|
|
|
|
|
2026-03-07 22:29:06 -08:00
|
|
|
// Stable light-space axes (independent of center position)
|
2026-02-04 16:08:35 -08:00
|
|
|
glm::vec3 up(0.0f, 0.0f, 1.0f);
|
|
|
|
|
if (std::abs(glm::dot(sunDir, up)) > 0.99f) {
|
|
|
|
|
up = glm::vec3(0.0f, 1.0f, 0.0f);
|
|
|
|
|
}
|
2026-03-07 22:29:06 -08:00
|
|
|
glm::vec3 lightRight = glm::normalize(glm::cross(sunDir, up));
|
|
|
|
|
glm::vec3 lightUp = glm::normalize(glm::cross(lightRight, sunDir));
|
|
|
|
|
|
|
|
|
|
// Snap center along light's right and up axes to align with texel grid.
|
|
|
|
|
// This eliminates sub-texel shifts that cause shadow shimmer.
|
|
|
|
|
float dotR = glm::dot(center, lightRight);
|
|
|
|
|
float dotU = glm::dot(center, lightUp);
|
|
|
|
|
dotR = std::floor(dotR / texelWorld) * texelWorld;
|
|
|
|
|
dotU = std::floor(dotU / texelWorld) * texelWorld;
|
|
|
|
|
float dotD = glm::dot(center, sunDir); // depth axis unchanged
|
|
|
|
|
center = lightRight * dotR + lightUp * dotU + sunDir * dotD;
|
2026-02-04 16:22:18 -08:00
|
|
|
shadowCenter = center;
|
2026-02-21 02:23:08 -08:00
|
|
|
|
2026-03-07 22:29:06 -08:00
|
|
|
glm::mat4 lightView = glm::lookAt(center - sunDir * kShadowLightDistance, center, up);
|
2026-02-04 16:30:24 -08:00
|
|
|
glm::mat4 lightProj = glm::ortho(-halfExtent, halfExtent, -halfExtent, halfExtent,
|
|
|
|
|
kShadowNearPlane, kShadowFarPlane);
|
2026-02-23 04:26:20 -08:00
|
|
|
lightProj[1][1] *= -1.0f; // Vulkan Y-flip for shadow pass
|
2026-02-04 16:08:35 -08:00
|
|
|
|
|
|
|
|
return lightProj * lightView;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-22 22:34:48 -08:00
|
|
|
void Renderer::setupWater1xPass() {
|
|
|
|
|
if (!waterRenderer || !vkCtx) return;
|
|
|
|
|
VkImageView depthView = vkCtx->getDepthResolveImageView();
|
|
|
|
|
if (!depthView) {
|
|
|
|
|
LOG_WARNING("No depth resolve image available - cannot create 1x water pass");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
waterRenderer->createWater1xPass(vkCtx->getSwapchainFormat(), vkCtx->getDepthFormat());
|
|
|
|
|
waterRenderer->createWater1xFramebuffers(
|
|
|
|
|
vkCtx->getSwapchainImageViews(), depthView, vkCtx->getSwapchainExtent());
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-07 22:03:28 -08:00
|
|
|
// ========================= Multithreaded Secondary Command Buffers =========================
|
|
|
|
|
|
|
|
|
|
bool Renderer::createSecondaryCommandResources() {
|
|
|
|
|
if (!vkCtx) return false;
|
|
|
|
|
VkDevice device = vkCtx->getDevice();
|
|
|
|
|
uint32_t queueFamily = vkCtx->getGraphicsQueueFamily();
|
|
|
|
|
|
|
|
|
|
VkCommandPoolCreateInfo poolCI{};
|
|
|
|
|
poolCI.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
|
|
|
|
|
poolCI.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
|
|
|
|
|
poolCI.queueFamilyIndex = queueFamily;
|
|
|
|
|
|
|
|
|
|
// Create worker command pools (one per worker thread)
|
|
|
|
|
for (uint32_t w = 0; w < NUM_WORKERS; ++w) {
|
|
|
|
|
if (vkCreateCommandPool(device, &poolCI, nullptr, &workerCmdPools_[w]) != VK_SUCCESS) {
|
|
|
|
|
LOG_ERROR("Failed to create worker command pool ", w);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create main-thread secondary command pool
|
|
|
|
|
if (vkCreateCommandPool(device, &poolCI, nullptr, &mainSecondaryCmdPool_) != VK_SUCCESS) {
|
|
|
|
|
LOG_ERROR("Failed to create main secondary command pool");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Allocate secondary command buffers
|
|
|
|
|
VkCommandBufferAllocateInfo allocInfo{};
|
|
|
|
|
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
|
|
|
|
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_SECONDARY;
|
|
|
|
|
allocInfo.commandBufferCount = 1;
|
|
|
|
|
|
|
|
|
|
// Worker secondaries: SEC_TERRAIN=1, SEC_WMO=2, SEC_M2=4 → worker pools 0,1,2
|
|
|
|
|
const uint32_t workerSecondaries[] = { SEC_TERRAIN, SEC_WMO, SEC_M2 };
|
|
|
|
|
for (uint32_t w = 0; w < NUM_WORKERS; ++w) {
|
|
|
|
|
allocInfo.commandPool = workerCmdPools_[w];
|
|
|
|
|
for (uint32_t f = 0; f < MAX_FRAMES; ++f) {
|
|
|
|
|
if (vkAllocateCommandBuffers(device, &allocInfo, &secondaryCmds_[workerSecondaries[w]][f]) != VK_SUCCESS) {
|
|
|
|
|
LOG_ERROR("Failed to allocate worker secondary buffer w=", w, " f=", f);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Main-thread secondaries: SEC_SKY=0, SEC_CHARS=3, SEC_POST=5, SEC_IMGUI=6
|
|
|
|
|
const uint32_t mainSecondaries[] = { SEC_SKY, SEC_CHARS, SEC_POST, SEC_IMGUI };
|
|
|
|
|
for (uint32_t idx : mainSecondaries) {
|
|
|
|
|
allocInfo.commandPool = mainSecondaryCmdPool_;
|
|
|
|
|
for (uint32_t f = 0; f < MAX_FRAMES; ++f) {
|
|
|
|
|
if (vkAllocateCommandBuffers(device, &allocInfo, &secondaryCmds_[idx][f]) != VK_SUCCESS) {
|
|
|
|
|
LOG_ERROR("Failed to allocate main secondary buffer idx=", idx, " f=", f);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
parallelRecordingEnabled_ = true;
|
|
|
|
|
LOG_INFO("Multithreaded rendering: ", NUM_WORKERS, " worker threads, ",
|
|
|
|
|
NUM_SECONDARIES, " secondary buffers [ENABLED]");
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Renderer::destroySecondaryCommandResources() {
|
|
|
|
|
if (!vkCtx) return;
|
|
|
|
|
VkDevice device = vkCtx->getDevice();
|
|
|
|
|
vkDeviceWaitIdle(device);
|
|
|
|
|
|
|
|
|
|
// Secondary buffers are freed when their pool is destroyed
|
|
|
|
|
for (uint32_t w = 0; w < NUM_WORKERS; ++w) {
|
|
|
|
|
if (workerCmdPools_[w]) {
|
|
|
|
|
vkDestroyCommandPool(device, workerCmdPools_[w], nullptr);
|
|
|
|
|
workerCmdPools_[w] = VK_NULL_HANDLE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (mainSecondaryCmdPool_) {
|
|
|
|
|
vkDestroyCommandPool(device, mainSecondaryCmdPool_, nullptr);
|
|
|
|
|
mainSecondaryCmdPool_ = VK_NULL_HANDLE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (auto& arr : secondaryCmds_)
|
|
|
|
|
for (auto& cmd : arr)
|
|
|
|
|
cmd = VK_NULL_HANDLE;
|
|
|
|
|
|
|
|
|
|
parallelRecordingEnabled_ = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
VkCommandBuffer Renderer::beginSecondary(uint32_t secondaryIndex) {
|
|
|
|
|
uint32_t frame = vkCtx->getCurrentFrame();
|
|
|
|
|
VkCommandBuffer cmd = secondaryCmds_[secondaryIndex][frame];
|
|
|
|
|
|
|
|
|
|
VkCommandBufferInheritanceInfo inheritInfo{};
|
|
|
|
|
inheritInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO;
|
|
|
|
|
inheritInfo.renderPass = activeRenderPass_;
|
|
|
|
|
inheritInfo.subpass = 0;
|
|
|
|
|
inheritInfo.framebuffer = activeFramebuffer_;
|
|
|
|
|
|
|
|
|
|
VkCommandBufferBeginInfo beginInfo{};
|
|
|
|
|
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
|
|
|
|
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT
|
|
|
|
|
| VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT;
|
|
|
|
|
beginInfo.pInheritanceInfo = &inheritInfo;
|
|
|
|
|
|
|
|
|
|
VkResult result = vkBeginCommandBuffer(cmd, &beginInfo);
|
|
|
|
|
if (result != VK_SUCCESS) {
|
|
|
|
|
LOG_ERROR("vkBeginCommandBuffer failed for secondary ", secondaryIndex,
|
|
|
|
|
" frame ", frame, " result=", static_cast<int>(result));
|
|
|
|
|
}
|
|
|
|
|
return cmd;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Renderer::setSecondaryViewportScissor(VkCommandBuffer cmd) {
|
|
|
|
|
VkViewport vp{};
|
|
|
|
|
vp.width = static_cast<float>(activeRenderExtent_.width);
|
|
|
|
|
vp.height = static_cast<float>(activeRenderExtent_.height);
|
|
|
|
|
vp.maxDepth = 1.0f;
|
|
|
|
|
vkCmdSetViewport(cmd, 0, 1, &vp);
|
|
|
|
|
|
|
|
|
|
VkRect2D sc{};
|
|
|
|
|
sc.extent = activeRenderExtent_;
|
|
|
|
|
vkCmdSetScissor(cmd, 0, 1, &sc);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-22 22:34:48 -08:00
|
|
|
void Renderer::renderReflectionPass() {
|
|
|
|
|
if (!waterRenderer || !camera || !waterRenderer->hasReflectionPass() || !waterRenderer->hasSurfaces()) return;
|
|
|
|
|
if (currentCmd == VK_NULL_HANDLE || !reflPerFrameUBOMapped) return;
|
|
|
|
|
|
|
|
|
|
// Reflection pass uses 1x MSAA. Scene pipelines must be render-pass-compatible,
|
|
|
|
|
// which requires matching sample counts. Only render scene into reflection when MSAA is off.
|
|
|
|
|
bool canRenderScene = (vkCtx->getMsaaSamples() == VK_SAMPLE_COUNT_1_BIT);
|
|
|
|
|
|
|
|
|
|
// Find dominant water height near camera
|
2026-03-27 16:47:30 -07:00
|
|
|
const glm::vec3 camPos = camera->getPosition();
|
|
|
|
|
auto waterH = waterRenderer->getDominantWaterHeight(camPos);
|
2026-02-22 22:34:48 -08:00
|
|
|
if (!waterH) return;
|
|
|
|
|
|
|
|
|
|
float waterHeight = *waterH;
|
|
|
|
|
|
|
|
|
|
// Skip reflection if camera is underwater (Z is up)
|
2026-03-27 16:47:30 -07:00
|
|
|
if (camPos.z < waterHeight + 0.5f) return;
|
2026-02-22 22:34:48 -08:00
|
|
|
|
|
|
|
|
// Compute reflected view and oblique projection
|
|
|
|
|
glm::mat4 reflView = WaterRenderer::computeReflectedView(*camera, waterHeight);
|
|
|
|
|
glm::mat4 reflProj = WaterRenderer::computeObliqueProjection(
|
|
|
|
|
camera->getProjectionMatrix(), reflView, waterHeight);
|
|
|
|
|
|
|
|
|
|
// Update water renderer's reflection UBO with the reflected viewProj
|
|
|
|
|
waterRenderer->updateReflectionUBO(reflProj * reflView);
|
|
|
|
|
|
|
|
|
|
// Fill the reflection per-frame UBO (same as normal but with reflected matrices)
|
|
|
|
|
GPUPerFrameData reflData = currentFrameData;
|
|
|
|
|
reflData.view = reflView;
|
|
|
|
|
reflData.projection = reflProj;
|
|
|
|
|
// Reflected camera position (Z is up)
|
2026-03-27 16:47:30 -07:00
|
|
|
glm::vec3 reflPos = camPos;
|
2026-02-22 22:34:48 -08:00
|
|
|
reflPos.z = 2.0f * waterHeight - reflPos.z;
|
|
|
|
|
reflData.viewPos = glm::vec4(reflPos, 1.0f);
|
|
|
|
|
std::memcpy(reflPerFrameUBOMapped, &reflData, sizeof(GPUPerFrameData));
|
|
|
|
|
|
|
|
|
|
// Begin reflection render pass (clears to black; scene rendered if pipeline-compatible)
|
|
|
|
|
if (!waterRenderer->beginReflectionPass(currentCmd)) return;
|
|
|
|
|
|
|
|
|
|
if (canRenderScene) {
|
|
|
|
|
// Render scene into reflection texture (sky + terrain + WMO only for perf)
|
|
|
|
|
if (skySystem) {
|
|
|
|
|
rendering::SkyParams skyParams;
|
2026-03-27 16:47:30 -07:00
|
|
|
auto* reflSkybox = skySystem->getSkybox();
|
|
|
|
|
skyParams.timeOfDay = reflSkybox ? reflSkybox->getTimeOfDay() : 12.0f;
|
2026-02-22 22:34:48 -08:00
|
|
|
if (lightingManager) {
|
|
|
|
|
const auto& lp = lightingManager->getLightingParams();
|
|
|
|
|
skyParams.directionalDir = lp.directionalDir;
|
|
|
|
|
skyParams.sunColor = lp.diffuseColor;
|
|
|
|
|
skyParams.skyTopColor = lp.skyTopColor;
|
|
|
|
|
skyParams.skyMiddleColor = lp.skyMiddleColor;
|
|
|
|
|
skyParams.skyBand1Color = lp.skyBand1Color;
|
|
|
|
|
skyParams.skyBand2Color = lp.skyBand2Color;
|
|
|
|
|
skyParams.cloudDensity = lp.cloudDensity;
|
|
|
|
|
skyParams.fogDensity = lp.fogDensity;
|
|
|
|
|
skyParams.horizonGlow = lp.horizonGlow;
|
|
|
|
|
}
|
2026-02-22 23:20:13 -08:00
|
|
|
// weatherIntensity left at default 0 for reflection pass (no game handler in scope)
|
2026-02-22 22:34:48 -08:00
|
|
|
skySystem->render(currentCmd, reflPerFrameDescSet, *camera, skyParams);
|
|
|
|
|
}
|
|
|
|
|
if (terrainRenderer && terrainEnabled) {
|
|
|
|
|
terrainRenderer->render(currentCmd, reflPerFrameDescSet, *camera);
|
|
|
|
|
}
|
|
|
|
|
if (wmoRenderer) {
|
|
|
|
|
wmoRenderer->render(currentCmd, reflPerFrameDescSet, *camera);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
waterRenderer->endReflectionPass(currentCmd);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-04 16:08:35 -08:00
|
|
|
void Renderer::renderShadowPass() {
|
2026-04-03 09:41:34 +03:00
|
|
|
ZoneScopedN("Renderer::renderShadowPass");
|
2026-03-02 08:06:35 -08:00
|
|
|
static const bool skipShadows = (std::getenv("WOWEE_SKIP_SHADOWS") != nullptr);
|
|
|
|
|
if (skipShadows) return;
|
2026-03-09 22:14:32 -07:00
|
|
|
if (!shadowsEnabled || shadowDepthImage[0] == VK_NULL_HANDLE) return;
|
2026-02-21 19:41:21 -08:00
|
|
|
if (currentCmd == VK_NULL_HANDLE) return;
|
2026-02-04 16:30:24 -08:00
|
|
|
|
2026-03-06 20:04:19 -08:00
|
|
|
// Shadows render every frame — throttling causes visible flicker on player/NPCs
|
2026-02-25 10:22:05 -08:00
|
|
|
|
2026-02-21 19:41:21 -08:00
|
|
|
// Compute and store light space matrix; write to per-frame UBO
|
2026-02-04 16:08:35 -08:00
|
|
|
lightSpaceMatrix = computeLightSpaceMatrix();
|
2026-02-23 05:45:22 -08:00
|
|
|
// Zero matrix means character position isn't set yet — skip shadow pass entirely.
|
|
|
|
|
if (lightSpaceMatrix == glm::mat4(0.0f)) return;
|
2026-02-21 19:41:21 -08:00
|
|
|
uint32_t frame = vkCtx->getCurrentFrame();
|
|
|
|
|
auto* ubo = reinterpret_cast<GPUPerFrameData*>(perFrameUBOMapped[frame]);
|
|
|
|
|
if (ubo) {
|
|
|
|
|
ubo->lightSpaceMatrix = lightSpaceMatrix;
|
2026-02-22 22:34:48 -08:00
|
|
|
ubo->shadowParams.x = shadowsEnabled ? 1.0f : 0.0f;
|
|
|
|
|
ubo->shadowParams.y = 0.8f;
|
2026-02-21 19:41:21 -08:00
|
|
|
}
|
|
|
|
|
|
2026-03-09 22:14:32 -07:00
|
|
|
// Barrier 1: transition this frame's shadow map into writable depth layout.
|
2026-02-21 19:41:21 -08:00
|
|
|
VkImageMemoryBarrier b1{};
|
|
|
|
|
b1.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
2026-03-09 22:14:32 -07:00
|
|
|
b1.oldLayout = shadowDepthLayout_[frame];
|
2026-02-21 19:41:21 -08:00
|
|
|
b1.newLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
|
|
|
|
b1.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
|
|
|
b1.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
2026-03-09 22:14:32 -07:00
|
|
|
b1.srcAccessMask = (shadowDepthLayout_[frame] == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
2026-02-22 10:23:20 -08:00
|
|
|
? VK_ACCESS_SHADER_READ_BIT
|
|
|
|
|
: 0;
|
|
|
|
|
b1.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT |
|
|
|
|
|
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
2026-03-09 22:14:32 -07:00
|
|
|
b1.image = shadowDepthImage[frame];
|
2026-02-21 19:41:21 -08:00
|
|
|
b1.subresourceRange = {VK_IMAGE_ASPECT_DEPTH_BIT, 0, 1, 0, 1};
|
2026-03-09 22:14:32 -07:00
|
|
|
VkPipelineStageFlags srcStage = (shadowDepthLayout_[frame] == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
|
2026-02-22 10:23:20 -08:00
|
|
|
? VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT
|
|
|
|
|
: VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
2026-02-21 19:41:21 -08:00
|
|
|
vkCmdPipelineBarrier(currentCmd,
|
2026-02-22 10:23:20 -08:00
|
|
|
srcStage, VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT,
|
2026-02-21 19:41:21 -08:00
|
|
|
0, 0, nullptr, 0, nullptr, 1, &b1);
|
|
|
|
|
|
|
|
|
|
// Begin shadow render pass
|
|
|
|
|
VkRenderPassBeginInfo rpInfo{};
|
|
|
|
|
rpInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
|
|
|
|
|
rpInfo.renderPass = shadowRenderPass;
|
2026-03-09 22:14:32 -07:00
|
|
|
rpInfo.framebuffer = shadowFramebuffer[frame];
|
2026-02-21 19:41:21 -08:00
|
|
|
rpInfo.renderArea = {{0, 0}, {SHADOW_MAP_SIZE, SHADOW_MAP_SIZE}};
|
|
|
|
|
VkClearValue clear{};
|
|
|
|
|
clear.depthStencil = {1.0f, 0};
|
|
|
|
|
rpInfo.clearValueCount = 1;
|
|
|
|
|
rpInfo.pClearValues = &clear;
|
|
|
|
|
vkCmdBeginRenderPass(currentCmd, &rpInfo, VK_SUBPASS_CONTENTS_INLINE);
|
|
|
|
|
|
|
|
|
|
VkViewport vp{0, 0, static_cast<float>(SHADOW_MAP_SIZE), static_cast<float>(SHADOW_MAP_SIZE), 0.0f, 1.0f};
|
|
|
|
|
vkCmdSetViewport(currentCmd, 0, 1, &vp);
|
|
|
|
|
VkRect2D sc{{0, 0}, {SHADOW_MAP_SIZE, SHADOW_MAP_SIZE}};
|
|
|
|
|
vkCmdSetScissor(currentCmd, 0, 1, &sc);
|
|
|
|
|
|
2026-02-21 19:49:50 -08:00
|
|
|
// Phase 7/8: render shadow casters
|
2026-03-06 20:38:58 -08:00
|
|
|
const float shadowCullRadius = shadowDistance_ * 1.35f;
|
2026-03-09 18:34:26 -07:00
|
|
|
if (terrainRenderer) {
|
|
|
|
|
terrainRenderer->renderShadow(currentCmd, lightSpaceMatrix, shadowCenter, shadowCullRadius);
|
|
|
|
|
}
|
2026-02-04 16:08:35 -08:00
|
|
|
if (wmoRenderer) {
|
2026-02-25 10:22:05 -08:00
|
|
|
wmoRenderer->renderShadow(currentCmd, lightSpaceMatrix, shadowCenter, shadowCullRadius);
|
2026-02-04 16:08:35 -08:00
|
|
|
}
|
2026-02-08 20:45:59 -08:00
|
|
|
if (m2Renderer) {
|
2026-02-25 10:22:05 -08:00
|
|
|
m2Renderer->renderShadow(currentCmd, lightSpaceMatrix, globalTime, shadowCenter, shadowCullRadius);
|
2026-02-21 19:41:21 -08:00
|
|
|
}
|
2026-02-21 19:49:50 -08:00
|
|
|
if (characterRenderer) {
|
2026-02-25 10:22:05 -08:00
|
|
|
characterRenderer->renderShadow(currentCmd, lightSpaceMatrix, shadowCenter, shadowCullRadius);
|
2026-02-21 19:49:50 -08:00
|
|
|
}
|
2026-02-21 19:41:21 -08:00
|
|
|
|
|
|
|
|
vkCmdEndRenderPass(currentCmd);
|
|
|
|
|
|
|
|
|
|
// Barrier 2: DEPTH_STENCIL_ATTACHMENT_OPTIMAL → SHADER_READ_ONLY_OPTIMAL
|
|
|
|
|
VkImageMemoryBarrier b2{};
|
|
|
|
|
b2.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
|
|
|
|
b2.oldLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
|
|
|
|
b2.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
|
|
|
|
b2.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
|
|
|
b2.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
|
|
|
|
b2.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
|
|
|
|
b2.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
2026-03-09 22:14:32 -07:00
|
|
|
b2.image = shadowDepthImage[frame];
|
2026-02-21 19:41:21 -08:00
|
|
|
b2.subresourceRange = {VK_IMAGE_ASPECT_DEPTH_BIT, 0, 1, 0, 1};
|
|
|
|
|
vkCmdPipelineBarrier(currentCmd,
|
|
|
|
|
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
|
|
|
|
|
0, 0, nullptr, 0, nullptr, 1, &b2);
|
2026-03-09 22:14:32 -07:00
|
|
|
shadowDepthLayout_[frame] = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
2026-02-04 16:08:35 -08:00
|
|
|
}
|
|
|
|
|
|
2026-02-02 12:24:50 -08:00
|
|
|
} // namespace rendering
|
|
|
|
|
} // namespace wowee
|