Add 3D character model preview to character creation screen

Render an animated M2 character model in the creation screen using a
dedicated CharacterRenderer with an offscreen FBO. Preview updates on
race/gender/appearance changes, supports mouse-drag rotation, and
composites skin, face, hair scalp, and underwear textures from
CharSections.dbc. Extracts getPlayerModelPath() to a shared free
function in game/character.
This commit is contained in:
Kelsi 2026-02-05 14:58:45 -08:00
parent 48d2808872
commit 060f2bbb9f
6 changed files with 631 additions and 22 deletions

View file

@ -133,5 +133,10 @@ const char* getClassName(Class characterClass);
*/
const char* getGenderName(Gender gender);
/**
* Get M2 model path for a given race and gender
*/
std::string getPlayerModelPath(Race race, Gender gender);
} // namespace game
} // namespace wowee

View file

@ -0,0 +1,57 @@
#pragma once
#include "game/character.hpp"
#include <GL/glew.h>
#include <memory>
#include <cstdint>
namespace wowee {
namespace pipeline { class AssetManager; }
namespace rendering {
class CharacterRenderer;
class Camera;
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);
void update(float deltaTime);
void render();
void rotate(float yawDelta);
GLuint getTextureId() const { return colorTexture_; }
int getWidth() const { return fboWidth_; }
int getHeight() const { return fboHeight_; }
private:
void createFBO();
void destroyFBO();
pipeline::AssetManager* assetManager_ = nullptr;
std::unique_ptr<CharacterRenderer> charRenderer_;
std::unique_ptr<Camera> camera_;
GLuint fbo_ = 0;
GLuint colorTexture_ = 0;
GLuint depthRenderbuffer_ = 0;
static constexpr int fboWidth_ = 400;
static constexpr int fboHeight_ = 500;
static constexpr uint32_t PREVIEW_MODEL_ID = 9999;
uint32_t instanceId_ = 0;
bool modelLoaded_ = false;
float modelYaw_ = 180.0f;
};
} // namespace rendering
} // namespace wowee

View file

@ -6,21 +6,27 @@
#include <string>
#include <functional>
#include <vector>
#include <memory>
namespace wowee {
namespace game { class GameHandler; }
namespace pipeline { class AssetManager; }
namespace rendering { class CharacterPreview; }
namespace ui {
class CharacterCreateScreen {
public:
CharacterCreateScreen();
~CharacterCreateScreen();
void render(game::GameHandler& gameHandler);
void update(float deltaTime);
void setOnCreate(std::function<void(const game::CharCreateData&)> cb) { onCreate = std::move(cb); }
void setOnCancel(std::function<void()> cb) { onCancel = std::move(cb); }
void setStatus(const std::string& msg, bool isError = false);
void reset();
void initializePreview(pipeline::AssetManager* am);
private:
char nameBuffer[13] = {}; // WoW max name = 12 chars + null
@ -36,6 +42,20 @@ private:
std::function<void(const game::CharCreateData&)> onCreate;
std::function<void()> onCancel;
// 3D model preview
std::unique_ptr<rendering::CharacterPreview> preview_;
int prevRaceIndex_ = -1;
int prevGenderIndex_ = -1;
int prevSkin_ = -1;
int prevFace_ = -1;
int prevHairStyle_ = -1;
int prevHairColor_ = -1;
int prevFacialHair_ = -1;
bool draggingPreview_ = false;
float dragStartX_ = 0.0f;
void updatePreviewIfNeeded();
};
} // namespace ui