whoa/src/component/CCharacterComponent.cpp
2025-10-22 22:46:01 -05:00

597 lines
16 KiB
C++

#include "component/CCharacterComponent.hpp"
#include "component/Texture.hpp"
#include "component/Util.hpp"
#include "db/Db.hpp"
#include "gx/Device.hpp"
#include "gx/Texture.hpp"
#include "model/CM2Model.hpp"
#include "object/Types.hpp"
#include <storm/Memory.hpp>
#include <storm/String.hpp>
st_race* CCharacterComponent::s_chrVarArray;
uint32_t CCharacterComponent::s_chrVarArrayLength;
EGxTexFormat CCharacterComponent::s_gxFormat;
uint32_t CCharacterComponent::s_mipLevels;
PREP_FUNC* CCharacterComponent::s_prepFunc[];
CompSectionInfo CCharacterComponent::s_sectionInfo[];
MipBits* CCharacterComponent::s_textureBuffer;
MipBits* CCharacterComponent::s_textureBufferCompressed;
uint32_t CCharacterComponent::s_textureSize;
CompSectionInfo CCharacterComponent::s_sectionInfoRaw[] = {
{ 0, 0, 256, 128 }, // SECTION_ARM_UPPER
{ 0, 128, 256, 128 }, // SECTION_ARM_LOWER
{ 0, 256, 256, 64 }, // SECTION_HAND
{ 256, 0, 256, 128 }, // SECTION_TORSO_UPPER
{ 256, 128, 256, 64 }, // SECTION_TORSO_LOWER
{ 256, 192, 256, 128 }, // SECTION_LEG_UPPER
{ 256, 320, 256, 128 }, // SECTION_LEG_LOWER
{ 256, 448, 256, 64 }, // SECTION_FOOT
{ 0, 320, 256, 64 }, // SECTION_HEAD_UPPER
{ 0, 384, 256, 128 }, // SECTION_HEAD_LOWER
};
int32_t s_bInRenderPrep = 0;
char* s_pathEnd;
char s_path[STORM_MAX_PATH];
#define TEXTURE_INDEX(section, texture) (3 * section + texture)
CCharacterComponent* CCharacterComponent::AllocComponent() {
// TODO ObjectAlloc
return STORM_NEW(CCharacterComponent);
}
void CCharacterComponent::Initialize() {
// TODO
// TODO proper implementation
CCharacterComponent::Initialize(GxTex_Rgb565, 9, 0, 0);
}
void CCharacterComponent::Initialize(EGxTexFormat textureFormat, uint32_t textureLevel, int32_t thread, int32_t compress) {
// TODO
s_pathEnd = s_path;
CCharacterComponent::s_prepFunc[SECTION_ARM_UPPER] = &CCharacterComponent::RenderPrepAU;
CCharacterComponent::s_prepFunc[SECTION_ARM_LOWER] = &CCharacterComponent::RenderPrepAL;
CCharacterComponent::s_prepFunc[SECTION_HAND] = &CCharacterComponent::RenderPrepHA;
CCharacterComponent::s_prepFunc[SECTION_TORSO_UPPER] = &CCharacterComponent::RenderPrepTU;
CCharacterComponent::s_prepFunc[SECTION_TORSO_LOWER] = &CCharacterComponent::RenderPrepTL;
CCharacterComponent::s_prepFunc[SECTION_LEG_UPPER] = &CCharacterComponent::RenderPrepLU;
CCharacterComponent::s_prepFunc[SECTION_LEG_LOWER] = &CCharacterComponent::RenderPrepLL;
CCharacterComponent::s_prepFunc[SECTION_FOOT] = &CCharacterComponent::RenderPrepFO;
CCharacterComponent::s_prepFunc[SECTION_HEAD_UPPER] = &CCharacterComponent::RenderPrepHU;
CCharacterComponent::s_prepFunc[SECTION_HEAD_LOWER] = &CCharacterComponent::RenderPrepHL;
// TODO
// Clamp mip levels between 6 and 9
uint32_t mipLevels = std::min(std::max(textureLevel, 6u), 9u);
// Cap mip levels to 8 if compression isn't enabled
if (!compress && mipLevels > 8) {
mipLevels = 8;
}
CCharacterComponent::s_mipLevels = mipLevels;
CCharacterComponent::s_textureSize = 1 << mipLevels;
// Scale section info to match mip levels
for (int32_t i = 0; i < NUM_COMPONENT_VARIATIONS; i++) {
auto& info = CCharacterComponent::s_sectionInfo[i];
auto& infoRaw = CCharacterComponent::s_sectionInfoRaw[i];
info.pos.x = infoRaw.pos.x >> (9 - mipLevels);
info.pos.y = infoRaw.pos.y >> (9 - mipLevels);
info.size.x = infoRaw.size.x >> (9 - mipLevels);
info.size.y = infoRaw.size.y >> (9 - mipLevels);
}
// TODO
CCharacterComponent::s_gxFormat = textureFormat;
// TODO
CCharacterComponent::InitDbData();
// TODO
CCharacterComponent::s_textureBuffer = TextureAllocMippedImg(
PIXEL_ARGB8888,
CCharacterComponent::s_textureSize,
CCharacterComponent::s_textureSize
);
// TODO
}
void CCharacterComponent::InitDbData() {
uint32_t varArrayLength = (g_chrRacesDB.m_maxID + 1) * UNITSEX_NUM_SEXES;
CCharacterComponent::s_chrVarArrayLength = varArrayLength;
BuildComponentArray(varArrayLength, &CCharacterComponent::s_chrVarArray);
// TODO CountFacialFeatures(varArrayLength, &CCharacterComponent::s_characterFacialHairStylesList);
}
void CCharacterComponent::Paste(void* srcTexture, MipBits* dstMips, const C2iVector& dstPos, const C2iVector& srcPos, const C2iVector& srcSize, TCTEXTUREINFO& srcInfo, int32_t srcMipLevel) {
uint32_t dstWidth = CCharacterComponent::s_textureSize * 4;
auto srcPal = TextureCacheGetPal(srcTexture);
if (!srcPal) {
// TODO fill in with crappy green (0x00FF00FF)
return;
}
switch (srcInfo.alphaSize) {
case 0:
CCharacterComponent::PasteOpaque(
srcTexture,
srcPal,
dstMips,
dstPos,
dstWidth,
srcPos,
srcSize,
srcInfo,
srcMipLevel,
-srcMipLevel
);
break;
case 1:
CCharacterComponent::PasteTransparent1Bit(
srcTexture,
srcPal,
dstMips,
dstPos,
dstWidth,
srcPos,
srcSize,
srcInfo,
srcMipLevel,
-srcMipLevel
);
break;
case 4:
CCharacterComponent::PasteTransparent4Bit(
srcTexture,
srcPal,
dstMips,
dstPos,
dstWidth,
srcPos,
srcSize,
srcInfo,
srcMipLevel,
-srcMipLevel
);
break;
case 8:
CCharacterComponent::PasteTransparent8Bit(
srcTexture,
srcPal,
dstMips,
dstPos,
dstWidth,
srcPos,
srcSize,
srcInfo,
srcMipLevel,
-srcMipLevel
);
break;
default:
// Do nothing
break;
}
}
void CCharacterComponent::PasteFromSkin(COMPONENT_SECTIONS section, void* srcTexture, MipBits* dstMips) {
if (!TextureCacheHasMips(srcTexture)) {
return;
}
auto& sectionInfo = CCharacterComponent::s_sectionInfo[section];
TCTEXTUREINFO srcInfo;
TextureCacheGetInfo(srcTexture, srcInfo, 1);
// Skin is always opaque
srcInfo.alphaSize = 0;
if (srcInfo.width >= CCharacterComponent::s_textureSize || srcInfo.height >= CCharacterComponent::s_textureSize ) {
// Calculate source mip level appropriate for CCharacterComponent::s_textureSize
int32_t srcMipLevel = 0;
int32_t srcWidth = srcInfo.width;
while (srcWidth > CCharacterComponent::s_textureSize) {
srcWidth /= 2;
srcMipLevel++;
}
CCharacterComponent::Paste(srcTexture, dstMips, sectionInfo.pos, sectionInfo.pos, sectionInfo.size, srcInfo, srcMipLevel);
} else {
CCharacterComponent::PasteScale(srcTexture, dstMips, sectionInfo.pos, sectionInfo.pos, sectionInfo.size, srcInfo);
}
}
void CCharacterComponent::PasteOpaque(void* srcTexture, const BlpPalPixel* srcPal, MipBits* dstMips, const C2iVector& dstPos, uint32_t dstWidth, const C2iVector& srcPos, const C2iVector& srcSize, TCTEXTUREINFO& srcInfo, int32_t srcMipLevel, int32_t dstMipLevelOfs) {
// TODO
}
void CCharacterComponent::PasteScale(void* srcTexture, MipBits* dstMips, const C2iVector& a3, const C2iVector& a4, const C2iVector& a5, TCTEXTUREINFO& srcInfo) {
// TODO
}
void CCharacterComponent::PasteTransparent1Bit(void* srcTexture, const BlpPalPixel* srcPal, MipBits* dstMips, const C2iVector& dstPos, uint32_t dstWidth, const C2iVector& srcPos, const C2iVector& srcSize, TCTEXTUREINFO& srcInfo, int32_t srcMipLevel, int32_t dstMipLevelOfs) {
// TODO
}
void CCharacterComponent::PasteTransparent4Bit(void* srcTexture, const BlpPalPixel* srcPal, MipBits* dstMips, const C2iVector& dstPos, uint32_t dstWidth, const C2iVector& srcPos, const C2iVector& srcSize, TCTEXTUREINFO& srcInfo, int32_t srcMipLevel, int32_t dstMipLevelOfs) {
// TODO
}
void CCharacterComponent::PasteTransparent8Bit(void* srcTexture, const BlpPalPixel* srcPal, MipBits* dstMips, const C2iVector& dstPos, uint32_t dstWidth, const C2iVector& srcPos, const C2iVector& srcSize, TCTEXTUREINFO& srcInfo, int32_t srcMipLevel, int32_t dstMipLevelOfs) {
// TODO
}
void CCharacterComponent::RenderPrepAL(CCharacterComponent* component) {
auto skin = component->m_texture[TEXTURE_INDEX(VARIATION_SKIN, 0)];
CCharacterComponent::PasteFromSkin(SECTION_ARM_LOWER, skin, CCharacterComponent::s_textureBuffer);
// TODO
}
void CCharacterComponent::RenderPrepAU(CCharacterComponent* component) {
// TODO
}
void CCharacterComponent::RenderPrepFO(CCharacterComponent* component) {
// TODO
}
void CCharacterComponent::RenderPrepHA(CCharacterComponent* component) {
// TODO
}
void CCharacterComponent::RenderPrepHL(CCharacterComponent* component) {
// TODO
}
void CCharacterComponent::RenderPrepHU(CCharacterComponent* component) {
// TODO
}
void CCharacterComponent::RenderPrepLL(CCharacterComponent* component) {
// TODO
}
void CCharacterComponent::RenderPrepLU(CCharacterComponent* component) {
// TODO
}
void CCharacterComponent::RenderPrepTL(CCharacterComponent* component) {
// TODO
}
void CCharacterComponent::RenderPrepTU(CCharacterComponent* component) {
// TODO
}
void CCharacterComponent::UpdateBaseTexture(EGxTexCommand cmd, uint32_t width, uint32_t height, uint32_t depth, uint32_t mipLevel, void* userArg, uint32_t& texelStrideInBytes, const void*& texels) {
auto component = static_cast<CCharacterComponent*>(userArg);
switch(cmd) {
case GxTex_Lock: {
if (!s_bInRenderPrep) {
component->RenderPrepAll();
}
break;
}
case GxTex_Latch: {
if (component->m_textureFormat == GxTex_Dxt1) {
// TODO
STORM_ASSERT(false);
} else {
texelStrideInBytes = 4 * width;
}
// TODO conditional check on some member of component
auto buffer = component->m_textureFormat == GxTex_Dxt1
? CCharacterComponent::s_textureBufferCompressed
: CCharacterComponent::s_textureBuffer;
texels = buffer[mipLevel].mip[0];
break;
}
// TODO unknown command
case 3: {
// TODO
break;
}
}
}
void CCharacterComponent::CreateBaseTexture() {
auto dataFormat = this->m_textureFormat == GxTex_Dxt1
? GxTex_Dxt1
: GxTex_Argb8888;
CGxTexFlags flags = CGxTexFlags(GxTex_LinearMipLinear, 0, 0, 0, 0, 0, 1);
if (GxDevApi() == GxApi_GLL) {
flags.m_bit15 = 1;
}
auto baseTexture = TextureCreate(
CCharacterComponent::s_textureSize,
CCharacterComponent::s_textureSize,
this->m_textureFormat,
dataFormat,
flags,
this,
&CCharacterComponent::UpdateBaseTexture,
"CharacterBaseSkin",
1
);
this->m_baseTexture = baseTexture;
this->m_data.model->ReplaceTexture(1, this->m_baseTexture);
}
void CCharacterComponent::GeosRenderPrep() {
// TODO
// Default all sections (0 - 2000) to hidden
this->m_data.model->SetGeometryVisible(0, 2000, 0);
// Show base "skin" section (0)
this->m_data.model->SetGeometryVisible(0, 0, 1);
// Show selected geosets
for (int32_t i = 0; i < NUM_GEOSET; i++) {
// TODO handle 1703
this->m_data.model->SetGeometryVisible(this->m_data.geosets[i], this->m_data.geosets[i], 1);
}
// TODO
this->m_flags &= ~0x4;
}
CharSectionsRec* CCharacterComponent::GetSectionsRecord(COMPONENT_VARIATIONS sectionIndex, int32_t variationIndex, int32_t colorIndex, bool* found) {
return ComponentGetSectionsRecord(
CCharacterComponent::s_chrVarArray,
this->m_data.raceID,
this->m_data.sexID,
sectionIndex,
variationIndex,
colorIndex,
found
);
}
void CCharacterComponent::Init(ComponentData* data, const char* a3) {
if (data->model) {
data->model->Release();
}
this->m_data = *data;
// TODO
this->SetSkinColor(this->m_data.skinColorID, false, true, a3);
// TODO
}
int32_t CCharacterComponent::ItemsLoaded(int32_t a2) {
// TODO
return 1;
}
void CCharacterComponent::LoadBaseVariation(COMPONENT_VARIATIONS sectionIndex, int32_t textureIndex, int32_t variationIndex, int32_t colorIndex, COMPONENT_SECTIONS section, const char* a7) {
int32_t index = TEXTURE_INDEX(sectionIndex, textureIndex);
if (this->m_texture[index]) {
TextureCacheDestroyTexture(this->m_texture[index]);
}
auto valid = ComponentValidateBase(
CCharacterComponent::s_chrVarArray,
this->m_data.raceID,
this->m_data.sexID,
sectionIndex,
variationIndex,
colorIndex
);
if (!valid) {
return;
}
auto sectionsRec = this->GetSectionsRecord(sectionIndex, variationIndex, colorIndex, nullptr);
auto textureName = sectionsRec->m_textureName[textureIndex];
if (*textureName) {
SStrCopy(s_pathEnd, textureName);
this->m_texture[index] = TextureCacheCreateTexture(s_path);
}
this->m_sections |= 1 << section;
// TODO
this->m_flags &= ~0x8;
}
void CCharacterComponent::PrepSections() {
// TODO
}
int32_t CCharacterComponent::RenderPrep(int32_t a2) {
if (this->m_data.flags & 0x1) {
if (this->m_flags & 0x4) {
this->GeosRenderPrep();
}
return 1;
}
// TODO
if (a2) {
// TODO
this->VariationsLoaded(1);
this->ItemsLoaded(1);
this->m_flags |= 8u;
this->RenderPrepSections();
// TODO this->Sub79F820();
return 1;
}
// TODO
}
void CCharacterComponent::RenderPrepAll() {
// TODO
this->m_flags &= ~0x8;
this->VariationsLoaded(1);
this->ItemsLoaded(1);
for (uint32_t i = 0; i < NUM_COMPONENT_VARIATIONS; i++) {
CCharacterComponent::s_prepFunc[i](this);
}
// TODO
this->m_flags &= ~0x1;
// TODO dirty mask?
this->m_sections = 0;
// TODO
}
void CCharacterComponent::RenderPrepSections() {
s_bInRenderPrep = 1;
if (this->m_flags & 0x4) {
this->GeosRenderPrep();
}
if (!this->m_baseTexture) {
this->CreateBaseTexture();
}
this->PrepSections();
this->m_flags &= ~0x1;
// TODO
s_bInRenderPrep = 0;
}
void CCharacterComponent::ReplaceExtraSkinTexture(const char* a2) {
// TODO
}
void CCharacterComponent::SetFace(int32_t faceID, bool a3, const char* a4) {
// TODO
}
void CCharacterComponent::SetSkinColor(int32_t skinColorID, bool a3, bool a4, const char* a5) {
bool isNPC = this->m_data.flags & 0x1;
this->m_data.skinColorID = skinColorID;
if (isNPC) {
return;
}
if (!ComponentValidateBase(CCharacterComponent::s_chrVarArray, this->m_data.raceID, this->m_data.sexID, VARIATION_SKIN, 0, skinColorID)) {
return;
}
auto numColors = ComponentGetNumColors(
CCharacterComponent::s_chrVarArray,
this->m_data.raceID,
this->m_data.sexID,
VARIATION_SKIN,
0
);
auto sectionsRec = ComponentGetSectionsRecord(
CCharacterComponent::s_chrVarArray,
this->m_data.raceID,
this->m_data.sexID,
VARIATION_SKIN,
0,
skinColorID,
nullptr
);
if (skinColorID < numColors && sectionsRec && !(sectionsRec->m_flags & 0x8)) {
// TODO underwear
}
this->ReplaceExtraSkinTexture(a5);
this->LoadBaseVariation(
VARIATION_SKIN,
0,
0,
this->m_data.skinColorID,
SECTION_TORSO_UPPER,
a5
);
this->SetFace(this->m_data.faceID, a3, a5);
// TODO
}
int32_t CCharacterComponent::VariationsLoaded(int32_t a2) {
TCTEXTUREINFO info;
for (int32_t v = 0; v < NUM_COMPONENT_VARIATIONS; v++) {
for (int32_t t = 0; t < 3; t++) {
auto texture = this->m_texture[TEXTURE_INDEX(v, t)];
if (texture && !TextureCacheGetInfo(texture, info, a2)) {
return 0;
}
}
}
this->m_flags &= ~0x2;
return 1;
}