mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2025-10-14 03:19:56 +00:00
feat: lxfml splitting for bbb (#1877)
* LXFML SPLITTING Included test file * move base to global namespace * wip need to test * update last fixes * update world sending bbb to be more efficient * Address feedback form Emo in doscord * Make LXFML class for robust and add more tests to edge cases and malformed data * get rid of the string copy and make the deep clone have a recursive limit * cleanup tests * fix test file locations * fix file path * KISS * add cmakelists * fix typos * NL @ EOF * tabs and split out to func * naming standard
This commit is contained in:
@@ -5,13 +5,43 @@
|
|||||||
#include "TinyXmlUtils.h"
|
#include "TinyXmlUtils.h"
|
||||||
|
|
||||||
#include <ranges>
|
#include <ranges>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <functional>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
// The base LXFML xml file to use when creating new models.
|
||||||
|
std::string g_base = R"(<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
|
||||||
|
<LXFML versionMajor="5" versionMinor="0">
|
||||||
|
<Meta>
|
||||||
|
<Application name="LEGO Universe" versionMajor="0" versionMinor="0"/>
|
||||||
|
<Brand name="LEGOUniverse"/>
|
||||||
|
<BrickSet version="457"/>
|
||||||
|
</Meta>
|
||||||
|
<Bricks>
|
||||||
|
</Bricks>
|
||||||
|
<RigidSystems>
|
||||||
|
</RigidSystems>
|
||||||
|
<GroupSystems>
|
||||||
|
<GroupSystem>
|
||||||
|
</GroupSystem>
|
||||||
|
</GroupSystems>
|
||||||
|
</LXFML>)";
|
||||||
|
}
|
||||||
|
|
||||||
Lxfml::Result Lxfml::NormalizePosition(const std::string_view data, const NiPoint3& curPosition) {
|
Lxfml::Result Lxfml::NormalizePosition(const std::string_view data, const NiPoint3& curPosition) {
|
||||||
Result toReturn;
|
Result toReturn;
|
||||||
|
|
||||||
|
// Handle empty or invalid input
|
||||||
|
if (data.empty()) {
|
||||||
|
return toReturn;
|
||||||
|
}
|
||||||
|
|
||||||
tinyxml2::XMLDocument doc;
|
tinyxml2::XMLDocument doc;
|
||||||
const auto err = doc.Parse(data.data());
|
// Use length-based parsing to avoid expensive string copy
|
||||||
|
const auto err = doc.Parse(data.data(), data.size());
|
||||||
if (err != tinyxml2::XML_SUCCESS) {
|
if (err != tinyxml2::XML_SUCCESS) {
|
||||||
LOG("Failed to parse xml %s.", StringifiedEnum::ToString(err).data());
|
|
||||||
return toReturn;
|
return toReturn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,7 +50,6 @@ Lxfml::Result Lxfml::NormalizePosition(const std::string_view data, const NiPoin
|
|||||||
|
|
||||||
auto lxfml = reader["LXFML"];
|
auto lxfml = reader["LXFML"];
|
||||||
if (!lxfml) {
|
if (!lxfml) {
|
||||||
LOG("Failed to find LXFML element.");
|
|
||||||
return toReturn;
|
return toReturn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,14 +78,17 @@ Lxfml::Result Lxfml::NormalizePosition(const std::string_view data, const NiPoin
|
|||||||
// Calculate the lowest and highest points on the entire model
|
// Calculate the lowest and highest points on the entire model
|
||||||
for (const auto& transformation : transformations | std::views::values) {
|
for (const auto& transformation : transformations | std::views::values) {
|
||||||
auto split = GeneralUtils::SplitString(transformation, ',');
|
auto split = GeneralUtils::SplitString(transformation, ',');
|
||||||
if (split.size() < 12) {
|
if (split.size() < 12) continue;
|
||||||
LOG("Not enough in the split?");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto x = GeneralUtils::TryParse<float>(split[9]).value();
|
auto xOpt = GeneralUtils::TryParse<float>(split[9]);
|
||||||
auto y = GeneralUtils::TryParse<float>(split[10]).value();
|
auto yOpt = GeneralUtils::TryParse<float>(split[10]);
|
||||||
auto z = GeneralUtils::TryParse<float>(split[11]).value();
|
auto zOpt = GeneralUtils::TryParse<float>(split[11]);
|
||||||
|
|
||||||
|
if (!xOpt.has_value() || !yOpt.has_value() || !zOpt.has_value()) continue;
|
||||||
|
|
||||||
|
auto x = xOpt.value();
|
||||||
|
auto y = yOpt.value();
|
||||||
|
auto z = zOpt.value();
|
||||||
if (x < lowest.x) lowest.x = x;
|
if (x < lowest.x) lowest.x = x;
|
||||||
if (y < lowest.y) lowest.y = y;
|
if (y < lowest.y) lowest.y = y;
|
||||||
if (z < lowest.z) lowest.z = z;
|
if (z < lowest.z) lowest.z = z;
|
||||||
@@ -87,13 +119,19 @@ Lxfml::Result Lxfml::NormalizePosition(const std::string_view data, const NiPoin
|
|||||||
for (auto& transformation : transformations | std::views::values) {
|
for (auto& transformation : transformations | std::views::values) {
|
||||||
auto split = GeneralUtils::SplitString(transformation, ',');
|
auto split = GeneralUtils::SplitString(transformation, ',');
|
||||||
if (split.size() < 12) {
|
if (split.size() < 12) {
|
||||||
LOG("Not enough in the split?");
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto x = GeneralUtils::TryParse<float>(split[9]).value() - newRootPos.x + curPosition.x;
|
auto xOpt = GeneralUtils::TryParse<float>(split[9]);
|
||||||
auto y = GeneralUtils::TryParse<float>(split[10]).value() - newRootPos.y + curPosition.y;
|
auto yOpt = GeneralUtils::TryParse<float>(split[10]);
|
||||||
auto z = GeneralUtils::TryParse<float>(split[11]).value() - newRootPos.z + curPosition.z;
|
auto zOpt = GeneralUtils::TryParse<float>(split[11]);
|
||||||
|
|
||||||
|
if (!xOpt.has_value() || !yOpt.has_value() || !zOpt.has_value()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto x = xOpt.value() - newRootPos.x + curPosition.x;
|
||||||
|
auto y = yOpt.value() - newRootPos.y + curPosition.y;
|
||||||
|
auto z = zOpt.value() - newRootPos.z + curPosition.z;
|
||||||
std::stringstream stream;
|
std::stringstream stream;
|
||||||
for (int i = 0; i < 9; i++) {
|
for (int i = 0; i < 9; i++) {
|
||||||
stream << split[i];
|
stream << split[i];
|
||||||
@@ -128,3 +166,285 @@ Lxfml::Result Lxfml::NormalizePosition(const std::string_view data, const NiPoin
|
|||||||
toReturn.center = newRootPos;
|
toReturn.center = newRootPos;
|
||||||
return toReturn;
|
return toReturn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deep-clone an XMLElement (attributes, text, and child elements) into a target document
|
||||||
|
// with maximum depth protection to prevent infinite loops
|
||||||
|
static tinyxml2::XMLElement* CloneElementDeep(const tinyxml2::XMLElement* src, tinyxml2::XMLDocument& dstDoc, int maxDepth = 100) {
|
||||||
|
if (!src || maxDepth <= 0) 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()) {
|
||||||
|
// Recursively clone child elements with decremented depth
|
||||||
|
auto* clonedChild = CloneElementDeep(childElem, dstDoc, maxDepth - 1);
|
||||||
|
if (clonedChild) dst->InsertEndChild(clonedChild);
|
||||||
|
} 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;
|
||||||
|
|
||||||
|
// Handle empty or invalid input
|
||||||
|
if (data.empty()) {
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent processing extremely large inputs that could cause hangs
|
||||||
|
if (data.size() > 10000000) { // 10MB limit
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
tinyxml2::XMLDocument doc;
|
||||||
|
// Use length-based parsing to avoid expensive string copy
|
||||||
|
const auto err = doc.Parse(data.data(), data.size());
|
||||||
|
if (err != tinyxml2::XML_SUCCESS) {
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* lxfml = doc.FirstChildElement("LXFML");
|
||||||
|
if (!lxfml) {
|
||||||
|
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;
|
||||||
|
|
||||||
|
// 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(g_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
|
||||||
|
std::string xmlString = printer.CStr();
|
||||||
|
if (xmlString.size() > 5000000) { // 5MB limit for normalization
|
||||||
|
Result emptyResult;
|
||||||
|
emptyResult.lxfml = xmlString;
|
||||||
|
return emptyResult;
|
||||||
|
}
|
||||||
|
auto normalized = NormalizePosition(xmlString, 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 : GeneralUtils::SplitString(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;
|
||||||
|
int maxIterations = 1000; // Safety limit to prevent infinite loops
|
||||||
|
int iteration = 0;
|
||||||
|
while (changed && iteration < maxIterations) {
|
||||||
|
changed = false;
|
||||||
|
iteration++;
|
||||||
|
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 : GeneralUtils::SplitString(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (iteration >= maxIterations) {
|
||||||
|
// Iteration limit reached, stop processing to prevent infinite loops
|
||||||
|
// The file is likely malformed, so just skip further processing
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
// 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 : GeneralUtils::SplitString(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;
|
||||||
|
}
|
||||||
|
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "NiPoint3.h"
|
#include "NiPoint3.h"
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ namespace Lxfml {
|
|||||||
// Normalizes a LXFML model to be positioned relative to its local 0, 0, 0 rather than a game worlds 0, 0, 0.
|
// Normalizes a LXFML model to be positioned relative to its local 0, 0, 0 rather than a game worlds 0, 0, 0.
|
||||||
// Returns a struct of its new center and the updated LXFML containing these edits.
|
// Returns a struct of its new center and the updated LXFML containing these edits.
|
||||||
[[nodiscard]] Result NormalizePosition(const std::string_view data, const NiPoint3& curPosition = NiPoint3Constant::ZERO);
|
[[nodiscard]] Result NormalizePosition(const std::string_view data, const NiPoint3& curPosition = NiPoint3Constant::ZERO);
|
||||||
|
[[nodiscard]] std::vector<Result> Split(const std::string_view data, const NiPoint3& curPosition = NiPoint3Constant::ZERO);
|
||||||
|
|
||||||
// these are only for the migrations due to a bug in one of the implementations.
|
// these are only for the migrations due to a bug in one of the implementations.
|
||||||
[[nodiscard]] Result NormalizePositionOnlyFirstPart(const std::string_view data);
|
[[nodiscard]] Result NormalizePositionOnlyFirstPart(const std::string_view data);
|
||||||
|
@@ -2566,9 +2566,6 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent
|
|||||||
inStream.Read(timeTaken);
|
inStream.Read(timeTaken);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Disabled this, as it's kinda silly to do this roundabout way of storing plaintext lxfml, then recompressing
|
|
||||||
it to send it back to the client.
|
|
||||||
|
|
||||||
On DLU we had agreed that bricks wouldn't be taken anyway, but if your server decides otherwise, feel free to
|
On DLU we had agreed that bricks wouldn't be taken anyway, but if your server decides otherwise, feel free to
|
||||||
comment this back out and add the needed code to get the bricks used from lxfml and take them from the inventory.
|
comment this back out and add the needed code to get the bricks used from lxfml and take them from the inventory.
|
||||||
|
|
||||||
@@ -2582,23 +2579,6 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent
|
|||||||
|
|
||||||
//We need to get a new ID for our model first:
|
//We need to get a new ID for our model first:
|
||||||
if (!entity || !entity->GetCharacter() || !entity->GetCharacter()->GetParentUser()) return;
|
if (!entity || !entity->GetCharacter() || !entity->GetCharacter()->GetParentUser()) return;
|
||||||
const uint32_t maxRetries = 100;
|
|
||||||
uint32_t retries = 0;
|
|
||||||
bool blueprintIDExists = true;
|
|
||||||
bool modelExists = true;
|
|
||||||
|
|
||||||
// Legacy logic to check for old random IDs (regenerating these is not really feasible)
|
|
||||||
// Probably good to have this anyway in case someone messes with the last_object_id or it gets reset somehow
|
|
||||||
LWOOBJID newIDL = LWOOBJID_EMPTY;
|
|
||||||
LWOOBJID blueprintID = LWOOBJID_EMPTY;
|
|
||||||
do {
|
|
||||||
if (newIDL != LWOOBJID_EMPTY) LOG("Generating blueprintID for UGC model, collision with existing model ID: %llu", blueprintID);
|
|
||||||
newIDL = ObjectIDManager::GetPersistentID();
|
|
||||||
blueprintID = ObjectIDManager::GetPersistentID();
|
|
||||||
++retries;
|
|
||||||
blueprintIDExists = Database::Get()->GetUgcModel(blueprintID).has_value();
|
|
||||||
modelExists = Database::Get()->GetModel(newIDL).has_value();
|
|
||||||
} while ((blueprintIDExists || modelExists) && retries < maxRetries);
|
|
||||||
|
|
||||||
//We need to get the propertyID: (stolen from Wincent's propertyManagementComp)
|
//We need to get the propertyID: (stolen from Wincent's propertyManagementComp)
|
||||||
const auto& worldId = Game::zoneManager->GetZone()->GetZoneID();
|
const auto& worldId = Game::zoneManager->GetZone()->GetZoneID();
|
||||||
@@ -2615,21 +2595,53 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent
|
|||||||
std::istringstream sd0DataStream(str);
|
std::istringstream sd0DataStream(str);
|
||||||
Sd0 sd0(sd0DataStream);
|
Sd0 sd0(sd0DataStream);
|
||||||
|
|
||||||
// Uncompress the data and normalize the position
|
// Uncompress the data, split, and nornmalize the model
|
||||||
const auto asStr = sd0.GetAsStringUncompressed();
|
const auto asStr = sd0.GetAsStringUncompressed();
|
||||||
const auto [newLxfml, newCenter] = Lxfml::NormalizePosition(asStr);
|
auto splitLxfmls = Lxfml::Split(asStr);
|
||||||
|
LOG_DEBUG("Split into %zu models", splitLxfmls.size());
|
||||||
|
|
||||||
// Recompress the data and save to the database
|
CBITSTREAM;
|
||||||
sd0.FromData(reinterpret_cast<const uint8_t*>(newLxfml.data()), newLxfml.size());
|
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::BLUEPRINT_SAVE_RESPONSE);
|
||||||
|
bitStream.Write(localId);
|
||||||
|
bitStream.Write(eBlueprintSaveResponseType::EverythingWorked);
|
||||||
|
bitStream.Write<uint32_t>(splitLxfmls.size());
|
||||||
|
|
||||||
|
std::vector<LWOOBJID> blueprintIDs;
|
||||||
|
std::vector<LWOOBJID> modelIDs;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < splitLxfmls.size(); ++i) {
|
||||||
|
// Legacy logic to check for old random IDs (regenerating these is not really feasible)
|
||||||
|
// Probably good to have this anyway in case someone messes with the last_object_id or it gets reset somehow
|
||||||
|
const uint32_t maxRetries = 100;
|
||||||
|
uint32_t retries = 0;
|
||||||
|
bool blueprintIDExists = true;
|
||||||
|
bool modelExists = true;
|
||||||
|
|
||||||
|
LWOOBJID newID = LWOOBJID_EMPTY;
|
||||||
|
LWOOBJID blueprintID = LWOOBJID_EMPTY;
|
||||||
|
do {
|
||||||
|
if (newID != LWOOBJID_EMPTY) LOG("Generating blueprintID for UGC model, collision with existing model ID: %llu", blueprintID);
|
||||||
|
newID = ObjectIDManager::GetPersistentID();
|
||||||
|
blueprintID = ObjectIDManager::GetPersistentID();
|
||||||
|
++retries;
|
||||||
|
blueprintIDExists = Database::Get()->GetUgcModel(blueprintID).has_value();
|
||||||
|
modelExists = Database::Get()->GetModel(newID).has_value();
|
||||||
|
} while ((blueprintIDExists || modelExists) && retries < maxRetries);
|
||||||
|
|
||||||
|
blueprintIDs.push_back(blueprintID);
|
||||||
|
modelIDs.push_back(newID);
|
||||||
|
|
||||||
|
// Save each model to the database
|
||||||
|
sd0.FromData(reinterpret_cast<const uint8_t*>(splitLxfmls[i].lxfml.data()), splitLxfmls[i].lxfml.size());
|
||||||
auto sd0AsStream = sd0.GetAsStream();
|
auto sd0AsStream = sd0.GetAsStream();
|
||||||
Database::Get()->InsertNewUgcModel(sd0AsStream, blueprintID, entity->GetCharacter()->GetParentUser()->GetAccountID(), entity->GetCharacter()->GetID());
|
Database::Get()->InsertNewUgcModel(sd0AsStream, blueprintID, entity->GetCharacter()->GetParentUser()->GetAccountID(), entity->GetCharacter()->GetID());
|
||||||
|
|
||||||
//Insert into the db as a BBB model:
|
// Insert the new property model
|
||||||
IPropertyContents::Model model;
|
IPropertyContents::Model model;
|
||||||
model.id = newIDL;
|
model.id = newID;
|
||||||
model.ugcId = blueprintID;
|
model.ugcId = blueprintID;
|
||||||
model.position = newCenter;
|
model.position = splitLxfmls[i].center;
|
||||||
model.rotation = NiQuaternion(0.0f, 0.0f, 0.0f, 0.0f);
|
model.rotation = QuatUtils::IDENTITY;
|
||||||
model.lot = 14;
|
model.lot = 14;
|
||||||
Database::Get()->InsertNewPropertyModel(propertyId, model, "Objects_14_name");
|
Database::Get()->InsertNewPropertyModel(propertyId, model, "Objects_14_name");
|
||||||
|
|
||||||
@@ -2638,7 +2650,7 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent
|
|||||||
(or you uncomment the lxfml decomp stuff above)
|
(or you uncomment the lxfml decomp stuff above)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// //Send off to UGC for processing, if enabled:
|
// Send off to UGC for processing, if enabled:
|
||||||
// if (Game::config->GetValue("ugc_remote") == "1") {
|
// if (Game::config->GetValue("ugc_remote") == "1") {
|
||||||
// std::string ugcIP = Game::config->GetValue("ugc_ip");
|
// std::string ugcIP = Game::config->GetValue("ugc_ip");
|
||||||
// int ugcPort = std::stoi(Game::config->GetValue("ugc_port"));
|
// int ugcPort = std::stoi(Game::config->GetValue("ugc_port"));
|
||||||
@@ -2653,39 +2665,33 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent
|
|||||||
// //the nif, hkx and checksum files are ready to be downloaded from cache.
|
// //the nif, hkx and checksum files are ready to be downloaded from cache.
|
||||||
// }
|
// }
|
||||||
|
|
||||||
//Tell the client their model is saved: (this causes us to actually pop out of our current state):
|
// Write the ID and data to the response packet
|
||||||
|
bitStream.Write(blueprintID);
|
||||||
|
|
||||||
const auto& newSd0 = sd0.GetAsVector();
|
const auto& newSd0 = sd0.GetAsVector();
|
||||||
uint32_t newSd0Size{};
|
uint32_t newSd0Size{};
|
||||||
for (const auto& chunk : newSd0) newSd0Size += chunk.size();
|
for (const auto& chunk : newSd0) newSd0Size += chunk.size();
|
||||||
CBITSTREAM;
|
|
||||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::BLUEPRINT_SAVE_RESPONSE);
|
|
||||||
bitStream.Write(localId);
|
|
||||||
bitStream.Write(eBlueprintSaveResponseType::EverythingWorked);
|
|
||||||
bitStream.Write<uint32_t>(1);
|
|
||||||
bitStream.Write(blueprintID);
|
|
||||||
|
|
||||||
bitStream.Write(newSd0Size);
|
bitStream.Write(newSd0Size);
|
||||||
|
|
||||||
for (const auto& chunk : newSd0) bitStream.WriteAlignedBytes(reinterpret_cast<const unsigned char*>(chunk.data()), chunk.size());
|
for (const auto& chunk : newSd0) bitStream.WriteAlignedBytes(reinterpret_cast<const unsigned char*>(chunk.data()), chunk.size());
|
||||||
|
}
|
||||||
|
|
||||||
SEND_PACKET;
|
SEND_PACKET;
|
||||||
|
|
||||||
//Now we have to construct this object:
|
// Create entities for each model
|
||||||
|
for (size_t i = 0; i < splitLxfmls.size(); ++i) {
|
||||||
EntityInfo info;
|
EntityInfo info;
|
||||||
info.lot = 14;
|
info.lot = 14;
|
||||||
info.pos = newCenter;
|
info.pos = splitLxfmls[i].center;
|
||||||
info.rot = {};
|
info.rot = QuatUtils::IDENTITY;
|
||||||
info.spawner = nullptr;
|
info.spawner = nullptr;
|
||||||
info.spawnerID = entity->GetObjectID();
|
info.spawnerID = entity->GetObjectID();
|
||||||
info.spawnerNodeID = 0;
|
info.spawnerNodeID = 0;
|
||||||
|
|
||||||
info.settings.push_back(new LDFData<LWOOBJID>(u"blueprintid", blueprintID));
|
info.settings.push_back(new LDFData<LWOOBJID>(u"blueprintid", blueprintIDs[i]));
|
||||||
info.settings.push_back(new LDFData<int>(u"componentWhitelist", 1));
|
info.settings.push_back(new LDFData<int>(u"componentWhitelist", 1));
|
||||||
info.settings.push_back(new LDFData<int>(u"modelType", 2));
|
info.settings.push_back(new LDFData<int>(u"modelType", 2));
|
||||||
info.settings.push_back(new LDFData<bool>(u"propertyObjectID", true));
|
info.settings.push_back(new LDFData<bool>(u"propertyObjectID", true));
|
||||||
info.settings.push_back(new LDFData<LWOOBJID>(u"userModelID", newIDL));
|
info.settings.push_back(new LDFData<LWOOBJID>(u"userModelID", modelIDs[i]));
|
||||||
|
|
||||||
Entity* newEntity = Game::entityManager->CreateEntity(info, nullptr);
|
Entity* newEntity = Game::entityManager->CreateEntity(info, nullptr);
|
||||||
if (newEntity) {
|
if (newEntity) {
|
||||||
Game::entityManager->ConstructEntity(newEntity);
|
Game::entityManager->ConstructEntity(newEntity);
|
||||||
@@ -2693,7 +2699,8 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent
|
|||||||
//Make sure the propMgmt doesn't delete our model after the server dies
|
//Make sure the propMgmt doesn't delete our model after the server dies
|
||||||
//Trying to do this after the entity is constructed. Shouldn't really change anything but
|
//Trying to do this after the entity is constructed. Shouldn't really change anything but
|
||||||
//there was an issue with builds not appearing since it was placed above ConstructEntity.
|
//there was an issue with builds not appearing since it was placed above ConstructEntity.
|
||||||
PropertyManagementComponent::Instance()->AddModel(newEntity->GetObjectID(), newIDL);
|
PropertyManagementComponent::Instance()->AddModel(newEntity->GetObjectID(), modelIDs[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1166,33 +1166,37 @@ void HandlePacket(Packet* packet) {
|
|||||||
LOG("Couldn't find property ID for zone %i, clone %i", zoneId, cloneId);
|
LOG("Couldn't find property ID for zone %i, clone %i", zoneId, cloneId);
|
||||||
goto noBBB;
|
goto noBBB;
|
||||||
}
|
}
|
||||||
for (auto& bbbModel : Database::Get()->GetUgcModels(propertyId)) {
|
|
||||||
|
// Workaround for not having a UGC server to get model LXFML onto the client so it
|
||||||
|
// can generate the physics and nif for the object.
|
||||||
|
|
||||||
|
auto bbbModels = Database::Get()->GetUgcModels(propertyId);
|
||||||
|
if (bbbModels.empty()) {
|
||||||
|
LOG("No BBB models found for property %llu", propertyId);
|
||||||
|
goto noBBB;
|
||||||
|
}
|
||||||
|
|
||||||
|
CBITSTREAM;
|
||||||
|
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::BLUEPRINT_SAVE_RESPONSE);
|
||||||
|
bitStream.Write<LWOOBJID>(LWOOBJID_EMPTY); //always zero so that a check on the client passes
|
||||||
|
bitStream.Write(eBlueprintSaveResponseType::EverythingWorked);
|
||||||
|
bitStream.Write<uint32_t>(bbbModels.size());
|
||||||
|
for (auto& bbbModel : bbbModels) {
|
||||||
LOG("Getting lxfml ugcID: %llu", bbbModel.id);
|
LOG("Getting lxfml ugcID: %llu", bbbModel.id);
|
||||||
|
|
||||||
bbbModel.lxfmlData.seekg(0, std::ios::end);
|
bbbModel.lxfmlData.seekg(0, std::ios::end);
|
||||||
size_t lxfmlSize = bbbModel.lxfmlData.tellg();
|
size_t lxfmlSize = bbbModel.lxfmlData.tellg();
|
||||||
bbbModel.lxfmlData.seekg(0);
|
bbbModel.lxfmlData.seekg(0);
|
||||||
|
|
||||||
//Send message:
|
// write data
|
||||||
LWOOBJID blueprintID = bbbModel.id;
|
LWOOBJID blueprintID = bbbModel.id;
|
||||||
|
|
||||||
// Workaround for not having a UGC server to get model LXFML onto the client so it
|
|
||||||
// can generate the physics and nif for the object.
|
|
||||||
CBITSTREAM;
|
|
||||||
BitStreamUtils::WriteHeader(bitStream, ServiceType::CLIENT, MessageType::Client::BLUEPRINT_SAVE_RESPONSE);
|
|
||||||
bitStream.Write<LWOOBJID>(LWOOBJID_EMPTY); //always zero so that a check on the client passes
|
|
||||||
bitStream.Write(eBlueprintSaveResponseType::EverythingWorked);
|
|
||||||
bitStream.Write<uint32_t>(1);
|
|
||||||
bitStream.Write(blueprintID);
|
bitStream.Write(blueprintID);
|
||||||
|
|
||||||
bitStream.Write<uint32_t>(lxfmlSize);
|
bitStream.Write<uint32_t>(lxfmlSize);
|
||||||
|
|
||||||
bitStream.WriteAlignedBytes(reinterpret_cast<const unsigned char*>(bbbModel.lxfmlData.str().c_str()), lxfmlSize);
|
bitStream.WriteAlignedBytes(reinterpret_cast<const unsigned char*>(bbbModel.lxfmlData.str().c_str()), lxfmlSize);
|
||||||
|
}
|
||||||
SystemAddress sysAddr = packet->systemAddress;
|
SystemAddress sysAddr = packet->systemAddress;
|
||||||
SEND_PACKET;
|
SEND_PACKET;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
noBBB:
|
noBBB:
|
||||||
|
|
||||||
|
@@ -10,6 +10,7 @@ set(DCOMMONTEST_SOURCES
|
|||||||
"TestLUString.cpp"
|
"TestLUString.cpp"
|
||||||
"TestLUWString.cpp"
|
"TestLUWString.cpp"
|
||||||
"dCommonDependencies.cpp"
|
"dCommonDependencies.cpp"
|
||||||
|
"LxfmlTests.cpp"
|
||||||
)
|
)
|
||||||
|
|
||||||
add_subdirectory(dEnumsTests)
|
add_subdirectory(dEnumsTests)
|
||||||
@@ -32,6 +33,8 @@ target_link_libraries(dCommonTests ${COMMON_LIBRARIES} GTest::gtest_main)
|
|||||||
# Copy test files to testing directory
|
# Copy test files to testing directory
|
||||||
add_subdirectory(TestBitStreams)
|
add_subdirectory(TestBitStreams)
|
||||||
file(COPY ${TESTBITSTREAMS} DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
|
file(COPY ${TESTBITSTREAMS} DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
add_subdirectory(LxfmlTestFiles)
|
||||||
|
file(COPY ${LXFMLTESTFILES} DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
|
||||||
# Discover the tests
|
# Discover the tests
|
||||||
gtest_discover_tests(dCommonTests)
|
gtest_discover_tests(dCommonTests)
|
||||||
|
18
tests/dCommonTests/LxfmlTestFiles/CMakeLists.txt
Normal file
18
tests/dCommonTests/LxfmlTestFiles/CMakeLists.txt
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
set(LXFMLTESTFILES
|
||||||
|
"deeply_nested.lxfml"
|
||||||
|
"empty_transform.lxfml"
|
||||||
|
"invalid_transform.lxfml"
|
||||||
|
"mixed_invalid_transform.lxfml"
|
||||||
|
"mixed_valid_invalid.lxfml"
|
||||||
|
"non_numeric_transform.lxfml"
|
||||||
|
"no_bricks.lxfml"
|
||||||
|
"test.lxfml"
|
||||||
|
"too_few_values.lxfml"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get the folder name and prepend it to the files above
|
||||||
|
get_filename_component(thisFolderName ${CMAKE_CURRENT_SOURCE_DIR} NAME)
|
||||||
|
list(TRANSFORM LXFMLTESTFILES PREPEND "${thisFolderName}/")
|
||||||
|
|
||||||
|
# Export our list of files
|
||||||
|
set(LXFMLTESTFILES ${LXFMLTESTFILES} PARENT_SCOPE)
|
50
tests/dCommonTests/LxfmlTestFiles/deeply_nested.lxfml
Normal file
50
tests/dCommonTests/LxfmlTestFiles/deeply_nested.lxfml
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<LXFML versionMajor="5" versionMinor="0">
|
||||||
|
<Meta>
|
||||||
|
<Application name="LEGO Universe" versionMajor="0" versionMinor="0"/>
|
||||||
|
<Brand name="LEGOUniverse"/>
|
||||||
|
<BrickSet version="457"/>
|
||||||
|
</Meta>
|
||||||
|
<Bricks>
|
||||||
|
<Brick refID="0" designID="3001">
|
||||||
|
<Part refID="0" designID="3001" materials="23">
|
||||||
|
<Bone refID="0" transformation="1,0,0,0,1,0,0,0,1,0,0,0"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
</Bricks>
|
||||||
|
<RigidSystems>
|
||||||
|
</RigidSystems>
|
||||||
|
<GroupSystems>
|
||||||
|
<GroupSystem>
|
||||||
|
<Group partRefs="0">
|
||||||
|
<Group partRefs="0">
|
||||||
|
<Group partRefs="0">
|
||||||
|
<Group partRefs="0">
|
||||||
|
<Group partRefs="0">
|
||||||
|
<Group partRefs="0">
|
||||||
|
<Group partRefs="0">
|
||||||
|
<Group partRefs="0">
|
||||||
|
<Group partRefs="0">
|
||||||
|
<Group partRefs="0">
|
||||||
|
<Group partRefs="0">
|
||||||
|
<Group partRefs="0">
|
||||||
|
<Group partRefs="0">
|
||||||
|
<Group partRefs="0">
|
||||||
|
<Group partRefs="0"/>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
</GroupSystem>
|
||||||
|
</GroupSystems>
|
||||||
|
</LXFML>
|
11
tests/dCommonTests/LxfmlTestFiles/empty_transform.lxfml
Normal file
11
tests/dCommonTests/LxfmlTestFiles/empty_transform.lxfml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<LXFML versionMajor="5" versionMinor="0">
|
||||||
|
<Meta></Meta>
|
||||||
|
<Bricks>
|
||||||
|
<Brick refID="0" designID="74340">
|
||||||
|
<Part refID="0" designID="3679">
|
||||||
|
<Bone refID="0" transformation=""/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
</Bricks>
|
||||||
|
</LXFML>
|
20
tests/dCommonTests/LxfmlTestFiles/invalid_transform.lxfml
Normal file
20
tests/dCommonTests/LxfmlTestFiles/invalid_transform.lxfml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
|
||||||
|
<LXFML versionMajor="5" versionMinor="0">
|
||||||
|
<Meta>
|
||||||
|
<Application name="LEGO Universe" versionMajor="0" versionMinor="0"/>
|
||||||
|
<Brand name="LEGOUniverse"/>
|
||||||
|
<BrickSet version="457"/>
|
||||||
|
</Meta>
|
||||||
|
<Bricks>
|
||||||
|
<Brick refID="0" designID="74340">
|
||||||
|
<Part refID="0" designID="3679" materials="23">
|
||||||
|
<Bone refID="0" transformation="invalid,matrix,with,text,values,here,not,numbers,at,all,fails,parse"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="1" designID="41533">
|
||||||
|
<Part refID="1" designID="41533" materials="23">
|
||||||
|
<Bone refID="1" transformation="1,2,3"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
</Bricks>
|
||||||
|
</LXFML>
|
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<LXFML versionMajor="5" versionMinor="0">
|
||||||
|
<Meta></Meta>
|
||||||
|
<Bricks>
|
||||||
|
<Brick refID="0" designID="74340">
|
||||||
|
<Part refID="0" designID="3679">
|
||||||
|
<Bone refID="0" transformation="1,0,invalid,0,1,0,0,0,1,10,20,30"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
</Bricks>
|
||||||
|
</LXFML>
|
44
tests/dCommonTests/LxfmlTestFiles/mixed_valid_invalid.lxfml
Normal file
44
tests/dCommonTests/LxfmlTestFiles/mixed_valid_invalid.lxfml
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
|
||||||
|
<LXFML versionMajor="5" versionMinor="0">
|
||||||
|
<Meta>
|
||||||
|
<Application name="LEGO Universe" versionMajor="0" versionMinor="0"/>
|
||||||
|
<Brand name="LEGOUniverse"/>
|
||||||
|
<BrickSet version="457"/>
|
||||||
|
</Meta>
|
||||||
|
<Bricks>
|
||||||
|
<Brick refID="0" designID="74340">
|
||||||
|
<Part refID="0" designID="3679" materials="23">
|
||||||
|
<Bone refID="0" transformation="1,0,0,0,1,0,0,0,1,0,0,0"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="1" designID="41533">
|
||||||
|
<Part refID="1" designID="41533" materials="23">
|
||||||
|
<Bone refID="1" transformation="invalid,transform,here,bad,values,foo,bar,baz,qux,0,0,0"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="2" designID="74340">
|
||||||
|
<Part refID="2" designID="3679" materials="23">
|
||||||
|
<Bone refID="2" transformation="1,0,0,0,1,0,0,0,1,10,20,30"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="3" designID="41533">
|
||||||
|
<Part refID="3" designID="41533" materials="23">
|
||||||
|
<Bone refID="3" transformation="1,2,3"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
</Bricks>
|
||||||
|
<RigidSystems>
|
||||||
|
<RigidSystem>
|
||||||
|
<Rigid boneRefs="0,2"/>
|
||||||
|
</RigidSystem>
|
||||||
|
<RigidSystem>
|
||||||
|
<Rigid boneRefs="1,3"/>
|
||||||
|
</RigidSystem>
|
||||||
|
</RigidSystems>
|
||||||
|
<GroupSystems>
|
||||||
|
<GroupSystem>
|
||||||
|
<Group partRefs="0,2"/>
|
||||||
|
<Group partRefs="1,3"/>
|
||||||
|
</GroupSystem>
|
||||||
|
</GroupSystems>
|
||||||
|
</LXFML>
|
4
tests/dCommonTests/LxfmlTestFiles/no_bricks.lxfml
Normal file
4
tests/dCommonTests/LxfmlTestFiles/no_bricks.lxfml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<LXFML versionMajor="5" versionMinor="0">
|
||||||
|
<Meta></Meta>
|
||||||
|
</LXFML>
|
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<LXFML versionMajor="5" versionMinor="0">
|
||||||
|
<Meta></Meta>
|
||||||
|
<Bricks>
|
||||||
|
<Brick refID="0" designID="74340">
|
||||||
|
<Part refID="0" designID="3679">
|
||||||
|
<Bone refID="0" transformation="a,b,c,d,e,f,g,h,i,j,k,l"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
</Bricks>
|
||||||
|
</LXFML>
|
336
tests/dCommonTests/LxfmlTestFiles/test.lxfml
Normal file
336
tests/dCommonTests/LxfmlTestFiles/test.lxfml
Normal file
@@ -0,0 +1,336 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no" ?>
|
||||||
|
<LXFML versionMajor="5" versionMinor="0">
|
||||||
|
<Meta>
|
||||||
|
<Application name="LEGO Universe" versionMajor="0" versionMinor="0"/>
|
||||||
|
<Brand name="LEGOUniverse"/>
|
||||||
|
<BrickSet version="457"/>
|
||||||
|
</Meta>
|
||||||
|
<Bricks>
|
||||||
|
<Brick refID="0" designID="74340">
|
||||||
|
<Part refID="0" designID="3679" materials="23">
|
||||||
|
<Bone refID="0" transformation="0.91537082195281982,0.28058713674545288,0.28873366117477417,-0.15140815079212189,0.90441381931304932,-0.39888754487037659,-0.37305742502212524,0.32141336798667908,0.87035715579986572,10.7959,4.83179,1.36732"/>
|
||||||
|
</Part>
|
||||||
|
<Part refID="1" designID="3680" materials="23">
|
||||||
|
<Bone refID="1" transformation="0.91537082195281982,0.28058713674545288,0.28873366117477417,-0.15140815079212189,0.90441381931304932,-0.39888754487037659,-0.37305742502212524,0.32141336798667908,0.87035715579986572,10.8444,4.54236,1.49497"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="1" designID="41533">
|
||||||
|
<Part refID="2" designID="41533" materials="23">
|
||||||
|
<Bone refID="2" transformation="0.91537082195281982,0.28058713674545288,0.28873366117477417,-0.15140815079212189,0.90441381931304932,-0.39888754487037659,-0.37305742502212524,0.32141336798667908,0.87035715579986572,11.5689,3.28748,3.18812"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="2" designID="74340">
|
||||||
|
<Part refID="3" designID="3679" materials="23">
|
||||||
|
<Bone refID="3" transformation="0.91537082195281982,0.28058713674545288,0.28873366117477417,-0.15140815079212189,0.90441381931304932,-0.39888754487037659,-0.37305742502212524,0.32141336798667908,0.87035715579986572,11.5689,3.28748,3.18812"/>
|
||||||
|
</Part>
|
||||||
|
<Part refID="4" designID="3680" materials="23">
|
||||||
|
<Bone refID="4" transformation="0.91537082195281982,0.28058713674545288,0.28873366117477417,-0.15140815079212189,0.90441381931304932,-0.39888754487037659,-0.37305742502212524,0.32141336798667908,0.87035715579986572,11.6174,2.99808,3.31576"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="3" designID="41533">
|
||||||
|
<Part refID="5" designID="41533" materials="23">
|
||||||
|
<Bone refID="5" transformation="0.91537082195281982,0.28058713674545288,0.28873366117477417,-0.15140815079212189,0.90441381931304932,-0.39888754487037659,-0.37305742502212524,0.32141336798667908,0.87035715579986572,12.3419,1.74316,5.0089"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="4" designID="3614">
|
||||||
|
<Part refID="6" designID="3614" materials="23">
|
||||||
|
<Bone refID="6" transformation="0.91537082195281982,0.28058713674545288,0.28873366117477417,-0.15140815079212189,0.90441381931304932,-0.39888754487037659,-0.37305742502212524,0.32141336798667908,0.87035715579986572,13.1227,1.67822,5.36752"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="5" designID="3004">
|
||||||
|
<Part refID="7" designID="3004" materials="23,0,0,0" decoration="0,0,0">
|
||||||
|
<Bone refID="7" transformation="0,0,-1,0,1,0,1,0,0,22,0.320038,10.8"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="6" designID="3036">
|
||||||
|
<Part refID="8" designID="3036" materials="23">
|
||||||
|
<Bone refID="8" transformation="1,0,0,0,1,0,0,0,1,18,3.05176e-05,12.4"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="7" designID="3036">
|
||||||
|
<Part refID="9" designID="3036" materials="23">
|
||||||
|
<Bone refID="9" transformation="1,0,0,0,1,0,0,0,1,14.8,0.320038,13.2"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="8" designID="3036">
|
||||||
|
<Part refID="10" designID="3036" materials="23">
|
||||||
|
<Bone refID="10" transformation="1,0,0,0,1,0,0,0,1,13.2,0.640045,11.6"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="9" designID="6106">
|
||||||
|
<Part refID="11" designID="6106" materials="23">
|
||||||
|
<Bone refID="11" transformation="1,0,0,0,1.0000001192092896,0,0,0,1,12.4,0.960052,6.8"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="10" designID="6106">
|
||||||
|
<Part refID="12" designID="6106" materials="23">
|
||||||
|
<Bone refID="12" transformation="1,0,0,0,1.0000001192092896,0,0,0,1,13.2,1.28006,6.8"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="11" designID="6106">
|
||||||
|
<Part refID="13" designID="6106" materials="23">
|
||||||
|
<Bone refID="13" transformation="1,0,0,0,1.0000001192092896,0,0,0,1,12.4,1.60007,6.8"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="12" designID="3730">
|
||||||
|
<Part refID="14" designID="3730" materials="23">
|
||||||
|
<Bone refID="14" transformation="-1,0,0,0,1,0,0,0,-1,13.2,1.92007,6.8"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="13" designID="73587">
|
||||||
|
<Part refID="15" designID="4592" materials="23">
|
||||||
|
<Bone refID="15" transformation="1,0,0,0,1,0,0,0,1,15.6,1.92007,6.8"/>
|
||||||
|
</Part>
|
||||||
|
<Part refID="16" designID="4593" materials="23">
|
||||||
|
<Bone refID="16" transformation="0.70092326402664185,-0.71323668956756592,0,0.71323668956756592,0.70092326402664185,0,0,0,1,15.6,2.34009,6.8"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="14" designID="3688">
|
||||||
|
<Part refID="17" designID="3688" materials="23">
|
||||||
|
<Bone refID="17" transformation="1,0,0,0,1,0,0,0,1,3.6,0.960022,15.6"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="15" designID="4085">
|
||||||
|
<Part refID="18" designID="4085" materials="23">
|
||||||
|
<Bone refID="18" transformation="1,0,0,0,1,0,0,0,1,5.2,0.960022,15.6"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="16" designID="3046">
|
||||||
|
<Part refID="19" designID="3046" materials="23">
|
||||||
|
<Bone refID="19" transformation="1,0,0,0,1,0,0,0,1,4.4,3.05176e-05,14.8"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="17" designID="73587">
|
||||||
|
<Part refID="20" designID="4592" materials="23">
|
||||||
|
<Bone refID="20" transformation="0,0,-1,0,0.99999994039535522,0,1,0,0,-21.2,3.05176e-05,-9.2"/>
|
||||||
|
</Part>
|
||||||
|
<Part refID="21" designID="4593" materials="23">
|
||||||
|
<Bone refID="21" transformation="0,-0.71323662996292114,-0.70092326402664185,0,0.70092320442199707,-0.71323668956756592,1,0,0,-21.2,0.420044,-9.2"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="18" designID="74340">
|
||||||
|
<Part refID="22" designID="3679" materials="23">
|
||||||
|
<Bone refID="22" transformation="0.28873360157012939,0.28058710694313049,-0.91537082195281982,-0.39888754487037659,0.90441375970840454,0.15140815079212189,0.87035715579986572,0.3214133083820343,0.37305739521980286,-16.0119,3.28745,-5.96892"/>
|
||||||
|
</Part>
|
||||||
|
<Part refID="23" designID="3680" materials="23">
|
||||||
|
<Bone refID="23" transformation="0.28873363137245178,0.28058710694313049,-0.91537082195281982,-0.39888754487037659,0.90441375970840454,0.15140816569328308,0.87035715579986572,0.32141333818435669,0.37305739521980286,-15.8842,2.99805,-6.01737"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="19" designID="41533">
|
||||||
|
<Part refID="24" designID="41533" materials="23">
|
||||||
|
<Bone refID="24" transformation="0.28873360157012939,0.28058710694313049,-0.91537082195281982,-0.39888754487037659,0.90441375970840454,0.15140816569328308,0.87035715579986572,0.3214133083820343,0.37305739521980286,-16.0119,3.28745,-5.96892"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="20" designID="41533">
|
||||||
|
<Part refID="25" designID="41533" materials="23">
|
||||||
|
<Bone refID="25" transformation="0.28873363137245178,0.28058710694313049,-0.91537082195281982,-0.39888754487037659,0.90441375970840454,0.15140816569328308,0.87035715579986572,0.32141333818435669,0.37305739521980286,-14.1911,1.74313,-6.74193"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="21" designID="3730">
|
||||||
|
<Part refID="26" designID="3730" materials="23">
|
||||||
|
<Bone refID="26" transformation="0,0,1,0,0.99999994039535522,0,-1,0,0,-12.4,1.92004,-7.60001"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="22" designID="6106">
|
||||||
|
<Part refID="27" designID="6106" materials="23">
|
||||||
|
<Bone refID="27" transformation="0,0,-1,0,1,0,1,0,0,-12.4,1.60004,-6.80001"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="23" designID="6106">
|
||||||
|
<Part refID="28" designID="6106" materials="23">
|
||||||
|
<Bone refID="28" transformation="0,0,-1,0,1,0,1,0,0,-12.4,1.28003,-7.60001"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="24" designID="6106">
|
||||||
|
<Part refID="29" designID="6106" materials="23">
|
||||||
|
<Bone refID="29" transformation="0,0,-1,0,1,0,1,0,0,-12.4,0.960022,-6.80001"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="25" designID="3036">
|
||||||
|
<Part refID="30" designID="3036" materials="23">
|
||||||
|
<Bone refID="30" transformation="0,0,-1,0,0.99999994039535522,0,1,0,0,-7.6,0.640015,-7.60001"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="26" designID="3036">
|
||||||
|
<Part refID="31" designID="3036" materials="23">
|
||||||
|
<Bone refID="31" transformation="0,0,-1,0,0.99999994039535522,0,1,0,0,-6,0.320007,-9.2"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="27" designID="3036">
|
||||||
|
<Part refID="32" designID="3036" materials="23">
|
||||||
|
<Bone refID="32" transformation="0,0,-1,0,0.99999994039535522,0,1,0,0,-6.8,0,-12.4"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="28" designID="3004">
|
||||||
|
<Part refID="33" designID="3004" materials="23,0,0,0" decoration="0,0,0">
|
||||||
|
<Bone refID="33" transformation="-1,0,0,0,0.99999994039535522,0,0,0,-1,-8.40001,0.320007,-16.4"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="29" designID="73587">
|
||||||
|
<Part refID="34" designID="4592" materials="23">
|
||||||
|
<Bone refID="34" transformation="0,0,-1,0,0.99999994039535522,0,1,0,0,-12.4,1.92004,-10"/>
|
||||||
|
</Part>
|
||||||
|
<Part refID="35" designID="4593" materials="23">
|
||||||
|
<Bone refID="35" transformation="0,-0.71323662996292114,-0.70092326402664185,0,0.70092320442199707,-0.71323668956756592,1,0,0,-12.4,2.34006,-10"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="30" designID="3614">
|
||||||
|
<Part refID="36" designID="3614" materials="23">
|
||||||
|
<Bone refID="36" transformation="0.28873363137245178,0.28058710694313049,-0.91537082195281982,-0.39888754487037659,0.90441375970840454,0.15140816569328308,0.87035715579986572,0.32141333818435669,0.37305739521980286,-13.8325,1.67819,-7.52268"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="31" designID="74340">
|
||||||
|
<Part refID="37" designID="3679" materials="23">
|
||||||
|
<Bone refID="37" transformation="0.28873363137245178,0.28058710694313049,-0.91537082195281982,-0.39888754487037659,0.90441375970840454,0.15140816569328308,0.87035715579986572,0.32141333818435669,0.37305739521980286,-17.8327,4.83176,-5.19592"/>
|
||||||
|
</Part>
|
||||||
|
<Part refID="38" designID="3680" materials="23">
|
||||||
|
<Bone refID="38" transformation="0.28873363137245178,0.28058710694313049,-0.91537082195281982,-0.39888754487037659,0.90441375970840454,0.15140816569328308,0.87035715579986572,0.32141333818435669,0.37305739521980286,-17.705,4.54233,-5.24437"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="32" designID="3046">
|
||||||
|
<Part refID="39" designID="3046" materials="23">
|
||||||
|
<Bone refID="39" transformation="0,0,-1,0,0.99999994039535522,0,1,0,0,-4.4,0,1.2"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="33" designID="4085">
|
||||||
|
<Part refID="40" designID="4085" materials="23">
|
||||||
|
<Bone refID="40" transformation="0,0,-1,0,0.99999994039535522,0,1,0,0,-3.6,0.959991,0.399994"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="34" designID="3688">
|
||||||
|
<Part refID="41" designID="3688" materials="23">
|
||||||
|
<Bone refID="41" transformation="0,0,-1,0,0.99999994039535522,0,1,0,0,-3.60001,0.959991,2"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
<Brick refID="35" designID="73587">
|
||||||
|
<Part refID="42" designID="4592" materials="23">
|
||||||
|
<Bone refID="42" transformation="1,0,0,0,1,0,0,0,1,7.60001,6.10352e-05,-9.2"/>
|
||||||
|
</Part>
|
||||||
|
<Part refID="43" designID="4593" materials="23">
|
||||||
|
<Bone refID="43" transformation="0.70092326402664185,-0.71323668956756592,0,0.71323668956756592,0.70092326402664185,0,0,0,1,7.6,0.420074,-9.2"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
</Bricks>
|
||||||
|
<RigidSystems>
|
||||||
|
<RigidSystem>
|
||||||
|
<Rigid refID="0" transformation="0.91537082195281982,0.28058713674545288,0.28873366117477417,-0.15140815079212189,0.90441381931304932,-0.39888754487037659,-0.37305742502212524,0.32141336798667908,0.87035715579986572,5.9959182739257812,459.87173461914062,69.367317199707031" boneRefs="0"/>
|
||||||
|
<Rigid refID="1" transformation="0.91537082195281982,0.28058713674545288,0.28873366117477417,-0.15140815079212189,0.90441381931304932,-0.39888754487037659,-0.37305742502212524,0.32141336798667908,0.87035715579986572,6.0443649291992187,459.58230590820312,69.494972229003906" boneRefs="1"/>
|
||||||
|
<Rigid refID="2" transformation="0.91537082195281982,0.28058713674545288,0.28873366117477417,-0.15140815079212189,0.90441381931304932,-0.39888754487037659,-0.37305742502212524,0.32141336798667908,0.87035715579986572,6.7689208984375,458.32742309570312,71.188117980957031" boneRefs="2,3"/>
|
||||||
|
<Rigid refID="3" transformation="0.91537082195281982,0.28058713674545288,0.28873366117477417,-0.15140815079212189,0.90441381931304932,-0.39888754487037659,-0.37305742502212524,0.32141336798667908,0.87035715579986572,6.8173675537109375,458.03802490234375,71.315757751464844" boneRefs="4"/>
|
||||||
|
<Rigid refID="4" transformation="0.91537082195281982,0.28058713674545288,0.28873366117477417,-0.15140815079212189,0.90441381931304932,-0.39888754487037659,-0.37305742502212524,0.32141336798667908,0.87035715579986572,7.54193115234375,456.78311157226562,73.008895874023438" boneRefs="5"/>
|
||||||
|
<Rigid refID="5" transformation="0.91537082195281982,0.28058713674545288,0.28873366117477417,-0.15140815079212189,0.90441381931304932,-0.39888754487037659,-0.37305742502212524,0.32141336798667908,0.87035715579986572,8.3226776123046875,456.71817016601562,73.367523193359375" boneRefs="6"/>
|
||||||
|
<Rigid refID="6" transformation="0,0,-1,0,1,0,1,0,0,17.19999885559082,455.3599853515625,78.799995422363281" boneRefs="7,8,9,10,11,12,13,14,15"/>
|
||||||
|
<Rigid refID="7" transformation="0.70092326402664185,-0.71323668956756592,0,0.71323668956756592,0.70092326402664185,0,0,0,1,10.800004959106445,457.38003540039062,74.800003051757813" boneRefs="16"/>
|
||||||
|
<Joint type="hinge">
|
||||||
|
<RigidRef rigidRef="0" a="0,-1,0" z="1,0,0" t="0.40000000596046448,-0.21002300083637238,-0.40000000596046448"/>
|
||||||
|
<RigidRef rigidRef="1" a="0,-1,0" z="-1,0,0" t="0.40000000596046448,0.10999999940395355,-0.40000000596046448"/>
|
||||||
|
</Joint>
|
||||||
|
<Joint type="hinge">
|
||||||
|
<RigidRef rigidRef="1" a="0,1,0" z="0,0,1" t="0.80000007152557373,0,0"/>
|
||||||
|
<RigidRef rigidRef="2" a="0,1,0" z="0,0,1" t="0,1.9199999570846558,-0.80000007152557373"/>
|
||||||
|
</Joint>
|
||||||
|
<Joint type="hinge">
|
||||||
|
<RigidRef rigidRef="2" a="0,-1,0" z="1,0,0" t="0.40000000596046448,-0.21002300083637238,-0.40000000596046448"/>
|
||||||
|
<RigidRef rigidRef="3" a="0,-1,0" z="-1,0,0" t="0.40000000596046448,0.10999999940395355,-0.40000000596046448"/>
|
||||||
|
</Joint>
|
||||||
|
<Joint type="hinge">
|
||||||
|
<RigidRef rigidRef="4" a="0,1,0" z="0,0,1" t="0,1.9199999570846558,-0.80000007152557373"/>
|
||||||
|
<RigidRef rigidRef="3" a="0,1,0" z="0,0,1" t="0.80000007152557373,0,0"/>
|
||||||
|
</Joint>
|
||||||
|
<Joint type="hinge">
|
||||||
|
<RigidRef rigidRef="5" a="0,1,0" z="0,0,1" t="0,0.31999999284744263,0"/>
|
||||||
|
<RigidRef rigidRef="4" a="0,1,0" z="0,0,1" t="0.80000007152557373,0,0"/>
|
||||||
|
</Joint>
|
||||||
|
<Joint type="ball">
|
||||||
|
<RigidRef rigidRef="6" a="-1,0,0" z="0,1,0" t="4.799992561340332,1.7600365877151489,-9.1999912261962891"/>
|
||||||
|
<RigidRef rigidRef="5" a="0,0,1" z="0,-1,0" t="0,0.15999999642372131,0.80000001192092896"/>
|
||||||
|
</Joint>
|
||||||
|
<Joint type="hinge">
|
||||||
|
<RigidRef rigidRef="6" a="1,0,0" z="0,1,0" t="3.9999923706054687,2.0200366973876953,-6.3999929428100586"/>
|
||||||
|
<RigidRef rigidRef="7" a="0,0,-1" z="-1,0,0" t="0,0,0"/>
|
||||||
|
</Joint>
|
||||||
|
</RigidSystem>
|
||||||
|
<RigidSystem>
|
||||||
|
<Rigid refID="8" transformation="1,0,0,0,1,0,0,0,1,-1.1999999284744263,455.99996948242187,83.599998474121094" boneRefs="17"/>
|
||||||
|
<Rigid refID="9" transformation="1,0,0,0,1,0,0,0,1,0.40000009536743164,455.99996948242187,83.600006103515625" boneRefs="18,19"/>
|
||||||
|
<Joint type="hinge">
|
||||||
|
<RigidRef rigidRef="9" a="0,1,0" z="0,0,1" t="-0.80000007152557373,0,-0.8000030517578125"/>
|
||||||
|
<RigidRef rigidRef="8" a="0,1,0" z="0,0,1" t="0.80000007152557373,0,-0.80000007152557373"/>
|
||||||
|
</Joint>
|
||||||
|
</RigidSystem>
|
||||||
|
<RigidSystem>
|
||||||
|
<Rigid refID="10" transformation="0,0,-1,0,0.99999994039535522,0,1,0,0,-26.000003814697266,455.03997802734375,58.799995422363281" boneRefs="20"/>
|
||||||
|
<Rigid refID="11" transformation="0,-0.71323662996292114,-0.70092326402664185,0,0.70092320442199707,-0.71323668956756592,1,0,0,-26.000003814697266,455.45999145507812,58.799995422363281" boneRefs="21"/>
|
||||||
|
<Joint type="hinge">
|
||||||
|
<RigidRef rigidRef="10" a="0,0,-1" z="0,1,0" t="0,0.41999998688697815,0"/>
|
||||||
|
<RigidRef rigidRef="11" a="0,0,-1" z="-1,0,0" t="0,0,0"/>
|
||||||
|
</Joint>
|
||||||
|
</RigidSystem>
|
||||||
|
<RigidSystem>
|
||||||
|
<Rigid refID="12" transformation="0.28873360157012939,0.28058710694313049,-0.91537082195281982,-0.39888754487037659,0.90441375970840454,0.15140815079212189,0.87035715579986572,0.3214133083820343,0.37305739521980286,-20.811885833740234,458.327392578125,62.031078338623047" boneRefs="22,24"/>
|
||||||
|
<Rigid refID="13" transformation="0.28873363137245178,0.28058710694313049,-0.91537082195281982,-0.39888754487037659,0.90441375970840454,0.15140816569328308,0.87035715579986572,0.32141333818435669,0.37305739521980286,-20.684246063232422,458.03799438476562,61.982631683349609" boneRefs="23"/>
|
||||||
|
<Rigid refID="14" transformation="0.28873363137245178,0.28058710694313049,-0.91537082195281982,-0.39888754487037659,0.90441375970840454,0.15140816569328308,0.87035715579986572,0.32141333818435669,0.37305739521980286,-18.991107940673828,456.7830810546875,61.258068084716797" boneRefs="25"/>
|
||||||
|
<Rigid refID="15" transformation="0,0,1,0,0.99999994039535522,0,-1,0,0,-17.200000762939453,456.95999145507812,60.399990081787109" boneRefs="26,27,28,29,30,31,32,33,34"/>
|
||||||
|
<Rigid refID="16" transformation="0.28873363137245178,0.28058710694313049,-0.91537082195281982,-0.39888754487037659,0.90441375970840454,0.15140816569328308,0.87035715579986572,0.32141333818435669,0.37305739521980286,-18.632480621337891,456.7181396484375,60.477321624755859" boneRefs="36"/>
|
||||||
|
<Rigid refID="17" transformation="0,-0.71323662996292114,-0.70092326402664185,0,0.70092320442199707,-0.71323668956756592,1,0,0,-17.200000762939453,457.3800048828125,57.999996185302734" boneRefs="35"/>
|
||||||
|
<Rigid refID="18" transformation="0.28873363137245178,0.28058710694313049,-0.91537082195281982,-0.39888754487037659,0.90441375970840454,0.15140816569328308,0.87035715579986572,0.32141333818435669,0.37305739521980286,-22.632686614990234,459.8717041015625,62.804080963134766" boneRefs="37"/>
|
||||||
|
<Rigid refID="19" transformation="0.28873363137245178,0.28058710694313049,-0.91537082195281982,-0.39888754487037659,0.90441375970840454,0.15140816569328308,0.87035715579986572,0.32141333818435669,0.37305739521980286,-22.505031585693359,459.582275390625,62.755634307861328" boneRefs="38"/>
|
||||||
|
<Joint type="hinge">
|
||||||
|
<RigidRef rigidRef="12" a="0,-1,0" z="1,0,0" t="0.40000000596046448,-0.21002300083637238,-0.40000000596046448"/>
|
||||||
|
<RigidRef rigidRef="13" a="0,-1,0" z="-1,0,0" t="0.40000000596046448,0.10999999940395355,-0.40000000596046448"/>
|
||||||
|
</Joint>
|
||||||
|
<Joint type="hinge">
|
||||||
|
<RigidRef rigidRef="12" a="0,1,0" z="0,0,1" t="0,1.9199999570846558,-0.80000007152557373"/>
|
||||||
|
<RigidRef rigidRef="19" a="0,1,0" z="0,0,1" t="0.80000007152557373,0,0"/>
|
||||||
|
</Joint>
|
||||||
|
<Joint type="hinge">
|
||||||
|
<RigidRef rigidRef="13" a="0,1,0" z="0,0,1" t="0.80000007152557373,0,0"/>
|
||||||
|
<RigidRef rigidRef="14" a="0,1,0" z="0,0,1" t="0,1.9199999570846558,-0.80000007152557373"/>
|
||||||
|
</Joint>
|
||||||
|
<Joint type="hinge">
|
||||||
|
<RigidRef rigidRef="16" a="0,1,0" z="0,0,1" t="0,0.31999999284744263,0"/>
|
||||||
|
<RigidRef rigidRef="14" a="0,1,0" z="0,0,1" t="0.80000007152557373,0,0"/>
|
||||||
|
</Joint>
|
||||||
|
<Joint type="ball">
|
||||||
|
<RigidRef rigidRef="15" a="0,0,-1" z="0,1,0" t="0.40000000596046448,0.15999999642372131,0.80000001192092896"/>
|
||||||
|
<RigidRef rigidRef="16" a="0,0,1" z="0,-1,0" t="0,0.15999999642372131,0.80000001192092896"/>
|
||||||
|
</Joint>
|
||||||
|
<Joint type="hinge">
|
||||||
|
<RigidRef rigidRef="15" a="0,0,1" z="0,1,0" t="-2.3999977111816406,0.41999998688697815,0"/>
|
||||||
|
<RigidRef rigidRef="17" a="0,0,-1" z="-1,0,0" t="0,0,0"/>
|
||||||
|
</Joint>
|
||||||
|
<Joint type="hinge">
|
||||||
|
<RigidRef rigidRef="18" a="0,-1,0" z="1,0,0" t="0.40000000596046448,-0.21002300083637238,-0.40000000596046448"/>
|
||||||
|
<RigidRef rigidRef="19" a="0,-1,0" z="-1,0,0" t="0.40000000596046448,0.10999999940395355,-0.40000000596046448"/>
|
||||||
|
</Joint>
|
||||||
|
</RigidSystem>
|
||||||
|
<RigidSystem>
|
||||||
|
<Rigid refID="20" transformation="0,0,-1,0,0.99999994039535522,0,1,0,0,-9.2000007629394531,455.03994750976562,69.199996948242188" boneRefs="39,40"/>
|
||||||
|
<Rigid refID="21" transformation="0,0,-1,0,0.99999994039535522,0,1,0,0,-8.4000053405761719,455.99993896484375,70" boneRefs="41"/>
|
||||||
|
<Joint type="hinge">
|
||||||
|
<RigidRef rigidRef="20" a="0,1,0" z="0,0,1" t="0,0.95999997854232788,0"/>
|
||||||
|
<RigidRef rigidRef="21" a="0,1,0" z="0,0,1" t="0.80000007152557373,0,-0.80000007152557373"/>
|
||||||
|
</Joint>
|
||||||
|
</RigidSystem>
|
||||||
|
<RigidSystem>
|
||||||
|
<Rigid refID="22" transformation="1,0,0,0,1,0,0,0,1,2.8000054359436035,455.04000854492187,58.799999237060547" boneRefs="42"/>
|
||||||
|
<Rigid refID="23" transformation="0.70092326402664185,-0.71323668956756592,0,0.71323668956756592,0.70092326402664185,0,0,0,1,2.8000044822692871,455.46002197265625,58.799999237060547" boneRefs="43"/>
|
||||||
|
<Joint type="hinge">
|
||||||
|
<RigidRef rigidRef="22" a="0,0,-1" z="0,1,0" t="0,0.41999998688697815,0"/>
|
||||||
|
<RigidRef rigidRef="23" a="0,0,-1" z="-1,0,0" t="0,0,0"/>
|
||||||
|
</Joint>
|
||||||
|
</RigidSystem>
|
||||||
|
</RigidSystems>
|
||||||
|
<GroupSystems>
|
||||||
|
<GroupSystem>
|
||||||
|
<Group transformation="1,0,0,0,1,0,0,0,1,0,0,0" pivot="0,0,0" partRefs="15,16">
|
||||||
|
<Group transformation="1,0,0,0,1,0,0,0,1,0,0,0" pivot="0,0,0" partRefs="19,18,17">
|
||||||
|
<Group transformation="1,0,0,0,1,0,0,0,1,0,0,0" pivot="0,0,0" partRefs="11,12,13">
|
||||||
|
<Group transformation="1,0,0,0,1,0,0,0,1,0,0,0" pivot="0,0,0" partRefs="10,9,7,8"/>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
<Group transformation="1,0,0,0,1,0,0,0,1,0,0,0" pivot="0,0,0" partRefs="25,34,35,20,21,31,37,38,30,24,39,22,23,32,41,26,28,27,36,40,29,33"/>
|
||||||
|
</GroupSystem>
|
||||||
|
</GroupSystems>
|
||||||
|
</LXFML>
|
11
tests/dCommonTests/LxfmlTestFiles/too_few_values.lxfml
Normal file
11
tests/dCommonTests/LxfmlTestFiles/too_few_values.lxfml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<LXFML versionMajor="5" versionMinor="0">
|
||||||
|
<Meta></Meta>
|
||||||
|
<Bricks>
|
||||||
|
<Brick refID="0" designID="74340">
|
||||||
|
<Part refID="0" designID="3679">
|
||||||
|
<Bone refID="0" transformation="1,0,0,0,1,0"/>
|
||||||
|
</Part>
|
||||||
|
</Brick>
|
||||||
|
</Bricks>
|
||||||
|
</LXFML>
|
297
tests/dCommonTests/LxfmlTests.cpp
Normal file
297
tests/dCommonTests/LxfmlTests.cpp
Normal file
@@ -0,0 +1,297 @@
|
|||||||
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
|
#include "Lxfml.h"
|
||||||
|
#include "TinyXmlUtils.h"
|
||||||
|
#include "dCommonDependencies.h"
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
using namespace TinyXmlUtils;
|
||||||
|
|
||||||
|
static std::string ReadFile(const std::string& filename) {
|
||||||
|
std::ifstream in(filename, std::ios::in | std::ios::binary);
|
||||||
|
if (!in.is_open()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
std::ostringstream ss;
|
||||||
|
ss << in.rdbuf();
|
||||||
|
return ss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string SerializeElement(tinyxml2::XMLElement* elem) {
|
||||||
|
tinyxml2::XMLPrinter p;
|
||||||
|
elem->Accept(&p);
|
||||||
|
return std::string(p.CStr());
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST(LxfmlTests, SplitUsesAllBricksAndNoDuplicates) {
|
||||||
|
// Read the test.lxfml file copied to build directory by CMake
|
||||||
|
std::string data = ReadFile("test.lxfml");
|
||||||
|
ASSERT_FALSE(data.empty()) << "Failed to read test.lxfml from build directory";
|
||||||
|
|
||||||
|
auto results = Lxfml::Split(data);
|
||||||
|
ASSERT_GT(results.size(), 0);
|
||||||
|
|
||||||
|
// parse original to count bricks
|
||||||
|
tinyxml2::XMLDocument doc;
|
||||||
|
ASSERT_EQ(doc.Parse(data.c_str()), tinyxml2::XML_SUCCESS);
|
||||||
|
DocumentReader reader(doc);
|
||||||
|
auto lxfml = reader["LXFML"];
|
||||||
|
ASSERT_TRUE(lxfml);
|
||||||
|
|
||||||
|
std::unordered_set<std::string> originalRigidSet;
|
||||||
|
if (auto* rsParent = doc.FirstChildElement("LXFML")->FirstChildElement("RigidSystems")) {
|
||||||
|
for (auto* rs = rsParent->FirstChildElement("RigidSystem"); rs; rs = rs->NextSiblingElement("RigidSystem")) {
|
||||||
|
originalRigidSet.insert(SerializeElement(rs));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unordered_set<std::string> originalGroupSet;
|
||||||
|
if (auto* gsParent = doc.FirstChildElement("LXFML")->FirstChildElement("GroupSystems")) {
|
||||||
|
for (auto* gs = gsParent->FirstChildElement("GroupSystem"); gs; gs = gs->NextSiblingElement("GroupSystem")) {
|
||||||
|
for (auto* g = gs->FirstChildElement("Group"); g; g = g->NextSiblingElement("Group")) {
|
||||||
|
// collect this group and nested groups
|
||||||
|
std::function<void(tinyxml2::XMLElement*)> collectGroups = [&](tinyxml2::XMLElement* grp) {
|
||||||
|
originalGroupSet.insert(SerializeElement(grp));
|
||||||
|
for (auto* child = grp->FirstChildElement("Group"); child; child = child->NextSiblingElement("Group")) collectGroups(child);
|
||||||
|
};
|
||||||
|
collectGroups(g);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unordered_set<std::string> originalBricks;
|
||||||
|
for (const auto& brick : lxfml["Bricks"]) {
|
||||||
|
const auto* ref = brick.Attribute("refID");
|
||||||
|
if (ref) originalBricks.insert(ref);
|
||||||
|
}
|
||||||
|
ASSERT_GT(originalBricks.size(), 0);
|
||||||
|
|
||||||
|
// Collect bricks across all results and ensure no duplicates and all used
|
||||||
|
std::unordered_set<std::string> usedBricks;
|
||||||
|
// Track used rigid systems and groups (serialized strings)
|
||||||
|
std::unordered_set<std::string> usedRigidSet;
|
||||||
|
std::unordered_set<std::string> usedGroupSet;
|
||||||
|
for (const auto& res : results) {
|
||||||
|
tinyxml2::XMLDocument outDoc;
|
||||||
|
ASSERT_EQ(outDoc.Parse(res.lxfml.c_str()), tinyxml2::XML_SUCCESS);
|
||||||
|
DocumentReader outReader(outDoc);
|
||||||
|
auto outLxfml = outReader["LXFML"];
|
||||||
|
ASSERT_TRUE(outLxfml);
|
||||||
|
// collect rigid systems in this output
|
||||||
|
if (auto* rsParent = outDoc.FirstChildElement("LXFML")->FirstChildElement("RigidSystems")) {
|
||||||
|
for (auto* rs = rsParent->FirstChildElement("RigidSystem"); rs; rs = rs->NextSiblingElement("RigidSystem")) {
|
||||||
|
auto s = SerializeElement(rs);
|
||||||
|
// no duplicate allowed across outputs
|
||||||
|
ASSERT_EQ(usedRigidSet.find(s), usedRigidSet.end()) << "Duplicate RigidSystem across splits";
|
||||||
|
usedRigidSet.insert(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// collect groups in this output
|
||||||
|
if (auto* gsParent = outDoc.FirstChildElement("LXFML")->FirstChildElement("GroupSystems")) {
|
||||||
|
for (auto* gs = gsParent->FirstChildElement("GroupSystem"); gs; gs = gs->NextSiblingElement("GroupSystem")) {
|
||||||
|
for (auto* g = gs->FirstChildElement("Group"); g; g = g->NextSiblingElement("Group")) {
|
||||||
|
std::function<void(tinyxml2::XMLElement*)> collectGroupsOut = [&](tinyxml2::XMLElement* grp) {
|
||||||
|
auto s = SerializeElement(grp);
|
||||||
|
ASSERT_EQ(usedGroupSet.find(s), usedGroupSet.end()) << "Duplicate Group across splits";
|
||||||
|
usedGroupSet.insert(s);
|
||||||
|
for (auto* child = grp->FirstChildElement("Group"); child; child = child->NextSiblingElement("Group")) collectGroupsOut(child);
|
||||||
|
};
|
||||||
|
collectGroupsOut(g);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const auto& brick : outLxfml["Bricks"]) {
|
||||||
|
const auto* ref = brick.Attribute("refID");
|
||||||
|
if (ref) {
|
||||||
|
// no duplicate allowed
|
||||||
|
ASSERT_EQ(usedBricks.find(ref), usedBricks.end()) << "Duplicate brick ref across splits: " << ref;
|
||||||
|
usedBricks.insert(ref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every original brick must be used in one of the outputs
|
||||||
|
for (const auto& bref : originalBricks) {
|
||||||
|
ASSERT_NE(usedBricks.find(bref), usedBricks.end()) << "Brick not used in splits: " << bref;
|
||||||
|
}
|
||||||
|
|
||||||
|
// And usedBricks should not contain anything outside original
|
||||||
|
for (const auto& ub : usedBricks) {
|
||||||
|
ASSERT_NE(originalBricks.find(ub), originalBricks.end()) << "Split produced unknown brick: " << ub;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure all original rigid systems and groups were used exactly once
|
||||||
|
ASSERT_EQ(originalRigidSet.size(), usedRigidSet.size()) << "RigidSystem count mismatch";
|
||||||
|
for (const auto& s : originalRigidSet) ASSERT_NE(usedRigidSet.find(s), usedRigidSet.end()) << "RigidSystem missing in splits";
|
||||||
|
|
||||||
|
ASSERT_EQ(originalGroupSet.size(), usedGroupSet.size()) << "Group count mismatch";
|
||||||
|
for (const auto& s : originalGroupSet) ASSERT_NE(usedGroupSet.find(s), usedGroupSet.end()) << "Group missing in splits";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests for invalid input handling - now working with the improved Split function
|
||||||
|
|
||||||
|
TEST(LxfmlTests, InvalidLxfmlHandling) {
|
||||||
|
// Test LXFML with invalid transformation matrices
|
||||||
|
std::string invalidTransformData = ReadFile("invalid_transform.lxfml");
|
||||||
|
ASSERT_FALSE(invalidTransformData.empty()) << "Failed to read invalid_transform.lxfml from build directory";
|
||||||
|
|
||||||
|
// The Split function should handle invalid transformation matrices gracefully
|
||||||
|
std::vector<Lxfml::Result> results;
|
||||||
|
EXPECT_NO_FATAL_FAILURE({
|
||||||
|
results = Lxfml::Split(invalidTransformData);
|
||||||
|
}) << "Split should not crash on invalid transformation matrices";
|
||||||
|
|
||||||
|
// Function should handle invalid transforms gracefully, possibly returning empty or partial results
|
||||||
|
// The exact behavior depends on how the function handles invalid numeric parsing
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LxfmlTests, EmptyLxfmlHandling) {
|
||||||
|
// Test with completely empty input
|
||||||
|
std::string emptyData = "";
|
||||||
|
std::vector<Lxfml::Result> results;
|
||||||
|
|
||||||
|
EXPECT_NO_FATAL_FAILURE({
|
||||||
|
results = Lxfml::Split(emptyData);
|
||||||
|
}) << "Split should not crash on empty input";
|
||||||
|
|
||||||
|
EXPECT_EQ(results.size(), 0) << "Empty input should return empty results";
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LxfmlTests, EmptyTransformHandling) {
|
||||||
|
// Test LXFML with empty transformation matrix
|
||||||
|
std::string testData = ReadFile("empty_transform.lxfml");
|
||||||
|
ASSERT_FALSE(testData.empty()) << "Failed to read empty_transform.lxfml from build directory";
|
||||||
|
|
||||||
|
std::vector<Lxfml::Result> results;
|
||||||
|
EXPECT_NO_FATAL_FAILURE({
|
||||||
|
results = Lxfml::Split(testData);
|
||||||
|
}) << "Split should not crash on empty transformation matrix";
|
||||||
|
|
||||||
|
// The function should handle empty transforms gracefully
|
||||||
|
// May return empty results or skip invalid bricks
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LxfmlTests, TooFewValuesTransformHandling) {
|
||||||
|
// Test LXFML with too few transformation values (needs 12, has fewer)
|
||||||
|
std::string testData = ReadFile("too_few_values.lxfml");
|
||||||
|
ASSERT_FALSE(testData.empty()) << "Failed to read too_few_values.lxfml from build directory";
|
||||||
|
|
||||||
|
std::vector<Lxfml::Result> results;
|
||||||
|
EXPECT_NO_FATAL_FAILURE({
|
||||||
|
results = Lxfml::Split(testData);
|
||||||
|
}) << "Split should not crash on transformation matrix with too few values";
|
||||||
|
|
||||||
|
// The function should handle incomplete transforms gracefully
|
||||||
|
// May return empty results or skip invalid bricks
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LxfmlTests, NonNumericTransformHandling) {
|
||||||
|
// Test LXFML with non-numeric transformation values
|
||||||
|
std::string testData = ReadFile("non_numeric_transform.lxfml");
|
||||||
|
ASSERT_FALSE(testData.empty()) << "Failed to read non_numeric_transform.lxfml from build directory";
|
||||||
|
|
||||||
|
std::vector<Lxfml::Result> results;
|
||||||
|
EXPECT_NO_FATAL_FAILURE({
|
||||||
|
results = Lxfml::Split(testData);
|
||||||
|
}) << "Split should not crash on non-numeric transformation values";
|
||||||
|
|
||||||
|
// The function should handle non-numeric transforms gracefully
|
||||||
|
// May return empty results or skip invalid bricks
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LxfmlTests, MixedInvalidTransformHandling) {
|
||||||
|
// Test LXFML with mixed valid/invalid transformation values within a matrix
|
||||||
|
std::string testData = ReadFile("mixed_invalid_transform.lxfml");
|
||||||
|
ASSERT_FALSE(testData.empty()) << "Failed to read mixed_invalid_transform.lxfml from build directory";
|
||||||
|
|
||||||
|
std::vector<Lxfml::Result> results;
|
||||||
|
EXPECT_NO_FATAL_FAILURE({
|
||||||
|
results = Lxfml::Split(testData);
|
||||||
|
}) << "Split should not crash on mixed valid/invalid transformation values";
|
||||||
|
|
||||||
|
// The function should handle mixed valid/invalid transforms gracefully
|
||||||
|
// May return empty results or skip invalid bricks
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LxfmlTests, NoBricksHandling) {
|
||||||
|
// Test LXFML with no Bricks section (should return empty gracefully)
|
||||||
|
std::string testData = ReadFile("no_bricks.lxfml");
|
||||||
|
ASSERT_FALSE(testData.empty()) << "Failed to read no_bricks.lxfml from build directory";
|
||||||
|
|
||||||
|
std::vector<Lxfml::Result> results;
|
||||||
|
EXPECT_NO_FATAL_FAILURE({
|
||||||
|
results = Lxfml::Split(testData);
|
||||||
|
}) << "Split should not crash on LXFML with no Bricks section";
|
||||||
|
|
||||||
|
// Should return empty results gracefully when no bricks are present
|
||||||
|
EXPECT_EQ(results.size(), 0) << "LXFML with no bricks should return empty results";
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LxfmlTests, MixedValidInvalidTransformsHandling) {
|
||||||
|
// Test LXFML with mix of valid and invalid transformation data
|
||||||
|
std::string mixedValidData = ReadFile("mixed_valid_invalid.lxfml");
|
||||||
|
ASSERT_FALSE(mixedValidData.empty()) << "Failed to read mixed_valid_invalid.lxfml from build directory";
|
||||||
|
|
||||||
|
// The Split function should handle mixed valid/invalid transforms gracefully
|
||||||
|
std::vector<Lxfml::Result> results;
|
||||||
|
EXPECT_NO_FATAL_FAILURE({
|
||||||
|
results = Lxfml::Split(mixedValidData);
|
||||||
|
}) << "Split should not crash on mixed valid/invalid transforms";
|
||||||
|
|
||||||
|
// Should process valid bricks and handle invalid ones gracefully
|
||||||
|
if (results.size() > 0) {
|
||||||
|
EXPECT_NO_FATAL_FAILURE({
|
||||||
|
for (size_t i = 0; i < results.size(); ++i) {
|
||||||
|
// Each result should have valid LXFML structure
|
||||||
|
tinyxml2::XMLDocument doc;
|
||||||
|
auto parseResult = doc.Parse(results[i].lxfml.c_str());
|
||||||
|
EXPECT_EQ(parseResult, tinyxml2::XML_SUCCESS)
|
||||||
|
<< "Result " << i << " should produce valid XML";
|
||||||
|
|
||||||
|
if (parseResult == tinyxml2::XML_SUCCESS) {
|
||||||
|
auto* lxfml = doc.FirstChildElement("LXFML");
|
||||||
|
EXPECT_NE(lxfml, nullptr) << "Result " << i << " should have LXFML root element";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) << "Mixed valid/invalid transform processing should not cause fatal errors";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(LxfmlTests, DeepCloneDepthProtection) {
|
||||||
|
// Test that deep cloning has protection against excessive nesting
|
||||||
|
std::string deeplyNestedLxfml = ReadFile("deeply_nested.lxfml");
|
||||||
|
ASSERT_FALSE(deeplyNestedLxfml.empty()) << "Failed to read deeply_nested.lxfml from build directory";
|
||||||
|
|
||||||
|
// The Split function should handle deeply nested structures without hanging
|
||||||
|
std::vector<Lxfml::Result> results;
|
||||||
|
EXPECT_NO_FATAL_FAILURE({
|
||||||
|
results = Lxfml::Split(deeplyNestedLxfml);
|
||||||
|
}) << "Split should not hang or crash on deeply nested XML structures";
|
||||||
|
|
||||||
|
// Should still produce valid output despite depth limitations
|
||||||
|
EXPECT_GT(results.size(), 0) << "Should produce at least one result even with deep nesting";
|
||||||
|
|
||||||
|
if (results.size() > 0) {
|
||||||
|
// Verify the result is still valid XML
|
||||||
|
tinyxml2::XMLDocument doc;
|
||||||
|
auto parseResult = doc.Parse(results[0].lxfml.c_str());
|
||||||
|
EXPECT_EQ(parseResult, tinyxml2::XML_SUCCESS) << "Result should still be valid XML";
|
||||||
|
|
||||||
|
if (parseResult == tinyxml2::XML_SUCCESS) {
|
||||||
|
auto* lxfml = doc.FirstChildElement("LXFML");
|
||||||
|
EXPECT_NE(lxfml, nullptr) << "Result should have LXFML root element";
|
||||||
|
|
||||||
|
// Verify that bricks are still included despite group nesting issues
|
||||||
|
auto* bricks = lxfml->FirstChildElement("Bricks");
|
||||||
|
EXPECT_NE(bricks, nullptr) << "Bricks element should be present";
|
||||||
|
if (bricks) {
|
||||||
|
auto* brick = bricks->FirstChildElement("Brick");
|
||||||
|
EXPECT_NE(brick, nullptr) << "At least one brick should be present";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user