summaryrefslogtreecommitdiff
path: root/lsrlytexporter.cpp
diff options
context:
space:
mode:
authorTreeki <treeki@gmail.com>2012-09-12 14:57:44 +0200
committerTreeki <treeki@gmail.com>2012-09-12 14:57:44 +0200
commit3189ca5efc5826405bf2e9c7d9949ad5ea9a8a10 (patch)
treed10e8cd4e462db28dd3c2f995905c4df57a20fed /lsrlytexporter.cpp
parentbe8b56a7f0a8f6ba5456a099b61d032fd8aa2f61 (diff)
downloadLayoutStudio-3189ca5efc5826405bf2e9c7d9949ad5ea9a8a10.tar.gz
LayoutStudio-3189ca5efc5826405bf2e9c7d9949ad5ea9a8a10.zip
initial un-exporter version
Diffstat (limited to 'lsrlytexporter.cpp')
-rw-r--r--lsrlytexporter.cpp1176
1 files changed, 1176 insertions, 0 deletions
diff --git a/lsrlytexporter.cpp b/lsrlytexporter.cpp
new file mode 100644
index 0000000..42fb587
--- /dev/null
+++ b/lsrlytexporter.cpp
@@ -0,0 +1,1176 @@
+#include "lsrlytexporter.h"
+#include "lyt/layout.h"
+#include "wii/texpalette.h"
+#include <QXmlStreamWriter>
+#include <QDir>
+#include <QFile>
+#include <QSet>
+#include <QMapIterator>
+
+static void collectPanesForList(QList<LYTPane *> *list, LYTPane *pane) {
+ list->append(pane);
+
+ foreach (LYTPane *p, pane->children)
+ collectPanesForList(list, p);
+}
+
+static void outputPaneTree(QXmlStreamWriter *w, LYTPane *pane) {
+ w->writeStartElement("paneTree");
+ w->writeAttribute("name", pane->name);
+
+ foreach (LYTPane *p, pane->children)
+ outputPaneTree(w, p);
+
+ w->writeEndElement();
+}
+
+static void writeTexCoordSet(QXmlStreamWriter &w, const LYTTexCoords &set) {
+ w.writeStartElement("texCoord");
+
+ static const char *vnames[] = {
+ "texLT", "texRT", "texLB", "texRB"
+ };
+ for (int i = 0; i < 4; i++) {
+ w.writeEmptyElement(vnames[i]);
+ w.writeAttribute("s", QString::number(set.coord[i].x()));
+ w.writeAttribute("t", QString::number(set.coord[i].y()));
+ }
+
+ w.writeEndElement(); // texCoord
+}
+
+static void writeAttribColour(QXmlStreamWriter &w, const QColor &c, bool hasAlpha=true) {
+ w.writeAttribute("r", QString::number(c.red()));
+ w.writeAttribute("g", QString::number(c.green()));
+ w.writeAttribute("b", QString::number(c.blue()));
+ if (hasAlpha)
+ w.writeAttribute("a", QString::number(c.alpha()));
+}
+
+static void writeAttribColourNoAlpha(QXmlStreamWriter &w, const QColor &c) {
+ writeAttribColour(w, c, false);
+}
+
+static bool isMaterialDetailed(const LYTMaterial &mat) {
+ int chk = 0;
+ if (mat.hasAlphaCompare) chk += 1;
+ if (mat.hasBlendMode) chk += 1;
+ if (mat.hasChanCtrl) chk += 1;
+ if (mat.hasMatCol) chk += 1;
+ if (mat.hasTevSwapTable) chk += 1;
+ if (chk == 0) return false;
+ if (chk == 5) return true;
+ qWarning("HUH????");
+ return false;
+}
+
+static void writeMaterial(QXmlStreamWriter &w, const LYTMaterial &mat, QSet<QString> &usedNames) {
+ bool isDetail = mat.hasChanCtrl;
+
+ QString matname = mat.name;
+ while (usedNames.contains(matname)) {
+ matname[0] = QChar(matname.at(0).unicode() + 1);
+ if (matname[0] == ('Z'+1))
+ matname[0] = 'a';
+ if (matname[0] == ('z'+1))
+ matname[0] = 'A';
+ }
+ usedNames.insert(matname);
+
+ w.writeStartElement("material");
+ w.writeAttribute("name", matname);
+ {
+ w.writeEmptyElement("blackColor");
+ writeAttribColourNoAlpha(w, mat.colours[0]);
+ w.writeEmptyElement("whiteColor");
+ writeAttribColourNoAlpha(w, mat.colours[1]);
+
+ static const char *Filters[] = {"Near", "Linear"};
+ static const char *Wraps[] = {"Clamp", "Repeat", "Mirror"};
+
+ foreach (const LYTTexMap &tm, mat.texMaps) {
+ w.writeEmptyElement("texMap");
+
+ QString niceName = tm.textureName;
+ niceName.replace(".tpl", "");
+ w.writeAttribute("imageName", niceName);
+ w.writeAttribute("wrap_s", Wraps[tm.wrap_s]);
+ w.writeAttribute("wrap_t", Wraps[tm.wrap_t]);
+ w.writeAttribute("filterMin", Filters[tm.min_filter]);
+ w.writeAttribute("filterMax", Filters[tm.mag_filter]);
+ // Left out on purpose: paletteName
+ }
+
+ foreach (const LYTTexSRT &srt, mat.texSRTs) {
+ w.writeStartElement("texMatrix");
+ w.writeAttribute("rotate", QString::number(srt.rotate));
+
+ w.writeEmptyElement("scale");
+ w.writeAttribute("x", QString::number(srt.xScale));
+ w.writeAttribute("y", QString::number(srt.yScale));
+
+ w.writeEmptyElement("translate");
+ w.writeAttribute("x", QString::number(srt.xTrans));
+ w.writeAttribute("y", QString::number(srt.yTrans));
+
+ w.writeEndElement(); // texMatrix
+ }
+
+ foreach (const LYTTexCoordGen &cg, mat.texCoordGens) {
+ w.writeEmptyElement("texCoordGen");
+ w.writeAttribute("func", (cg.genType == 1 ? "Mtx2x4" : "ERROR"));
+ w.writeAttribute("srcParam", (cg.src>=4 && cg.src<=11)?QString("Tex%1").arg(cg.src-4):"ERROR");
+ int fixMtx = (cg.mtx == 60) ? -1 : ((cg.mtx / 3) - 10);
+ w.writeAttribute("matrix", QString::number(fixMtx));
+ }
+
+ // this is most likely INCORRECT.
+ w.writeEmptyElement("textureStage");
+ w.writeAttribute("texMap", "0");
+ w.writeAttribute("texCoordGen", "0");
+
+ w.writeEmptyElement("texBlendRatio");
+ w.writeAttribute("color", "255");
+
+ // TODO: textureStage, texBlendRatio, indirectStages(MaterialWarp),..?
+ }
+ w.writeEndElement(); // material
+
+ w.writeStartElement("materialRevo");
+ w.writeAttribute("name", matname);
+ if (isDetail) {
+ w.writeAttribute("tevStageNum", QString::number(mat.tevStages.count()));
+ w.writeAttribute("indirectStageNum", QString::number(mat.indTexStages.count()));
+ }
+ {
+ qDebug("Chk: %c%c%c%c%c %d,%d,%d,%d,%d,%d",
+ mat.hasAlphaCompare?'x':'_',
+ mat.hasBlendMode?'x':'_',
+ mat.hasChanCtrl?'x':'_',
+ mat.hasMatCol?'x':'_',
+ mat.hasTevSwapTable?'x':'_',
+ mat.indTexSRTs.count(),
+ mat.indTexStages.count(),
+ mat.tevStages.count(),
+ mat.texCoordGens.count(),
+ mat.texMaps.count(),
+ mat.texSRTs.count());
+
+ w.writeEmptyElement("channelControl");
+ w.writeAttribute("channel", "Color0");
+ w.writeAttribute("materialSource", mat.hasChanCtrl?(mat.chanCtrl.colourMatSrc==0?"Register":"Vertex"):"Vertex");
+
+ w.writeEmptyElement("channelControl");
+ w.writeAttribute("channel", "Alpha0");
+ w.writeAttribute("materialSource", mat.hasChanCtrl?(mat.chanCtrl.colourMatSrc==0?"Register":"Vertex"):"Vertex");
+
+ w.writeEmptyElement("matColReg");
+ writeAttribColour(w, mat.hasMatCol ? mat.matCol : QColor::fromRgb(255,255,255,255));
+
+ for (int i = 0; i < 3; i++) {
+ w.writeEmptyElement("tevColReg");
+ writeAttribColour(w, mat.colours[i]);
+ }
+
+ for (int i = 0; i < 4; i++) {
+ w.writeEmptyElement("tevConstReg");
+ writeAttribColour(w, mat.tevKColour[i]);
+ }
+
+ // blehhhhhhhhhhhh DRY!!!
+ static const char *Filters[] = {"Near", "Linear"};
+ static const char *Wraps[] = {"Clamp", "Repeat", "Mirror"};
+
+ foreach (const LYTTexMap &tm, mat.texMaps) {
+ w.writeEmptyElement("texMap");
+
+ QString niceName = tm.textureName;
+ niceName.replace(".tpl", "");
+ w.writeAttribute("imageName", niceName);
+ w.writeAttribute("wrap_s", Wraps[tm.wrap_s]);
+ w.writeAttribute("wrap_t", Wraps[tm.wrap_t]);
+ w.writeAttribute("filterMin", Filters[tm.min_filter]);
+ w.writeAttribute("filterMax", Filters[tm.mag_filter]);
+ // Left out on purpose: paletteName
+ }
+
+ foreach (const LYTTexSRT &srt, mat.texSRTs) {
+ w.writeStartElement("texMatrix");
+ w.writeAttribute("rotate", QString::number(srt.rotate));
+
+ w.writeEmptyElement("scale");
+ w.writeAttribute("x", QString::number(srt.xScale));
+ w.writeAttribute("y", QString::number(srt.yScale));
+
+ w.writeEmptyElement("translate");
+ w.writeAttribute("x", QString::number(srt.xTrans));
+ w.writeAttribute("y", QString::number(srt.yTrans));
+
+ w.writeEndElement(); // texMatrix
+ }
+
+ foreach (const LYTTexCoordGen &cg, mat.texCoordGens) {
+ w.writeEmptyElement("texCoordGen");
+ w.writeAttribute("func", (cg.genType == 1 ? "Mtx2x4" : "ERROR"));
+ w.writeAttribute("srcParam", (cg.src>=4 && cg.src<=11)?QString("Tex%1").arg(cg.src-4):"ERROR");
+ int fixMtx = (cg.mtx == 60) ? -1 : ((cg.mtx / 3) - 10);
+ w.writeAttribute("matrix", QString::number(fixMtx));
+ }
+ // end horrible lack of DRY
+
+ if (mat.hasTevSwapTable) {
+ static const char *Swaps[] = {"Red","Green","Blue","Alpha"};
+ for (int i = 0; i < 4; i++) {
+ w.writeEmptyElement("swapTable");
+ w.writeAttribute("r", Swaps[mat.tevSwapTable.mode[i].red]);
+ w.writeAttribute("g", Swaps[mat.tevSwapTable.mode[i].green]);
+ w.writeAttribute("b", Swaps[mat.tevSwapTable.mode[i].blue]);
+ w.writeAttribute("a", Swaps[mat.tevSwapTable.mode[i].alpha]);
+ }
+ } else {
+ for (int i = 0; i < 4; i++) {
+ w.writeEmptyElement("swapTable");
+ w.writeAttribute("r", "Red");
+ w.writeAttribute("g", "Green");
+ w.writeAttribute("b", "Blue");
+ w.writeAttribute("a", "Alpha");
+ }
+ }
+
+ foreach (const LYTTexSRT &srt, mat.indTexSRTs) {
+ w.writeStartElement("indirectMatrix");
+ w.writeAttribute("rotate", QString::number(srt.rotate));
+
+ w.writeEmptyElement("scale");
+ w.writeAttribute("x", QString::number(srt.xScale));
+ w.writeAttribute("y", QString::number(srt.yScale));
+
+ w.writeEmptyElement("translate");
+ w.writeAttribute("x", QString::number(srt.xTrans));
+ w.writeAttribute("y", QString::number(srt.yTrans));
+
+ w.writeEndElement(); // texMatrix
+ }
+
+ static const char *IndScale[] = {
+ "V1","V2","V4","V8","V16","V32","V64","V128","V256"
+ };
+ foreach (const LYTIndirectStage &ist, mat.indTexStages) {
+ w.writeEmptyElement("indirectStage");
+ w.writeAttribute("texMap", QString::number(ist.texMap));
+ w.writeAttribute("texCoord", QString::number(ist.texCoord));
+ w.writeAttribute("scale_s", IndScale[ist.wrap_s]);
+ w.writeAttribute("scale_t", IndScale[ist.wrap_t]);
+ }
+
+ // for no indirects? no idea
+ if (mat.indTexSRTs.count() == 0) {
+ for (int i = 0; i < 3; i++) {
+ w.writeStartElement("indirectMatrix");
+ w.writeAttribute("rotate", "0");
+
+ w.writeEmptyElement("scale");
+ w.writeAttribute("x", "1");
+ w.writeAttribute("y", "1");
+
+ w.writeEmptyElement("translate");
+ w.writeAttribute("x", "0");
+ w.writeAttribute("y", "0");
+
+ w.writeEndElement(); // indirectMatrix
+ }
+ }
+
+ if (mat.indTexStages.count() == 0) {
+ for (int i = 0; i < 4; i++) {
+ w.writeEmptyElement("indirectStage");
+ w.writeAttribute("texMap", "0");
+ w.writeAttribute("texCoordGen", "0");
+ w.writeAttribute("scale_s", "V1");
+ w.writeAttribute("scale_t", "V1");
+ }
+ }
+
+ // the rest
+
+ static const char *TevCChan[] = {
+ "xERR","xERR","xERR","xERR","Color0a0","xERR",
+ "ColorZero","xERR","xERR"
+ };
+ static const char *CArg[] = {
+ "CPrev","APrev","C0","A0","C1","A1","C2","A2",
+ "TexC","TexA","RasC","RasA","V1_0","V0_5","Konst","V0"
+ };
+ static const char *AArg[] = {
+ "APrev","A0","A1","A2","TexA","RasA","Konst","V0"
+ };
+ static const char *KCSel[] = {
+ "V8_8","V7_8","V6_8","V5_8","V4_8","V3_8","V2_8","V1_8",
+ "xERR","xERR","xERR","xERR",
+ "K0","K1","K2","K3",
+ "K0_r","K1_r","K2_r","K3_r",
+ "K0_g","K1_g","K2_g","K3_g",
+ "K0_b","K1_b","K2_b","K3_b",
+ "K0_a","K1_a","K2_a","K3_a"
+ };
+ static const char *KASel[] = {
+ "V8_8","V7_8","V6_8","V5_8","V4_8","V3_8","V2_8","V1_8",
+ "xERR","xERR","xERR","xERR","xERR","xERR","xERR","xERR",
+ "K0_r","K1_r","K2_r","K3_r",
+ "K0_g","K1_g","K2_g","K3_g",
+ "K0_b","K1_b","K2_b","K3_b",
+ "K0_a","K1_a","K2_a","K3_a"
+ };
+ static const char *ColOp[] = {
+ "Add","Sub","x","x","x","x","x","x",
+ "Comp_r8_gt","Comp_r8_eq",
+ "Comp_gr16_gt","Comp_gr16_eq",
+ "Comp_bgr24_gt","Comp_bgr24_eq",
+ "Comp_rgb8_gt","Comp_rgb8_eq"
+ };
+ static const char *AlOp[] = {
+ "Add","Sub","x","x","x","x","x","x",
+ "Comp_r8_gt","Comp_r8_eq",
+ "Comp_gr16_gt","Comp_gr16_eq",
+ "Comp_bgr24_gt","Comp_bgr24_eq",
+ "Comp_a8_gt","Comp_a8_eq"
+ };
+ static const char *Bias[] = {"V0", "P0_5", "M0_5"};
+ static const char *Scale[] = {"V1", "V2", "V4", "V1_2"};
+ static const char *Reg[] = {"Prev", "Reg0", "Reg1", "Reg2"};
+ foreach (const LYTTevStage &st, mat.tevStages) {
+ w.writeStartElement("tevStage");
+ w.writeAttribute("colorChannel", (st.colour==-1)?"ColorNull":TevCChan[st.colour]);
+ w.writeAttribute("texMap", QString::number(st.texMap));
+ w.writeAttribute("texCoordGen", QString::number(st.texCoord));
+ w.writeAttribute("rasColSwap", QString::number(st.rasSwapMode));
+ w.writeAttribute("texColSwap", QString::number(st.texSwapMode));
+
+ w.writeEmptyElement("color");
+ w.writeAttribute("a", CArg[st.colourInA]);
+ w.writeAttribute("b", CArg[st.colourInB]);
+ w.writeAttribute("c", CArg[st.colourInC]);
+ w.writeAttribute("d", CArg[st.colourInD]);
+ w.writeAttribute("konst", KCSel[st.colourConst]);
+ w.writeAttribute("op", ColOp[st.colourOp]);
+ w.writeAttribute("bias", Bias[st.colourBias]);
+ w.writeAttribute("scale", Scale[st.colourScale]);
+ w.writeAttribute("clamp", st.colourClamp?"true":"false");
+ w.writeAttribute("outReg", Reg[st.colourOutReg]);
+
+ w.writeEmptyElement("alpha");
+ w.writeAttribute("a", AArg[st.alphaInA]);
+ w.writeAttribute("b", AArg[st.alphaInB]);
+ w.writeAttribute("c", AArg[st.alphaInC]);
+ w.writeAttribute("d", AArg[st.alphaInD]);
+ w.writeAttribute("konst", KASel[st.alphaConst]);
+ w.writeAttribute("op", AlOp[st.alphaOp]);
+ w.writeAttribute("bias", Bias[st.alphaBias]);
+ w.writeAttribute("scale", Scale[st.alphaScale]);
+ w.writeAttribute("clamp", st.alphaClamp?"true":"false");
+ w.writeAttribute("outReg", Reg[st.alphaOutReg]);
+
+ static const char *IFormat[] = {"V8","V5","V4","V3"};
+ static const char *IBias[] = {"None","S","T","ST","U","SU","TU","STU"};
+ static const char *IMtx[] = {"Off","V0","V1","V2","x","S0","S1","S2","x","T0","T1","T2"};
+ static const char *IWrap[] = {"Off","V256","V128","V64","V32","V16","V0"};
+
+ w.writeEmptyElement("indirect");
+ w.writeAttribute("indStage", QString::number(st.indStage));
+ w.writeAttribute("format", IFormat[st.indFormat]);
+ w.writeAttribute("bias", IBias[st.indBias]);
+ w.writeAttribute("matrix", IMtx[st.indMatrix]);
+ w.writeAttribute("wrap_s", IWrap[st.indWrapS]);
+ w.writeAttribute("wrap_t", IWrap[st.indWrapT]);
+ w.writeAttribute("addPrev", st.indAddPrev?"true":"false");
+ w.writeAttribute("utcLod", st.indUtcLod?"true":"false");
+ if (st.indAlphaSel != 0)
+ qFatal("indAlphaSel not off!");
+ w.writeAttribute("alpha", "Off");
+
+ w.writeEndElement(); // tevStage
+ }
+
+ // wtf bbq
+ if (mat.tevStages.count() == 0) {
+ // todo: different for >1 texture? must check.
+ w.writeStartElement("tevStage");
+ w.writeAttribute("colorChannel", "Color0a0");
+ w.writeAttribute("texMap", "-1");
+ w.writeAttribute("texCoordGen", "-1");
+ w.writeAttribute("rasColSwap", "0");
+ w.writeAttribute("texColSwap", "0");
+
+ w.writeEmptyElement("color");
+ w.writeAttribute("a", "RasC");
+ w.writeAttribute("b", "V0");
+ w.writeAttribute("c", "V0");
+ w.writeAttribute("d", "V0");
+ w.writeAttribute("konst", "K0");
+ w.writeAttribute("op", "Add");
+ w.writeAttribute("bias", "V0");
+ w.writeAttribute("scale", "V1");
+ w.writeAttribute("clamp", "true");
+ w.writeAttribute("outReg", "Prev");
+
+ w.writeEmptyElement("alpha");
+ w.writeAttribute("a", "RasA");
+ w.writeAttribute("b", "V0");
+ w.writeAttribute("c", "V0");
+ w.writeAttribute("d", "V0");
+ w.writeAttribute("konst", "K0_a");
+ w.writeAttribute("op", "Add");
+ w.writeAttribute("bias", "V0");
+ w.writeAttribute("scale", "V1");
+ w.writeAttribute("clamp", "true");
+ w.writeAttribute("outReg", "Prev");
+
+ w.writeEmptyElement("indirect");
+ w.writeAttribute("indStage", "0");
+ w.writeAttribute("format", "V8");
+ w.writeAttribute("bias", "STU");
+ w.writeAttribute("matrix", "Off");
+ w.writeAttribute("wrap_s", "Off");
+ w.writeAttribute("wrap_t", "Off");
+ w.writeAttribute("addPrev", "false");
+ w.writeAttribute("alpha", "Off");
+ w.writeAttribute("utcLod", "false");
+
+ w.writeEndElement(); // tevStage
+ }
+
+ if (mat.hasAlphaCompare) {
+ static const char *Compare[] = {"Never","Less","Equal","LEqual","Greater","NEqual","GEqual","Always"};
+ static const char *AlphaOp[] = {"And","Or","Xor","Xnor"};
+ w.writeEmptyElement("alphaCompare");
+ w.writeAttribute("comp0", Compare[mat.alphaCompare.comp0]);
+ w.writeAttribute("ref0", QString::number(mat.alphaCompare.ref0));
+ w.writeAttribute("op", AlphaOp[mat.alphaCompare.op]);
+ w.writeAttribute("comp1", Compare[mat.alphaCompare.comp1]);
+ w.writeAttribute("ref1", QString::number(mat.alphaCompare.ref1));
+ } else {
+ w.writeEmptyElement("alphaCompare");
+ w.writeAttribute("comp0", "Always");
+ w.writeAttribute("ref0", "0");
+ w.writeAttribute("op", "And");
+ w.writeAttribute("comp1", "Always");
+ w.writeAttribute("ref1", "0");
+ }
+
+ if (mat.hasBlendMode) {
+ static const char *BmType[] = {"None","Blend","Logic","Subtract"};
+ static const char *SrcFactor[] = {"V0","V1_0","DstClr","InvDstClr","SrcAlpha","InvSrcAlpha","DstAlpha","InvDstAlpha"};
+ static const char *DstFactor[] = {"V0","V1_0","SrcClr","InvSrcClr","SrcAlpha","InvSrcAlpha","DstAlpha","InvDstAlpha"};
+ static const char *LogicOp[] = {
+ "Clear", "And", "RevAnd", "Copy",
+ "InvAnd", "NoOp", "Xor", "Or",
+ "Nor", "Equiv", "Inv", "RevOr",
+ "InvCopy", "InvOr", "Nand", "Set"
+ };
+ w.writeEmptyElement("blendMode");
+ w.writeAttribute("type", BmType[mat.blendMode.type]);
+ w.writeAttribute("srcFactor", SrcFactor[mat.blendMode.srcFactor]);
+ w.writeAttribute("dstFactor", DstFactor[mat.blendMode.destFactor]);
+ w.writeAttribute("op", LogicOp[mat.blendMode.op]);
+ } else {
+ w.writeEmptyElement("blendMode");
+ w.writeAttribute("type", "Blend");
+ w.writeAttribute("srcFactor", "SrcAlpha");
+ w.writeAttribute("dstFactor", "InvSrcAlpha");
+ w.writeAttribute("op", "Copy");
+ }
+
+ // what have I missed...? tevStageNum attrib?, displayFace attrib?,
+ // indirectStageNum attrib?
+ }
+ w.writeEndElement(); // materialRevo
+}
+
+static void exportRlyt(LYTLayout *layout, QIODevice *output, const QMap<QString, GX::TextureFormat> &texFormats) {
+ QXmlStreamWriter w(output);
+ w.setAutoFormatting(true);
+
+ w.writeStartDocument();
+
+ w.writeStartElement("nw4r_layout");
+ w.writeDefaultNamespace("http://www.nintendo.co.jp/NW4R/LayoutEditor");
+ w.writeAttribute("version", "1.2.0");
+
+ w.writeStartElement("head");
+ {
+ w.writeEmptyElement("create");
+ w.writeAttribute("user", "someone");
+ w.writeAttribute("host", "somewhere");
+ w.writeAttribute("date", "2006-07-26T10:20:54.551+09:00");
+ w.writeAttribute("source", "");
+
+ w.writeEmptyElement("title");
+
+ w.writeEmptyElement("comment");
+
+ w.writeEmptyElement("generator");
+ w.writeAttribute("name", "LayoutStudio");
+ w.writeAttribute("version", "NONE_OF_YOUR_BUSINESS");
+ }
+ w.writeEndElement(); // head
+
+ w.writeStartElement("body");
+ w.writeStartElement("rlyt");
+
+
+ // Stuff begins here
+ QSet<QString> usedNames;
+
+ static const char *PaneKinds[] = {
+ "Null", "Picture", "TextBox", "Window", "Bounding"
+ };
+ static const char *HorzOrigin[] = {"Left", "Center", "Right"};
+ static const char *VertOrigin[] = {"Top", "Center", "Bottom"};
+
+ w.writeStartElement("paneSet");
+ {
+ // Collect pane list
+ QList<LYTPane *> paneList;
+ collectPanesForList(&paneList, layout->rootPane);
+
+ // now write them all
+ foreach (LYTPane *p, paneList) {
+ w.writeStartElement("pane");
+ w.writeAttribute("kind", PaneKinds[(int)p->type()]);
+ w.writeAttribute("name", p->name);
+ w.writeAttribute("visible", p->visible?"true":"false");
+ w.writeAttribute("locked", "false");
+ w.writeAttribute("alpha", QString::number(p->alpha));
+ if (p->influencedAlpha)
+ w.writeAttribute("influencedAlpha", "true");
+ if (p->isWidescreen)
+ w.writeAttribute("locationAdjust", "true");
+ {
+ w.writeEmptyElement("comment");
+
+ w.writeEmptyElement("basePositionType");
+ w.writeAttribute("x", HorzOrigin[(int)p->horzOrigin]);
+ w.writeAttribute("y", VertOrigin[(int)p->vertOrigin]);
+
+ w.writeEmptyElement("translate");
+ w.writeAttribute("x", QString::number(p->xTrans));
+ w.writeAttribute("y", QString::number(p->yTrans));
+ w.writeAttribute("z", QString::number(p->zTrans));
+
+ w.writeEmptyElement("rotate");
+ w.writeAttribute("x", QString::number(p->xRot));
+ w.writeAttribute("y", QString::number(p->yRot));
+ w.writeAttribute("z", QString::number(p->zRot));
+
+ w.writeEmptyElement("scale");
+ w.writeAttribute("x", QString::number(p->xScale));
+ w.writeAttribute("y", QString::number(p->yScale));
+
+ w.writeEmptyElement("size");
+ w.writeAttribute("x", QString::number(p->width));
+ w.writeAttribute("y", QString::number(p->height));
+
+
+ if (p->type() == LYTPane::PictureType) {
+ LYTPicture *pic = (LYTPicture*)p;
+
+ const LYTMaterial &mat = *(layout->materials.getMaterialByName(pic->materialName));
+
+ w.writeStartElement("picture");
+ w.writeAttribute("detailSetting", isMaterialDetailed(mat)?"true":"false");
+
+ static const char *vnames[] = {
+ "vtxColLT", "vtxColRT", "vtxColLB", "vtxColRB"
+ };
+ for (int i = 0; i < 4; i++) {
+ w.writeEmptyElement(vnames[i]);
+ writeAttribColour(w, pic->vtxColours[i]);
+ }
+
+ for (int i = 0; i < pic->texCoords.count(); i++)
+ writeTexCoordSet(w, pic->texCoords.at(i));
+
+ writeMaterial(w, mat, usedNames);
+
+ w.writeEndElement(); // picture
+
+ } else if (p->type() == LYTPane::TextBoxType) {
+ LYTTextBox *tb = (LYTTextBox*)p;
+
+ static const char *TextAlignments[] = {
+ "Synchronous", "Left", "Center", "Right"
+ };
+
+ w.writeStartElement("textBox");
+ w.writeAttribute("font", tb->fontName);
+ w.writeAttribute("textAlignment", TextAlignments[(int)tb->alignment]);
+ w.writeAttribute("lineSpace", QString::number(tb->lineSpace));
+ w.writeAttribute("charSpace", QString::number(tb->charSpace));
+ w.writeAttribute("allocateStringLength", QString::number(tb->bufferLength));
+
+ w.writeEmptyElement("fontSize");
+ w.writeAttribute("x", QString::number(tb->fontSizeX));
+ w.writeAttribute("y", QString::number(tb->fontSizeY));
+
+ w.writeTextElement("text", tb->text);
+
+ w.writeEmptyElement("topColor");
+ writeAttribColour(w, tb->colour1);
+
+ w.writeEmptyElement("bottomColor");
+ writeAttribColour(w, tb->colour2);
+
+ w.writeEmptyElement("positionType");
+ w.writeAttribute("x", HorzOrigin[(int)tb->textHorzPos]);
+ w.writeAttribute("y", VertOrigin[(int)tb->textVertPos]);
+
+ writeMaterial(w, *(layout->materials.getMaterialByName(tb->materialName)), usedNames);
+
+ w.writeEndElement(); // textBox
+
+ } else if (p->type() == LYTPane::WindowType) {
+ LYTWindow *win = (LYTWindow*)p;
+
+ w.writeStartElement("window");
+
+ w.writeStartElement("content");
+ {
+ const LYTMaterial &mat = *(layout->materials.getMaterialByName(win->contentMaterialName));
+ w.writeAttribute("detailSetting", isMaterialDetailed(mat)?"true":"false");
+
+ static const char *vnames[] = {
+ "vtxColLT", "vtxColRT", "vtxColLB", "vtxColRB"
+ };
+ for (int i = 0; i < 4; i++) {
+ w.writeEmptyElement(vnames[i]);
+ writeAttribColour(w, win->contentVtxColours[i]);
+ }
+
+ foreach (const LYTTexCoords &set, win->contentTexCoords)
+ writeTexCoordSet(w, set);
+
+ writeMaterial(w, mat, usedNames);
+ }
+ w.writeEndElement(); // content
+
+ const char *wframetypes[] = {
+ "CornerLT", "CornerRT", "CornerLB", "CornerRB",
+ "FrameT", "FrameB", "FrameL", "FrameR"
+ };
+ int frameidx = 0;
+ foreach (const LYTWindowFrame *fr, win->frames) {
+ const LYTMaterial &fmat = *(layout->materials.getMaterialByName(fr->materialName));
+
+ w.writeStartElement("frame");
+ w.writeAttribute("frameType", wframetypes[frameidx]);
+ w.writeAttribute("detailSetting", isMaterialDetailed(fmat)?"true":"false");
+
+ static const char *tflipNames[] = {
+ "None", "FlipH", "FlipV", "Rotate90", "Rotate180", "Rotate270"
+ };
+ w.writeTextElement("textureFlip", tflipNames[(int)fr->type]);
+
+ writeMaterial(w, fmat, usedNames);
+
+ w.writeEndElement(); // frame
+
+ frameidx++;
+ }
+
+ w.writeEmptyElement("contentInflation");
+ w.writeAttribute("l", QString::number(win->contentOverflowLeft));
+ w.writeAttribute("r", QString::number(win->contentOverflowRight));
+ w.writeAttribute("t", QString::number(win->contentOverflowTop));
+ w.writeAttribute("b", QString::number(win->contentOverflowBottom));
+
+ w.writeEndElement(); // window
+
+ } else if (p->type() == LYTPane::BoundingType) {
+ w.writeEmptyElement("bounding");
+ }
+
+
+ w.writeStartElement("userData");
+ w.writeEmptyElement("string");
+ w.writeEndElement();
+ }
+ w.writeEndElement(); // pane
+ }
+ }
+ w.writeEndElement(); // paneSet
+
+
+ // Now, the hierarchy
+ w.writeStartElement("paneHierarchy");
+ {
+ outputPaneTree(&w, layout->rootPane);
+ }
+ w.writeEndElement(); // paneHierarchy
+
+
+ w.writeStartElement("groupSet");
+ {
+ w.writeStartElement("group");
+ w.writeAttribute("name", "RootGroup");
+ {
+ foreach (LYTGroup *g, layout->groups) {
+ w.writeStartElement("group");
+ w.writeAttribute("name", g->name);
+ {
+ foreach (LYTPane *p, g->panes) {
+ w.writeEmptyElement("paneRef");
+ w.writeAttribute("name", p->name);
+ }
+ w.writeEmptyElement("comment");
+ }
+ w.writeEndElement(); // group
+ }
+
+ w.writeEmptyElement("comment");
+ }
+ w.writeEndElement(); // group
+ }
+ w.writeEndElement(); // groupSet
+
+
+ w.writeStartElement("screenSetting");
+ w.writeAttribute("origin", "Normal");
+ {
+ w.writeEmptyElement("layoutSize");
+ w.writeAttribute("x", QString::number(layout->width));
+ w.writeAttribute("y", QString::number(layout->height));
+
+ w.writeStartElement("backGround");
+ {
+ w.writeEmptyElement("color");
+ w.writeAttribute("r", "169");
+ w.writeAttribute("g", "169");
+ w.writeAttribute("b", "169");
+ w.writeAttribute("a", "255");
+ }
+ w.writeEndElement(); // backGround
+
+ w.writeStartElement("grid");
+ w.writeAttribute("thickLineInterval", "40");
+ w.writeAttribute("thinDivisionNum", "4");
+ w.writeAttribute("visible", "true");
+ w.writeAttribute("moveMethod", "Grid");
+ {
+ w.writeEmptyElement("color");
+ w.writeAttribute("r", "128");
+ w.writeAttribute("g", "128");
+ w.writeAttribute("b", "64");
+ w.writeAttribute("a", "128");
+ }
+ w.writeEndElement(); // grid
+ }
+ w.writeEndElement(); // screenSetting
+
+
+ const char *TexFormats[] = {
+ "I4", "I8", "IA4", "IA8", "RGB565", "RGB5A3", "RGBA8",
+ "xxx", "C4x", "C8x", "C14x", "xxx", "xxx", "xxx", "CMPR"
+ };
+
+ QStringList texfiles = layout->generateTextureRefs();
+ foreach (const QString &texfile, texfiles) {
+ QString stripname = texfile;
+ stripname.replace(".tpl", "");
+
+ w.writeEmptyElement("textureFile");
+ w.writeAttribute("imagePath", QString("texture\\%1.tga").arg(stripname));
+ w.writeAttribute("format", TexFormats[(int)texFormats.value(stripname)]);
+ }
+
+
+ // find out all the fonts that are used
+ QStringList fonts;
+ layout->rootPane->addFontRefsToList(fonts);
+
+ foreach (const QString &fontfile, fonts) {
+ w.writeEmptyElement("fontFile");
+ w.writeAttribute("path", QString("..\\fonts\\%1").arg(fontfile));
+ }
+
+
+ // Finish up
+
+
+ w.writeEndElement(); // rlyt
+ w.writeEndElement(); // body
+
+ w.writeEndDocument();
+}
+
+
+static void exportTexture(const WiiTPLTexture &tex, QIODevice *outputDevice) {
+ QDataStream out(outputDevice);
+ out.setByteOrder(QDataStream::LittleEndian);
+
+ out << (quint8)0x14; // ID length
+ out << (quint8)0; // no colour map
+ out << (quint8)2; // uncompressed true colour
+ WritePadding(5, out); // colour map spec, not used
+ out << (quint16)0; // X origin
+ out << (quint16)0; // Y origin
+ out << (quint16)tex.image.width(); // width
+ out << (quint16)tex.image.height(); // height
+ out << (quint8)32; // bit depth
+ out << (quint8)8; // alpha channel, direction (dir not used)
+
+ out.writeRawData("NW4R_Tga Ver1.0\0\0\0\xc1\xb0", 0x14);
+
+ // put together the image
+ for (int y = tex.image.height() - 1; y >= 0; y--) {
+ const QRgb *line = (const QRgb*)tex.image.scanLine(y);
+ for (int x = 0; x < tex.image.width(); x++) {
+ out << (quint32)line[x];
+ }
+ }
+
+ // now, data...
+ static const char *TexFormats[] = {
+ "i4", "i8", "ia4", "ia8", "rgb565", "rgb5a3", "rgba8",
+ "XXX", "c4", "c8", "c14", "XXX", "XXX", "XXX", "cmpr"
+ };
+
+ out.setByteOrder(QDataStream::BigEndian);
+
+ // format block
+ out.writeRawData("nw4r_tfm", 8);
+ const char *tfm = TexFormats[(int)tex.format];
+ quint32 tfm_size = 12 + qstrlen(tfm);
+ out << tfm_size;
+ out.writeRawData(tfm, qstrlen(tfm));
+
+ // end block
+ out.writeRawData("nw4r_end", 8);
+ out << (quint32)12;
+}
+
+#include "lyt/animation.h"
+
+static int AnimKeyFor(LYTAnimPiece::Type type, const LYTAnimEntry &entry) {
+ switch (type) {
+ case LYTAnimPiece::TexSRTAnim:
+ case LYTAnimPiece::IndTexSRTAnim:
+ case LYTAnimPiece::TexPatAnim:
+ return (entry.id << 8) | entry.target;
+ default:
+ return entry.target;
+ }
+}
+
+struct AnimExportThing {
+ QVector<LYTAnimHermiteKey> hermites;
+ QVector<LYTAnimStepKey> steps;
+ QStringList refRes;
+};
+
+void LSExportPackage(LYTPackageBase *pkg, QString dirStr) {
+ QDir dir(dirStr);
+ if (!dir.exists())
+ dir.mkpath(".");
+
+ if (!dir.exists("texture"))
+ dir.mkdir("texture");
+
+ QMap<QString, GX::TextureFormat> texFormats;
+
+ QStringList texes = pkg->listTextures();
+ foreach (const QString &tplName, texes) {
+ if (!tplName.endsWith(".tpl"))
+ continue;
+
+ QByteArray tplData = pkg->getTexture(tplName);
+ QDataStream ds(tplData);
+ WiiTexPalette tpl(ds);
+
+ const WiiTPLTexture &tex = tpl.textures.first();
+
+ QString texName = tplName;
+ texName.replace(".tpl", "");
+
+ texFormats.insert(texName, tex.format);
+
+ QFile tgaFile(dir.filePath(QString("texture/%1.tga").arg(texName)));
+ tgaFile.open(QFile::WriteOnly);
+ exportTexture(tex, &tgaFile);
+ tgaFile.close();
+ }
+
+ QString brlytName = pkg->listLayouts().first();
+ QString lytName = brlytName;
+ lytName.replace(".brlyt", "");
+
+ LYTLayout layout(*pkg, brlytName);
+
+ QFile rlytFile(dir.filePath(QString("%1.rlyt").arg(lytName)));
+ rlytFile.open(QFile::WriteOnly);
+ exportRlyt(&layout, &rlytFile, texFormats);
+ rlytFile.close();
+
+
+ // Now handle the rlan
+ QStringList brlans = pkg->listAnims();
+ QMap<int, LYTAnimation *> animPtrsByTag;
+ foreach (const QString &brlanName, brlans) {
+ if (!brlanName.endsWith(".brlan"))
+ continue;
+
+ QByteArray brlanData = pkg->getAnim(brlanName);
+ LYTAnimation *anim = new LYTAnimation(brlanData);
+
+ animPtrsByTag.insert(anim->sourceTagNumber, anim);
+ }
+
+ QFile rlanFile(dir.filePath(QString("%1.rlan").arg(lytName)));
+ rlanFile.open(QFile::WriteOnly);
+ QXmlStreamWriter aw(&rlanFile);
+
+ aw.setAutoFormatting(true);
+ aw.writeStartDocument();
+
+ aw.writeStartElement("nw4r_layout");
+ aw.writeDefaultNamespace("http://www.nintendo.co.jp/NW4R/LayoutEditor");
+ aw.writeAttribute("version", "1.2.0");
+
+ aw.writeStartElement("head");
+ {
+ aw.writeEmptyElement("create");
+ aw.writeAttribute("user", "someone");
+ aw.writeAttribute("host", "somewhere");
+ aw.writeAttribute("date", "2006-07-26T10:20:54.551+09:00");
+ aw.writeAttribute("source", "");
+
+ aw.writeEmptyElement("title");
+
+ aw.writeEmptyElement("comment");
+
+ aw.writeEmptyElement("generator");
+ aw.writeAttribute("name", "LayoutStudio");
+ aw.writeAttribute("version", "NONE_OF_YOUR_BUSINESS");
+ }
+ aw.writeEndElement(); // head
+
+ aw.writeStartElement("body");
+ {
+ int minFrame = 0x7fffffff, maxFrame = -1;
+ foreach (const LYTAnimation *anim, animPtrsByTag) {
+ aw.writeStartElement("animTag");
+ aw.writeAttribute("name", anim->name);
+ aw.writeAttribute("startFrame", QString::number(anim->sourceStartFrame));
+ aw.writeAttribute("endFrame", QString::number(anim->sourceEndFrame));
+ aw.writeAttribute("fileName", anim->name);
+ aw.writeAttribute("descendingBind", anim->recursiveBind?"true":"false");
+ aw.writeAttribute("animLoop", anim->loop?"Loop":"OneTime");
+
+ if (anim->sourceStartFrame < minFrame)
+ minFrame = anim->sourceStartFrame;
+ if (anim->sourceEndFrame > maxFrame)
+ maxFrame = anim->sourceEndFrame;
+
+ aw.writeEmptyElement("comment");
+
+ foreach (const LYTAnimGroupInfo &grp, anim->groups) {
+ aw.writeEmptyElement("group");
+ aw.writeAttribute("name", grp.name);
+ }
+
+ aw.writeEndElement(); // animTag
+ }
+
+ // This is probably not the most efficient way to do this
+ // but who gives a shit
+ QSet<LYTAnimPiece::Type> usedAnimTypes;
+ foreach (const LYTAnimation *anim, animPtrsByTag) {
+ foreach (const LYTAnimBlock &block, anim->blocks) {
+ foreach (const LYTAnimPiece &piece, block.pieces) {
+ usedAnimTypes.insert(piece.type);
+ }
+ }
+ }
+
+ // Now do processing for each type
+ static const char *AnimTypes[] = {
+ "PainSRT", "TextureSRT", "Visibility", "VertexColor",
+ "MaterialColor", "TexturePattern", "IndTextureSRT"
+ };
+ foreach (LYTAnimPiece::Type type, usedAnimTypes) {
+ aw.writeStartElement("rlan");
+ aw.writeAttribute("animType", AnimTypes[(int)type]);
+ aw.writeAttribute("startFrame", QString::number(minFrame));
+ aw.writeAttribute("endFrame", QString::number(maxFrame));
+ aw.writeAttribute("convertStartFrame", QString::number(minFrame));
+ aw.writeAttribute("convertEndFrame", QString::number(maxFrame));
+
+ // collect the contents
+ QMap<QString,bool> contentsUsed;
+
+ foreach (const LYTAnimation *anim, animPtrsByTag) {
+ foreach (const LYTAnimBlock &block, anim->blocks) {
+ foreach (const LYTAnimPiece &piece, block.pieces) {
+ if (piece.type == type)
+ contentsUsed.insert(block.name, true);
+ }
+ }
+ }
+
+ // now put them together!
+ QMapIterator<QString,bool> contentsUsedIter(contentsUsed);
+ while (contentsUsedIter.hasNext()) {
+ contentsUsedIter.next();
+
+ aw.writeStartElement("animContent");
+ aw.writeAttribute("name", contentsUsedIter.key());
+
+ // Collect the targets being used
+ QMap<int,AnimExportThing> targetsUsed;
+ foreach (const LYTAnimation *anim, animPtrsByTag) {
+ foreach (const LYTAnimBlock &block, anim->blocks) {
+ if (block.name != contentsUsedIter.key())
+ continue;
+
+ foreach (const LYTAnimPiece &piece, block.pieces) {
+ if (piece.type != type)
+ continue;
+
+ foreach (const LYTAnimEntry &entry, piece.entries) {
+ int key = AnimKeyFor(type, entry);
+ if (!targetsUsed.contains(key))
+ targetsUsed.insert(key, AnimExportThing());
+
+ AnimExportThing &thing = targetsUsed[key];
+ if (entry.curveType == 0)
+ qFatal("what!");
+ else if (entry.curveType == 1) {
+ // step keys
+ foreach (const LYTAnimStepKey &key, entry.stepKeys) {
+ LYTAnimStepKey newKey = key;
+ newKey.frame += anim->sourceStartFrame;
+ thing.steps.append(newKey);
+ }
+ } else if (entry.curveType == 2) {
+ // hermite keys
+ foreach (const LYTAnimHermiteKey &key, entry.keys) {
+ LYTAnimHermiteKey newKey = key;
+ newKey.frame += anim->sourceStartFrame;
+ thing.hermites.append(newKey);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Now export them...
+ QMapIterator<int,AnimExportThing> tuIter(targetsUsed);
+ while (tuIter.hasNext()) {
+ tuIter.next();
+
+ // assemble the start tag
+ int ak = tuIter.key();
+
+ static const char *PainTargets[] = {
+ "TranslateX", "TranslateY", "TranslateZ",
+ "RotateX", "RotateY", "RotateZ",
+ "ScaleX", "ScaleY",
+ "SizeW", "SizeH"
+ };
+
+ static const char *VtxColTargets[] = {
+ "LT_r", "LT_g", "LT_b", "LT_a",
+ "RT_r", "RT_g", "RT_b", "RT_a",
+ "LB_r", "LB_g", "LB_b", "LB_a",
+ "RB_r", "RB_g", "RB_b", "RB_a",
+ "PaneAlpha"
+ };
+
+ static const char *MatColTargets[] = {
+ "MatColor0_r", "MatColor0_g", "MatColor0_b", "MatColor0_a",
+ "TevColor0_r", "TevColor0_g", "TevColor0_b", "TevColor0_a",
+ "TevColor1_r", "TevColor1_g", "TevColor1_b", "TevColor1_a",
+ "TevColor2_r", "TevColor2_g", "TevColor2_b", "TevColor2_a",
+ "TevKonst0_r", "TevKonst0_g", "TevKonst0_b", "TevKonst0_a",
+ "TevKonst1_r", "TevKonst1_g", "TevKonst1_b", "TevKonst1_a",
+ "TevKonst2_r", "TevKonst2_g", "TevKonst2_b", "TevKonst2_a",
+ "TevKonst3_r", "TevKonst3_g", "TevKonst3_b", "TevKonst3_a"
+ };
+
+ static const char *SRTTargets[] = {
+ "TranslateS", "TranslateT", "Rotate", "ScaleS", "ScaleT"
+ };
+
+ static const char *PatTargets[] = {
+ "Image", "Palette"
+ };
+
+ switch (type) {
+ case LYTAnimPiece::PaneAnim:
+ aw.writeStartElement("animPainSRTTarget");
+ aw.writeAttribute("target", PainTargets[ak]);
+ break;
+ case LYTAnimPiece::VisAnim:
+ aw.writeStartElement("animVisibilityTarget");
+ aw.writeAttribute("target", "Visibility");
+ break;
+ case LYTAnimPiece::VtxClrAnim:
+ aw.writeStartElement("animVertexColorTarget");
+ aw.writeAttribute("target", VtxColTargets[ak]);
+ break;
+ case LYTAnimPiece::MatClrAnim:
+ aw.writeStartElement("animMaterialColorTarget");
+ aw.writeAttribute("target", MatColTargets[ak]);
+ break;
+ case LYTAnimPiece::TexSRTAnim:
+ aw.writeStartElement("animTexSRTTarget");
+ aw.writeAttribute("id", QString::number(ak>>8));
+ aw.writeAttribute("target", SRTTargets[ak&0xFF]);
+ break;
+ case LYTAnimPiece::IndTexSRTAnim:
+ aw.writeStartElement("animIndTexSRTTarget");
+ aw.writeAttribute("id", QString::number(ak>>8));
+ aw.writeAttribute("target", SRTTargets[ak&0xFF]);
+ break;
+ case LYTAnimPiece::TexPatAnim:
+ aw.writeStartElement("animTexPatternTarget");
+ aw.writeAttribute("id", QString::number(ak>>8));
+ aw.writeAttribute("target", PatTargets[ak&0xFF]);
+
+ //foreach (const QString &bee, )
+ break;
+ }
+
+ foreach (const LYTAnimHermiteKey &key, tuIter.value().hermites) {
+ aw.writeEmptyElement("key");
+ aw.writeAttribute("frame", QString::number(key.frame));
+ aw.writeAttribute("value", QString::number(key.value));
+ aw.writeAttribute("slope", QString::number(key.slope));
+ }
+
+ foreach (const LYTAnimStepKey &key, tuIter.value().steps) {
+ aw.writeEmptyElement("key");
+ aw.writeAttribute("frame", QString::number(key.frame));
+ aw.writeAttribute("value", QString::number(key.value));
+ aw.writeAttribute("slopeType", "Step");
+ }
+
+ aw.writeEndElement(); // target thing
+ }
+
+ aw.writeEndElement(); // animContent
+ }
+
+ aw.writeEndElement(); // rlan
+ }
+ }
+ aw.writeEndElement(); // body
+
+ aw.writeEndElement(); // nw4r_layout
+
+ aw.writeEndDocument();
+
+ rlanFile.close();
+
+ qDeleteAll(animPtrsByTag.begin(), animPtrsByTag.end());
+}