mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2025-10-11 09:58:05 +00:00
LXFML SPLITTING
Included test file
This commit is contained in:
@@ -5,6 +5,10 @@
|
||||
#include "TinyXmlUtils.h"
|
||||
|
||||
#include <ranges>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <functional>
|
||||
#include <sstream>
|
||||
|
||||
Lxfml::Result Lxfml::NormalizePosition(const std::string_view data, const NiPoint3& curPosition) {
|
||||
Result toReturn;
|
||||
@@ -128,3 +132,270 @@ Lxfml::Result Lxfml::NormalizePosition(const std::string_view data, const NiPoin
|
||||
toReturn.center = newRootPos;
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
// Deep-clone an XMLElement (attributes, text, and child elements) into a target document
|
||||
static tinyxml2::XMLElement* CloneElementDeep(const tinyxml2::XMLElement* src, tinyxml2::XMLDocument& dstDoc) {
|
||||
if (!src) return nullptr;
|
||||
auto* dst = dstDoc.NewElement(src->Name());
|
||||
|
||||
// copy attributes
|
||||
for (const tinyxml2::XMLAttribute* attr = src->FirstAttribute(); attr; attr = attr->Next()) {
|
||||
dst->SetAttribute(attr->Name(), attr->Value());
|
||||
}
|
||||
|
||||
// copy children (elements and text)
|
||||
for (const tinyxml2::XMLNode* child = src->FirstChild(); child; child = child->NextSibling()) {
|
||||
if (const tinyxml2::XMLElement* childElem = child->ToElement()) {
|
||||
dst->InsertEndChild(CloneElementDeep(childElem, dstDoc));
|
||||
} else if (const tinyxml2::XMLText* txt = child->ToText()) {
|
||||
auto* n = dstDoc.NewText(txt->Value());
|
||||
dst->InsertEndChild(n);
|
||||
} else if (const tinyxml2::XMLComment* c = child->ToComment()) {
|
||||
auto* n = dstDoc.NewComment(c->Value());
|
||||
dst->InsertEndChild(n);
|
||||
}
|
||||
}
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
std::vector<Lxfml::Result> Lxfml::Split(const std::string_view data, const NiPoint3& curPosition) {
|
||||
std::vector<Result> results;
|
||||
tinyxml2::XMLDocument doc;
|
||||
const auto err = doc.Parse(data.data());
|
||||
if (err != tinyxml2::XML_SUCCESS) {
|
||||
LOG("Failed to parse xml %s.", StringifiedEnum::ToString(err).data());
|
||||
return results;
|
||||
}
|
||||
|
||||
auto* lxfml = doc.FirstChildElement("LXFML");
|
||||
if (!lxfml) {
|
||||
LOG("Failed to find LXFML element.");
|
||||
return results;
|
||||
}
|
||||
|
||||
// Build maps: partRef -> Part element, partRef -> Brick element, boneRef -> partRef, brickRef -> Brick element
|
||||
std::unordered_map<std::string, tinyxml2::XMLElement*> partRefToPart;
|
||||
std::unordered_map<std::string, tinyxml2::XMLElement*> partRefToBrick;
|
||||
std::unordered_map<std::string, std::string> boneRefToPartRef;
|
||||
std::unordered_map<std::string, tinyxml2::XMLElement*> brickByRef;
|
||||
|
||||
auto* bricksParent = lxfml->FirstChildElement("Bricks");
|
||||
if (bricksParent) {
|
||||
for (auto* brick = bricksParent->FirstChildElement("Brick"); brick; brick = brick->NextSiblingElement("Brick")) {
|
||||
const char* brickRef = brick->Attribute("refID");
|
||||
if (brickRef) brickByRef.emplace(std::string(brickRef), brick);
|
||||
for (auto* part = brick->FirstChildElement("Part"); part; part = part->NextSiblingElement("Part")) {
|
||||
const char* partRef = part->Attribute("refID");
|
||||
if (partRef) {
|
||||
partRefToPart.emplace(std::string(partRef), part);
|
||||
partRefToBrick.emplace(std::string(partRef), brick);
|
||||
}
|
||||
auto* bone = part->FirstChildElement("Bone");
|
||||
if (bone) {
|
||||
const char* boneRef = bone->Attribute("refID");
|
||||
if (boneRef) boneRefToPartRef.emplace(std::string(boneRef), partRef ? std::string(partRef) : std::string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Collect RigidSystem elements
|
||||
std::vector<tinyxml2::XMLElement*> rigidSystems;
|
||||
auto* rigidSystemsParent = lxfml->FirstChildElement("RigidSystems");
|
||||
if (rigidSystemsParent) {
|
||||
for (auto* rs = rigidSystemsParent->FirstChildElement("RigidSystem"); rs; rs = rs->NextSiblingElement("RigidSystem")) {
|
||||
rigidSystems.push_back(rs);
|
||||
}
|
||||
}
|
||||
|
||||
// Collect top-level groups (immediate children of GroupSystem)
|
||||
std::vector<tinyxml2::XMLElement*> groupRoots;
|
||||
auto* groupSystemsParent = lxfml->FirstChildElement("GroupSystems");
|
||||
if (groupSystemsParent) {
|
||||
for (auto* gs = groupSystemsParent->FirstChildElement("GroupSystem"); gs; gs = gs->NextSiblingElement("GroupSystem")) {
|
||||
for (auto* group = gs->FirstChildElement("Group"); group; group = group->NextSiblingElement("Group")) {
|
||||
groupRoots.push_back(group);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Track used bricks and rigidsystems
|
||||
std::unordered_set<std::string> usedBrickRefs;
|
||||
std::unordered_set<tinyxml2::XMLElement*> usedRigidSystems;
|
||||
|
||||
auto splitListAttr = [](const char* attr) {
|
||||
std::vector<std::string> out;
|
||||
if (!attr) return out;
|
||||
std::stringstream ss(attr);
|
||||
std::string token;
|
||||
while (std::getline(ss, token, ',')) {
|
||||
if (!token.empty()) out.push_back(token);
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
// Helper to create output document from sets of brick refs and rigidsystem pointers
|
||||
auto makeOutput = [&](const std::unordered_set<std::string>& bricksToInclude, const std::vector<tinyxml2::XMLElement*>& rigidSystemsToInclude, const std::vector<tinyxml2::XMLElement*>& groupsToInclude = {}) {
|
||||
tinyxml2::XMLDocument outDoc;
|
||||
outDoc.Parse(Lxfml::base.c_str());
|
||||
auto* outRoot = outDoc.FirstChildElement("LXFML");
|
||||
auto* outBricks = outRoot->FirstChildElement("Bricks");
|
||||
auto* outRigidSystems = outRoot->FirstChildElement("RigidSystems");
|
||||
auto* outGroupSystems = outRoot->FirstChildElement("GroupSystems");
|
||||
|
||||
// clone and insert bricks
|
||||
for (const auto& bref : bricksToInclude) {
|
||||
auto it = brickByRef.find(bref);
|
||||
if (it == brickByRef.end()) continue;
|
||||
tinyxml2::XMLElement* cloned = CloneElementDeep(it->second, outDoc);
|
||||
if (cloned) outBricks->InsertEndChild(cloned);
|
||||
}
|
||||
|
||||
// clone and insert rigidsystems
|
||||
for (auto* rsPtr : rigidSystemsToInclude) {
|
||||
tinyxml2::XMLElement* cloned = CloneElementDeep(rsPtr, outDoc);
|
||||
if (cloned) outRigidSystems->InsertEndChild(cloned);
|
||||
}
|
||||
|
||||
// clone and insert group(s) if requested
|
||||
if (outGroupSystems && !groupsToInclude.empty()) {
|
||||
// clear default children
|
||||
while (outGroupSystems->FirstChild()) outGroupSystems->DeleteChild(outGroupSystems->FirstChild());
|
||||
// create a GroupSystem element and append requested groups
|
||||
auto* newGS = outDoc.NewElement("GroupSystem");
|
||||
for (auto* gptr : groupsToInclude) {
|
||||
tinyxml2::XMLElement* clonedG = CloneElementDeep(gptr, outDoc);
|
||||
if (clonedG) newGS->InsertEndChild(clonedG);
|
||||
}
|
||||
outGroupSystems->InsertEndChild(newGS);
|
||||
}
|
||||
|
||||
// Print to string
|
||||
tinyxml2::XMLPrinter printer;
|
||||
outDoc.Print(&printer);
|
||||
// Normalize position and compute center using existing helper
|
||||
auto normalized = NormalizePosition(printer.CStr(), curPosition);
|
||||
return normalized;
|
||||
};
|
||||
|
||||
// 1) Process groups (each top-level Group becomes one output; nested groups are included)
|
||||
for (auto* groupRoot : groupRoots) {
|
||||
// collect all partRefs in this group's subtree
|
||||
std::unordered_set<std::string> partRefs;
|
||||
std::function<void(const tinyxml2::XMLElement*)> collectParts = [&](const tinyxml2::XMLElement* g) {
|
||||
if (!g) return;
|
||||
const char* partAttr = g->Attribute("partRefs");
|
||||
if (partAttr) {
|
||||
for (auto& tok : splitListAttr(partAttr)) partRefs.insert(tok);
|
||||
}
|
||||
for (auto* child = g->FirstChildElement("Group"); child; child = child->NextSiblingElement("Group")) collectParts(child);
|
||||
};
|
||||
collectParts(groupRoot);
|
||||
|
||||
// Build initial sets of bricks and boneRefs
|
||||
std::unordered_set<std::string> bricksIncluded;
|
||||
std::unordered_set<std::string> boneRefsIncluded;
|
||||
for (const auto& pref : partRefs) {
|
||||
auto pit = partRefToBrick.find(pref);
|
||||
if (pit != partRefToBrick.end()) {
|
||||
const char* bref = pit->second->Attribute("refID");
|
||||
if (bref) bricksIncluded.insert(std::string(bref));
|
||||
}
|
||||
auto partIt = partRefToPart.find(pref);
|
||||
if (partIt != partRefToPart.end()) {
|
||||
auto* bone = partIt->second->FirstChildElement("Bone");
|
||||
if (bone) {
|
||||
const char* bref = bone->Attribute("refID");
|
||||
if (bref) boneRefsIncluded.insert(std::string(bref));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Iteratively include any RigidSystems that reference any boneRefsIncluded
|
||||
bool changed = true;
|
||||
std::vector<tinyxml2::XMLElement*> rigidSystemsToInclude;
|
||||
while (changed) {
|
||||
changed = false;
|
||||
for (auto* rs : rigidSystems) {
|
||||
if (usedRigidSystems.find(rs) != usedRigidSystems.end()) continue;
|
||||
// parse boneRefs of this rigid system (from its <Rigid> children)
|
||||
bool intersects = false;
|
||||
std::vector<std::string> rsBoneRefs;
|
||||
for (auto* rigid = rs->FirstChildElement("Rigid"); rigid; rigid = rigid->NextSiblingElement("Rigid")) {
|
||||
const char* battr = rigid->Attribute("boneRefs");
|
||||
if (!battr) continue;
|
||||
for (auto& tok : splitListAttr(battr)) {
|
||||
rsBoneRefs.push_back(tok);
|
||||
if (boneRefsIncluded.find(tok) != boneRefsIncluded.end()) intersects = true;
|
||||
}
|
||||
}
|
||||
if (!intersects) continue;
|
||||
// include this rigid system and all boneRefs it references
|
||||
usedRigidSystems.insert(rs);
|
||||
rigidSystemsToInclude.push_back(rs);
|
||||
for (const auto& br : rsBoneRefs) {
|
||||
boneRefsIncluded.insert(br);
|
||||
auto bpIt = boneRefToPartRef.find(br);
|
||||
if (bpIt != boneRefToPartRef.end()) {
|
||||
auto partRef = bpIt->second;
|
||||
auto pbIt = partRefToBrick.find(partRef);
|
||||
if (pbIt != partRefToBrick.end()) {
|
||||
const char* bref = pbIt->second->Attribute("refID");
|
||||
if (bref && bricksIncluded.insert(std::string(bref)).second) changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// also include bricks for any newly discovered parts
|
||||
}
|
||||
}
|
||||
|
||||
// include bricks from bricksIncluded into used set
|
||||
for (const auto& b : bricksIncluded) usedBrickRefs.insert(b);
|
||||
|
||||
// make output doc and push result (include this group's XML)
|
||||
std::vector<tinyxml2::XMLElement*> groupsVec{ groupRoot };
|
||||
auto normalized = makeOutput(bricksIncluded, rigidSystemsToInclude, groupsVec);
|
||||
results.push_back(normalized);
|
||||
}
|
||||
|
||||
// 2) Process remaining RigidSystems (each becomes its own file)
|
||||
for (auto* rs : rigidSystems) {
|
||||
if (usedRigidSystems.find(rs) != usedRigidSystems.end()) continue;
|
||||
std::unordered_set<std::string> bricksIncluded;
|
||||
// collect boneRefs referenced by this rigid system
|
||||
for (auto* rigid = rs->FirstChildElement("Rigid"); rigid; rigid = rigid->NextSiblingElement("Rigid")) {
|
||||
const char* battr = rigid->Attribute("boneRefs");
|
||||
if (!battr) continue;
|
||||
for (auto& tok : splitListAttr(battr)) {
|
||||
auto bpIt = boneRefToPartRef.find(tok);
|
||||
if (bpIt != boneRefToPartRef.end()) {
|
||||
auto partRef = bpIt->second;
|
||||
auto pbIt = partRefToBrick.find(partRef);
|
||||
if (pbIt != partRefToBrick.end()) {
|
||||
const char* bref = pbIt->second->Attribute("refID");
|
||||
if (bref) bricksIncluded.insert(std::string(bref));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// mark used
|
||||
for (const auto& b : bricksIncluded) usedBrickRefs.insert(b);
|
||||
usedRigidSystems.insert(rs);
|
||||
|
||||
std::vector<tinyxml2::XMLElement*> rsVec{ rs };
|
||||
auto normalized = makeOutput(bricksIncluded, rsVec);
|
||||
results.push_back(normalized);
|
||||
}
|
||||
|
||||
// 3) Any remaining bricks not included become their own files
|
||||
for (const auto& [bref, brickPtr] : brickByRef) {
|
||||
if (usedBrickRefs.find(bref) != usedBrickRefs.end()) continue;
|
||||
std::unordered_set<std::string> bricksIncluded{ bref };
|
||||
auto normalized = makeOutput(bricksIncluded, {});
|
||||
results.push_back(normalized);
|
||||
usedBrickRefs.insert(bref);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
Reference in New Issue
Block a user