diff --git a/src/features/editScene/systems/CharacterSlotSystem.cpp b/src/features/editScene/systems/CharacterSlotSystem.cpp index 0a1b954..c8c3f8d 100644 --- a/src/features/editScene/systems/CharacterSlotSystem.cpp +++ b/src/features/editScene/systems/CharacterSlotSystem.cpp @@ -231,13 +231,21 @@ Ogre::String CharacterSlotSystem::resolveMesh( const Ogre::String &slot, const SlotSelection &sel, int outfitLevel) { - if (!sel.explicitMesh.empty()) + if (!sel.explicitMesh.empty()) { + Ogre::LogManager::getSingleton().logMessage( + "[CharacterSlotSystem] resolveMesh(" + age + "," + sex + + "," + slot + ") -> explicit: " + sel.explicitMesh); return sel.explicitMesh; + } if (!s_catalogLoaded || !s_bodyParts.contains(age) || !s_bodyParts[age].contains(sex) || - !s_bodyParts[age][sex].contains(slot)) + !s_bodyParts[age][sex].contains(slot)) { + Ogre::LogManager::getSingleton().logMessage( + "[CharacterSlotSystem] resolveMesh(" + age + "," + sex + + "," + slot + ") -> catalog miss"); return ""; + } const auto &slotEntries = s_bodyParts[age][sex][slot]; @@ -245,37 +253,55 @@ Ogre::String CharacterSlotSystem::resolveMesh( if (outfitLevel >= 2 && sel.layer2Mesh != "none" && !sel.layer2Mesh.empty()) { for (const auto &entry : slotEntries) { - if (entry.value("mesh", "") == sel.layer2Mesh) + if (entry.value("mesh", "") == sel.layer2Mesh) { + Ogre::LogManager::getSingleton().logMessage( + "[CharacterSlotSystem] resolveMesh(" + age + "," + sex + + "," + slot + ") -> layer2: " + sel.layer2Mesh); return sel.layer2Mesh; + } } } if (outfitLevel >= 1 && sel.layer1Mesh != "none" && !sel.layer1Mesh.empty()) { for (const auto &entry : slotEntries) { - if (entry.value("mesh", "") == sel.layer1Mesh) + if (entry.value("mesh", "") == sel.layer1Mesh) { + Ogre::LogManager::getSingleton().logMessage( + "[CharacterSlotSystem] resolveMesh(" + age + "," + sex + + "," + slot + ") -> layer1: " + sel.layer1Mesh); return sel.layer1Mesh; + } } } - /* Fallback to layer 0 (nude base) — prefer canonical base mesh */ - Ogre::String canonicalBase = sex + "_" + slot + ".mesh"; - Ogre::String firstLayer0; + /* Fallback to layer 0 (nude base) — prefer shortest name + * (base mesh name is shortest, combined meshes add suffixes) */ + Ogre::String bestLayer0; for (const auto &entry : slotEntries) { if (entry.value("layer", 0) == 0) { Ogre::String mesh = entry["mesh"].get(); - if (mesh == canonicalBase) - return mesh; - if (firstLayer0.empty()) - firstLayer0 = mesh; + if (bestLayer0.empty() || mesh.length() < bestLayer0.length()) + bestLayer0 = mesh; } } - if (!firstLayer0.empty()) - return firstLayer0; + if (!bestLayer0.empty()) { + Ogre::LogManager::getSingleton().logMessage( + "[CharacterSlotSystem] resolveMesh(" + age + "," + sex + + "," + slot + ") -> layer0base: " + bestLayer0); + return bestLayer0; + } /* Last resort: first available entry */ - if (!slotEntries.empty()) - return slotEntries[0]["mesh"].get(); + if (!slotEntries.empty()) { + Ogre::String mesh = slotEntries[0]["mesh"].get(); + Ogre::LogManager::getSingleton().logMessage( + "[CharacterSlotSystem] resolveMesh(" + age + "," + sex + + "," + slot + ") -> lastResort: " + mesh); + return mesh; + } + Ogre::LogManager::getSingleton().logMessage( + "[CharacterSlotSystem] resolveMesh(" + age + "," + sex + + "," + slot + ") -> empty fallback"); return ""; } @@ -291,10 +317,20 @@ void CharacterSlotSystem::update() total++; if (cs.dirty) { dirtyCount++; + Ogre::LogManager::getSingleton().logMessage( + "[CharacterSlotSystem] update: entity " + + Ogre::StringConverter::toString(e.id()) + + " is dirty, rebuilding"); buildCharacter(e, cs); cs.dirty = false; } }); + if (dirtyCount > 0) { + Ogre::LogManager::getSingleton().logMessage( + "[CharacterSlotSystem] update: total=" + + Ogre::StringConverter::toString(total) + " dirty=" + + Ogre::StringConverter::toString(dirtyCount)); + } } static const nlohmann::json *findCatalogEntry( @@ -341,6 +377,13 @@ static void ensureMeshPoseAnimation(const Ogre::String &meshName) void CharacterSlotSystem::buildCharacter(flecs::entity e, CharacterSlotsComponent &cs) { + Ogre::LogManager::getSingleton().logMessage( + "[CharacterSlotSystem] buildCharacter: age=" + cs.age + + " sex=" + cs.sex + " outfitLevel=" + + Ogre::StringConverter::toString(cs.outfitLevel) + + " slots=" + Ogre::StringConverter::toString( + (size_t)cs.slotSelections.size())); + destroyCharacterParts(e); /* Migrate old slots map to slotSelections if needed */ @@ -361,12 +404,18 @@ void CharacterSlotSystem::buildCharacter(flecs::entity e, } } - if (!e.has()) + if (!e.has()) { + Ogre::LogManager::getSingleton().logMessage( + "[CharacterSlotSystem] buildCharacter: no TransformComponent"); return; + } auto &transform = e.get_mut(); - if (!transform.node) + if (!transform.node) { + Ogre::LogManager::getSingleton().logMessage( + "[CharacterSlotSystem] buildCharacter: no transform node"); return; + } /* Determine master slot (face preferred, else first non-empty) */ Ogre::String masterSlot; @@ -390,6 +439,9 @@ void CharacterSlotSystem::buildCharacter(flecs::entity e, } if (masterSlot.empty()) { + Ogre::LogManager::getSingleton().logMessage( + "[CharacterSlotSystem] buildCharacter: masterSlot EMPTY — " + "no valid mesh for any slot"); return; } @@ -397,8 +449,16 @@ void CharacterSlotSystem::buildCharacter(flecs::entity e, cs.slotSelections[masterSlot], cs.outfitLevel); - if (masterMesh.empty()) + if (masterMesh.empty()) { + Ogre::LogManager::getSingleton().logMessage( + "[CharacterSlotSystem] buildCharacter: masterMesh empty for " + + masterSlot); return; + } + + Ogre::LogManager::getSingleton().logMessage( + "[CharacterSlotSystem] buildCharacter: masterSlot=" + masterSlot + + " masterMesh=" + masterMesh); /* Pre-create pose animation on mesh so entity knows about it */ ensureMeshPoseAnimation(masterMesh); @@ -412,6 +472,12 @@ void CharacterSlotSystem::buildCharacter(flecs::entity e, m_entities[e.id()].parts[masterSlot] = masterEnt; cs.masterEntity = masterEnt; + Ogre::LogManager::getSingleton().logMessage( + "[CharacterSlotSystem] buildCharacter: created master entity " + + masterMesh + " (submeshes=" + + Ogre::StringConverter::toString(meshPtr->getNumSubMeshes()) + + ")"); + /* Setup pose animation for shape keys */ const nlohmann::json *entry = findCatalogEntry( cs.age, cs.sex, masterSlot, masterMesh); @@ -422,7 +488,7 @@ void CharacterSlotSystem::buildCharacter(flecs::entity e, e.get_mut().dirty = true; } catch (const Ogre::Exception &ex) { Ogre::LogManager::getSingleton().logMessage( - "CharacterSlotSystem: Failed to load master mesh '" + + "[CharacterSlotSystem] buildCharacter: FAILED to load master mesh '" + masterMesh + "': " + ex.getDescription()); return; } @@ -453,11 +519,17 @@ void CharacterSlotSystem::buildCharacter(flecs::entity e, applyShapeKeys(e, partEnt, entry); } catch (const Ogre::Exception &ex) { Ogre::LogManager::getSingleton().logMessage( - "CharacterSlotSystem: Failed to load part '" + + "[CharacterSlotSystem] buildCharacter: FAILED to load part '" + slot + "' mesh '" + mesh + "': " + ex.getDescription()); } } + + Ogre::LogManager::getSingleton().logMessage( + "[CharacterSlotSystem] buildCharacter: DONE for entity " + + Ogre::StringConverter::toString(e.id()) + + " parts=" + Ogre::StringConverter::toString( + (size_t)m_entities[e.id()].parts.size())); } void CharacterSlotSystem::applyShapeKeys(flecs::entity e,