#include "component/CCharacterComponent.hpp" #include "component/Texture.hpp" #include "component/Util.hpp" #include "db/Db.hpp" #include "gx/Blp.hpp" #include "gx/Device.hpp" #include "gx/Texture.hpp" #include "model/CM2Model.hpp" #include "object/Types.hpp" #include #include 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_SECTIONS; 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) { // Prepare first mip level C2iVector curSrcSize = srcSize; C2iVector curSrcPos = srcPos; C2iVector curDstPos = dstPos; uint32_t curSrcWidth = srcInfo.width >> srcMipLevel; uint32_t curDstWidth = dstWidth; // Paste texture for each mip level for (int32_t curMipLevel = srcMipLevel; curMipLevel < srcInfo.mipCount; curMipLevel++) { auto srcMip = TextureCacheGetMip(srcTexture, curMipLevel); auto srcRow = &srcMip[(curSrcPos.y * curSrcWidth) + curSrcPos.x]; auto dstMip = reinterpret_cast(dstMips[curMipLevel + dstMipLevelOfs].mip[0]); auto dstRow = &dstMip[(curDstPos.y * curDstWidth) + (curDstPos.x * 4)]; // Calculate the end of the source region auto srcEnd = srcRow + curSrcWidth * curSrcSize.y; // Copy each row while (srcRow < srcEnd) { auto dst = reinterpret_cast(dstRow); for (uint32_t x = 0; x < curSrcSize.x; x++) { // Get palette entry auto& src = srcPal[srcRow[x]]; // Copy color from palette entry dst->b = src.b; dst->g = src.g; dst->r = src.r; dst->a = 0xFF; dst++; } // Move to next row srcRow += curSrcWidth; dstRow += curDstWidth; } // Prepare next mip level curSrcSize.x = std::max(1, curSrcSize.x / 2); curSrcSize.y = std::max(1, curSrcSize.y / 2); curSrcPos.x /= 2; curSrcPos.y /= 2; curDstPos.x /= 2; curDstPos.y /= 2; curDstWidth /= 2; curSrcWidth /= 2; } } 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) { auto skin = component->m_texture[TEXTURE_INDEX(VARIATION_SKIN, 0)]; CCharacterComponent::PasteFromSkin(SECTION_ARM_UPPER, skin, CCharacterComponent::s_textureBuffer); // TODO } void CCharacterComponent::RenderPrepFO(CCharacterComponent* component) { auto skin = component->m_texture[TEXTURE_INDEX(VARIATION_SKIN, 0)]; CCharacterComponent::PasteFromSkin(SECTION_FOOT, skin, CCharacterComponent::s_textureBuffer); // TODO } void CCharacterComponent::RenderPrepHA(CCharacterComponent* component) { auto skin = component->m_texture[TEXTURE_INDEX(VARIATION_SKIN, 0)]; CCharacterComponent::PasteFromSkin(SECTION_HAND, skin, CCharacterComponent::s_textureBuffer); // TODO } void CCharacterComponent::RenderPrepHL(CCharacterComponent* component) { auto sectionsRec = ComponentGetSectionsRecord( CCharacterComponent::s_chrVarArray, component->m_data.raceID, component->m_data.sexID, VARIATION_SKIN, 0, component->m_data.skinColorID, nullptr ); if (sectionsRec && sectionsRec->m_flags & 0x8) { auto skin = component->m_texture[TEXTURE_INDEX(VARIATION_SKIN, 0)]; CCharacterComponent::PasteFromSkin(SECTION_HEAD_LOWER, skin, CCharacterComponent::s_textureBuffer); } // TODO } void CCharacterComponent::RenderPrepHU(CCharacterComponent* component) { auto sectionsRec = ComponentGetSectionsRecord( CCharacterComponent::s_chrVarArray, component->m_data.raceID, component->m_data.sexID, VARIATION_SKIN, 0, component->m_data.skinColorID, nullptr ); if (sectionsRec && sectionsRec->m_flags & 0x8) { auto skin = component->m_texture[TEXTURE_INDEX(VARIATION_SKIN, 0)]; CCharacterComponent::PasteFromSkin(SECTION_HEAD_UPPER, skin, CCharacterComponent::s_textureBuffer); } // TODO } void CCharacterComponent::RenderPrepLL(CCharacterComponent* component) { auto skin = component->m_texture[TEXTURE_INDEX(VARIATION_SKIN, 0)]; CCharacterComponent::PasteFromSkin(SECTION_LEG_LOWER, skin, CCharacterComponent::s_textureBuffer); // TODO } void CCharacterComponent::RenderPrepLU(CCharacterComponent* component) { auto skin = component->m_texture[TEXTURE_INDEX(VARIATION_SKIN, 0)]; CCharacterComponent::PasteFromSkin(SECTION_LEG_UPPER, skin, CCharacterComponent::s_textureBuffer); // TODO } void CCharacterComponent::RenderPrepTL(CCharacterComponent* component) { auto skin = component->m_texture[TEXTURE_INDEX(VARIATION_SKIN, 0)]; CCharacterComponent::PasteFromSkin(SECTION_TORSO_LOWER, skin, CCharacterComponent::s_textureBuffer); // TODO } void CCharacterComponent::RenderPrepTU(CCharacterComponent* component) { auto skin = component->m_texture[TEXTURE_INDEX(VARIATION_SKIN, 0)]; CCharacterComponent::PasteFromSkin(SECTION_TORSO_UPPER, skin, CCharacterComponent::s_textureBuffer); // 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(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_SECTIONS; 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)) { auto underwearRec = this->GetSectionsRecord(VARIATION_UNDERWEAR, 0, skinColorID, nullptr); auto t0 = TEXTURE_INDEX(VARIATION_UNDERWEAR, 0); auto t1 = TEXTURE_INDEX(VARIATION_UNDERWEAR, 1); if (this->m_texture[t0]) { TextureCacheDestroyTexture(this->m_texture[t0]); this->m_texture[t0] = nullptr; } if (this->m_texture[t1]) { TextureCacheDestroyTexture(this->m_texture[t1]); this->m_texture[t1] = nullptr; } if (*underwearRec->m_textureName[0]) { SStrCopy(s_pathEnd, underwearRec->m_textureName[0]); this->m_texture[t0] = TextureCacheCreateTexture(s_path); STORM_ASSERT(this->m_texture[t0]); } if (*underwearRec->m_textureName[1]) { SStrCopy(s_pathEnd, underwearRec->m_textureName[1]); this->m_texture[t1] = TextureCacheCreateTexture(s_path); STORM_ASSERT(this->m_texture[t1]); } } 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; }