mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-07-31 09:49:53 -04:00
Two bugs, one symptom each. attachWeapons() ran at the end of applyEquipment(), past an early return taken by any character whose body skin could not be composited. Those characters showed no weapon at all, and kept the previously selected character's weapon and enchant, because detaching happens in attachWeapons() too. Weapon attachment depends on nothing in the geoset/skin work, so do it first and unconditionally. Weapon and effect models were also loaded under fixed ids. CharacterRenderer's model cache is keyed by id and skips loading when the id is present, so every character after the first was handed the first one's weapon model. Key the id by the asset path instead. Also drop the renderUnlit plumbing added for the backdrop: preview instances take the simple-texture shader path, which never reaches the lighting branch it changed, so it was a no-op.
149 lines
5.9 KiB
C++
149 lines
5.9 KiB
C++
#pragma once
|
|
|
|
#include "game/character.hpp"
|
|
#include <vulkan/vulkan.h>
|
|
#include <vk_mem_alloc.h>
|
|
#include <memory>
|
|
#include <cstdint>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
#include <vector>
|
|
|
|
namespace wowee {
|
|
namespace pipeline { class AssetManager; struct M2Model; }
|
|
namespace rendering {
|
|
|
|
class CharacterRenderer;
|
|
class Camera;
|
|
class VkContext;
|
|
class VkTexture;
|
|
class VkRenderTarget;
|
|
|
|
class CharacterPreview {
|
|
public:
|
|
CharacterPreview();
|
|
~CharacterPreview();
|
|
|
|
bool initialize(pipeline::AssetManager* am);
|
|
void shutdown();
|
|
|
|
bool loadCharacter(game::Race race, game::Gender gender,
|
|
uint8_t skin, uint8_t face,
|
|
uint8_t hairStyle, uint8_t hairColor,
|
|
uint8_t facialHair, bool useFemaleModel = false);
|
|
|
|
// Apply equipment overlays/geosets using SMSG_CHAR_ENUM equipment data (ItemDisplayInfo.dbc).
|
|
bool applyEquipment(const std::vector<game::EquipmentItem>& equipment);
|
|
|
|
void update(float deltaTime);
|
|
void render();
|
|
void rotate(float yawDelta);
|
|
|
|
// Off-screen composite pass — call from Renderer::beginFrame() before main render pass
|
|
void compositePass(VkCommandBuffer cmd, uint32_t frameIndex);
|
|
|
|
// Mark that the preview needs compositing this frame (call from UI each frame)
|
|
void requestComposite() { compositeRequested_ = true; }
|
|
|
|
// Returns the ImGui texture handle. Returns VK_NULL_HANDLE until the first
|
|
// compositePass has run (image is in UNDEFINED layout before that).
|
|
VkDescriptorSet getTextureId() const { return compositeRendered_ ? imguiTextureId_ : VK_NULL_HANDLE; }
|
|
int getWidth() const { return fboWidth_; }
|
|
int getHeight() const { return fboHeight_; }
|
|
|
|
CharacterRenderer* getCharacterRenderer() { return charRenderer_.get(); }
|
|
uint32_t getInstanceId() const { return instanceId_; }
|
|
uint32_t getModelId() const { return PREVIEW_MODEL_ID; }
|
|
bool isModelLoaded() const { return modelLoaded_; }
|
|
|
|
private:
|
|
struct FacialHairGeosets {
|
|
uint16_t geoset100 = 0;
|
|
uint16_t geoset300 = 0;
|
|
uint16_t geoset200 = 0;
|
|
};
|
|
|
|
void createFBO();
|
|
void destroyFBO();
|
|
void ensureAppearanceGeosetsLoaded();
|
|
std::unordered_set<uint16_t> buildBaseGeosets();
|
|
uint16_t selectedHairScalpGeoset() const;
|
|
|
|
// Read an M2 (plus its .skin for WotLK-era models) through the asset manager.
|
|
bool loadPreviewM2(const std::string& m2Path, pipeline::M2Model& outModel);
|
|
// Hang the character's weapons off the preview model's hand attachments.
|
|
void attachWeapons(const std::vector<game::EquipmentItem>& equipment);
|
|
// Put the weapon's enchant glint on it (char enum reports the ItemVisual id directly).
|
|
void attachWeaponEnchantVisual(uint32_t attachmentId, uint32_t itemVisualId);
|
|
// Load the race's glue scene (Stormwind for humans, Orgrimmar for orcs, ...) as a backdrop.
|
|
void loadRacialBackdrop(game::Race race);
|
|
|
|
pipeline::AssetManager* assetManager_ = nullptr;
|
|
VkContext* vkCtx_ = nullptr;
|
|
std::unique_ptr<CharacterRenderer> charRenderer_;
|
|
std::unique_ptr<Camera> camera_;
|
|
|
|
// Off-screen render target (color + depth)
|
|
std::unique_ptr<VkRenderTarget> renderTarget_;
|
|
|
|
// Per-frame UBO for preview camera/lighting (double-buffered)
|
|
static constexpr uint32_t MAX_FRAMES = 2;
|
|
VkDescriptorPool previewDescPool_ = VK_NULL_HANDLE;
|
|
VkBuffer previewUBO_[MAX_FRAMES] = {};
|
|
VmaAllocation previewUBOAlloc_[MAX_FRAMES] = {};
|
|
void* previewUBOMapped_[MAX_FRAMES] = {};
|
|
VkDescriptorSet previewPerFrameSet_[MAX_FRAMES] = {};
|
|
|
|
// Dummy 1x1 depth texture for shadow map placeholder (sampler2DShadow compatible)
|
|
VkImage dummyShadowImage_ = VK_NULL_HANDLE;
|
|
VkImageView dummyShadowView_ = VK_NULL_HANDLE;
|
|
VmaAllocation dummyShadowAlloc_ = VK_NULL_HANDLE;
|
|
VkSampler dummyShadowSampler_ = VK_NULL_HANDLE; // owned by VkContext sampler cache
|
|
|
|
// ImGui texture handle for displaying the preview (VkDescriptorSet in Vulkan backend)
|
|
VkDescriptorSet imguiTextureId_ = VK_NULL_HANDLE;
|
|
|
|
// 4:5 portrait aspect ratio — taller than wide to show full character body
|
|
// from head to feet in the character creation/selection screen. Rendered at
|
|
// roughly the size it is displayed at, so the larger panel is not upscaled mush.
|
|
static constexpr int fboWidth_ = 640;
|
|
static constexpr int fboHeight_ = 800;
|
|
|
|
static constexpr uint32_t PREVIEW_MODEL_ID = 9999;
|
|
static constexpr uint32_t PREVIEW_BACKDROP_MODEL_ID = 9996;
|
|
|
|
// CharacterRenderer::loadModel() keeps a model cache keyed by id and skips
|
|
// loading when the id is already present, so a fixed id per hand would hand
|
|
// every subsequent character the first character's weapon. Key the id by what
|
|
// is actually being loaded instead: same weapon reuses the model, different
|
|
// weapon gets its own.
|
|
uint32_t previewModelIdFor(const std::string& assetKey);
|
|
std::unordered_map<std::string, uint32_t> previewModelIds_;
|
|
uint32_t nextPreviewModelId_ = 20000;
|
|
uint32_t backdropInstanceId_ = 0;
|
|
int backdropRace_ = -1; // race whose glue scene is currently loaded (-1 = none)
|
|
uint32_t instanceId_ = 0;
|
|
bool modelLoaded_ = false;
|
|
bool compositeRequested_ = false;
|
|
bool compositeRendered_ = false; // True after first successful compositePass
|
|
float modelYaw_ = 90.0f;
|
|
|
|
// Cached info from loadCharacter() for later recompositing.
|
|
game::Race race_ = game::Race::HUMAN;
|
|
game::Gender gender_ = game::Gender::MALE;
|
|
bool useFemaleModel_ = false;
|
|
uint8_t hairStyle_ = 0;
|
|
uint8_t facialHair_ = 0;
|
|
std::string bodySkinPath_;
|
|
std::vector<std::string> baseLayers_; // face + underwear, etc.
|
|
uint32_t skinTextureSlotIndex_ = 0;
|
|
|
|
bool appearanceGeosetsLoaded_ = false;
|
|
std::unordered_map<uint32_t, uint16_t> hairGeosetMap_;
|
|
std::unordered_map<uint32_t, FacialHairGeosets> facialHairGeosetMap_;
|
|
};
|
|
|
|
} // namespace rendering
|
|
} // namespace wowee
|