2015-09-07 04:05:44 +00:00
|
|
|
#include "CMDL.hpp"
|
2016-03-02 07:29:19 +00:00
|
|
|
#include "../DNAMP1/CMDLMaterials.hpp"
|
|
|
|
#include "../DNAMP1/CSKR.hpp"
|
2016-08-10 02:52:00 +00:00
|
|
|
#include "../DNAMP1/MREA.hpp"
|
2016-03-02 07:29:19 +00:00
|
|
|
#include "../DNAMP2/CMDLMaterials.hpp"
|
|
|
|
#include "../DNAMP2/CSKR.hpp"
|
|
|
|
#include "../DNAMP3/CMDLMaterials.hpp"
|
|
|
|
#include "../DNAMP3/CSKR.hpp"
|
2016-08-10 21:54:53 +00:00
|
|
|
#include "zeus/CAABox.hpp"
|
2017-12-29 08:08:12 +00:00
|
|
|
#include "hecl/Blender/Connection.hpp"
|
2015-09-07 04:05:44 +00:00
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
namespace DataSpec::DNACMDL {
|
2015-09-07 04:05:44 +00:00
|
|
|
|
2016-03-02 07:29:19 +00:00
|
|
|
template <class MaterialSet>
|
2018-12-08 05:30:43 +00:00
|
|
|
void GetVertexAttributes(const MaterialSet& matSet, std::vector<VertexAttributes>& attributesOut) {
|
|
|
|
attributesOut.clear();
|
|
|
|
attributesOut.reserve(matSet.materials.size());
|
|
|
|
|
|
|
|
for (const typename MaterialSet::Material& mat : matSet.materials) {
|
|
|
|
const typename MaterialSet::Material::VAFlags& vaFlags = mat.getVAFlags();
|
|
|
|
attributesOut.emplace_back();
|
|
|
|
VertexAttributes& va = attributesOut.back();
|
|
|
|
|
|
|
|
va.pos = vaFlags.position();
|
|
|
|
va.norm = vaFlags.normal();
|
|
|
|
va.color0 = vaFlags.color0();
|
|
|
|
va.color1 = vaFlags.color1();
|
|
|
|
|
|
|
|
if ((va.uvs[0] = vaFlags.tex0()))
|
|
|
|
++va.uvCount;
|
|
|
|
if ((va.uvs[1] = vaFlags.tex1()))
|
|
|
|
++va.uvCount;
|
|
|
|
if ((va.uvs[2] = vaFlags.tex2()))
|
|
|
|
++va.uvCount;
|
|
|
|
if ((va.uvs[3] = vaFlags.tex3()))
|
|
|
|
++va.uvCount;
|
|
|
|
if ((va.uvs[4] = vaFlags.tex4()))
|
|
|
|
++va.uvCount;
|
|
|
|
if ((va.uvs[5] = vaFlags.tex5()))
|
|
|
|
++va.uvCount;
|
|
|
|
if ((va.uvs[6] = vaFlags.tex6()))
|
|
|
|
++va.uvCount;
|
|
|
|
|
|
|
|
va.pnMtxIdx = vaFlags.pnMatIdx();
|
|
|
|
|
|
|
|
if ((va.texMtxIdx[0] = vaFlags.tex0MatIdx()))
|
|
|
|
++va.texMtxIdxCount;
|
|
|
|
if ((va.texMtxIdx[1] = vaFlags.tex1MatIdx()))
|
|
|
|
++va.texMtxIdxCount;
|
|
|
|
if ((va.texMtxIdx[2] = vaFlags.tex2MatIdx()))
|
|
|
|
++va.texMtxIdxCount;
|
|
|
|
if ((va.texMtxIdx[3] = vaFlags.tex3MatIdx()))
|
|
|
|
++va.texMtxIdxCount;
|
|
|
|
if ((va.texMtxIdx[4] = vaFlags.tex4MatIdx()))
|
|
|
|
++va.texMtxIdxCount;
|
|
|
|
if ((va.texMtxIdx[5] = vaFlags.tex5MatIdx()))
|
|
|
|
++va.texMtxIdxCount;
|
|
|
|
if ((va.texMtxIdx[6] = vaFlags.tex6MatIdx()))
|
|
|
|
++va.texMtxIdxCount;
|
|
|
|
|
|
|
|
va.shortUVs = mat.getFlags().lightmapUVArray();
|
|
|
|
}
|
2016-03-02 07:29:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
template <class PAKRouter, class MaterialSet>
|
2018-12-08 05:30:43 +00:00
|
|
|
void ReadMaterialSetToBlender_1_2(hecl::blender::PyOutStream& os, const MaterialSet& matSet, const PAKRouter& pakRouter,
|
|
|
|
const typename PAKRouter::EntryType& entry, unsigned setIdx) {
|
|
|
|
/* Texmaps */
|
|
|
|
os << "texmap_list = []\n";
|
|
|
|
for (const UniqueID32& tex : matSet.head.textureIDs) {
|
|
|
|
std::string texName = pakRouter.getBestEntryName(tex);
|
|
|
|
const nod::Node* node;
|
|
|
|
const typename PAKRouter::EntryType* texEntry = pakRouter.lookupEntry(tex, &node);
|
|
|
|
hecl::ProjectPath txtrPath = pakRouter.getWorking(texEntry);
|
|
|
|
if (!txtrPath.isNone()) {
|
|
|
|
txtrPath.makeDirChain(false);
|
|
|
|
PAKEntryReadStream rs = texEntry->beginReadStream(*node);
|
|
|
|
TXTR::Extract(rs, txtrPath);
|
2016-03-02 07:29:19 +00:00
|
|
|
}
|
2018-12-08 05:30:43 +00:00
|
|
|
hecl::SystemString resPath = pakRouter.getResourceRelativePath(entry, tex);
|
|
|
|
hecl::SystemUTF8Conv resPathView(resPath);
|
2019-07-20 04:27:21 +00:00
|
|
|
os.format(fmt(
|
|
|
|
"if '{}' in bpy.data.images:\n"
|
|
|
|
" image = bpy.data.images['{}']\n"
|
2018-12-08 05:30:43 +00:00
|
|
|
"else:\n"
|
2019-07-20 04:27:21 +00:00
|
|
|
" image = bpy.data.images.load('''//{}''')\n"
|
|
|
|
" image.name = '{}'\n"
|
2019-05-08 03:50:21 +00:00
|
|
|
"texmap_list.append(image)\n"
|
2019-07-20 04:27:21 +00:00
|
|
|
"\n"),
|
|
|
|
texName, texName, resPathView, texName);
|
2018-12-08 05:30:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
unsigned m = 0;
|
|
|
|
for (const typename MaterialSet::Material& mat : matSet.materials) {
|
|
|
|
MaterialSet::ConstructMaterial(os, mat, setIdx, m++);
|
|
|
|
os << "materials.append(new_material)\n";
|
|
|
|
}
|
2016-03-02 07:29:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
template <class PAKRouter, class MaterialSet>
|
2018-12-08 05:30:43 +00:00
|
|
|
void ReadMaterialSetToBlender_3(hecl::blender::PyOutStream& os, const MaterialSet& matSet, const PAKRouter& pakRouter,
|
|
|
|
const typename PAKRouter::EntryType& entry, unsigned setIdx) {
|
|
|
|
unsigned m = 0;
|
|
|
|
for (const typename MaterialSet::Material& mat : matSet.materials) {
|
|
|
|
MaterialSet::ConstructMaterial(os, pakRouter, entry, mat, setIdx, m++);
|
|
|
|
os << "materials.append(new_material)\n";
|
|
|
|
}
|
2016-03-02 07:29:19 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
template void ReadMaterialSetToBlender_3<PAKRouter<DNAMP3::PAKBridge>, DNAMP3::MaterialSet>(
|
|
|
|
hecl::blender::PyOutStream& os, const DNAMP3::MaterialSet& matSet, const PAKRouter<DNAMP3::PAKBridge>& pakRouter,
|
|
|
|
const PAKRouter<DNAMP3::PAKBridge>::EntryType& entry, unsigned setIdx);
|
2016-03-04 05:28:20 +00:00
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
class DLReader {
|
2016-03-02 07:29:19 +00:00
|
|
|
public:
|
2018-12-08 05:30:43 +00:00
|
|
|
/* Class used for splitting verts with shared positions but different skinning matrices */
|
|
|
|
class ExtraVertTracker {
|
|
|
|
std::map<atUint16, std::vector<std::pair<atInt16, atUint16>>> m_extraVerts;
|
|
|
|
atUint16 m_maxBasePos = 0;
|
|
|
|
atUint16 m_nextOverPos = 1;
|
|
|
|
|
|
|
|
public:
|
|
|
|
atInt16 addPosSkinPair(atUint16 pos, atInt16 skin) {
|
|
|
|
m_maxBasePos = std::max(m_maxBasePos, pos);
|
|
|
|
auto search = m_extraVerts.find(pos);
|
|
|
|
if (search == m_extraVerts.end()) {
|
|
|
|
m_extraVerts[pos] = {std::make_pair(skin, 0)};
|
|
|
|
return skin;
|
|
|
|
}
|
|
|
|
std::vector<std::pair<atInt16, atUint16>>& vertTrack = search->second;
|
|
|
|
for (const std::pair<atInt16, atUint16>& s : vertTrack)
|
|
|
|
if (s.first == skin)
|
|
|
|
return vertTrack.front().first;
|
|
|
|
vertTrack.push_back(std::make_pair(skin, m_nextOverPos++));
|
|
|
|
return vertTrack.front().first;
|
|
|
|
}
|
2016-03-02 07:29:19 +00:00
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
template <class RigPair>
|
|
|
|
atUint32 sendAdditionalVertsToBlender(hecl::blender::PyOutStream& os, const RigPair& rp, atUint32 baseVert) const {
|
|
|
|
atUint32 addedVerts = 0;
|
|
|
|
atUint32 nextVert = 1;
|
|
|
|
while (nextVert < m_nextOverPos) {
|
|
|
|
for (const std::pair<atUint16, std::vector<std::pair<atInt16, atUint16>>>& ev : m_extraVerts) {
|
|
|
|
for (const std::pair<atInt16, atUint16>& se : ev.second) {
|
|
|
|
if (se.second == nextVert) {
|
2019-07-20 04:27:21 +00:00
|
|
|
os.format(fmt(
|
2018-12-08 05:30:43 +00:00
|
|
|
"bm.verts.ensure_lookup_table()\n"
|
2019-07-20 04:27:21 +00:00
|
|
|
"orig_vert = bm.verts[{}]\n"
|
|
|
|
"vert = bm.verts.new(orig_vert.co)\n"),
|
2018-12-08 05:30:43 +00:00
|
|
|
ev.first + baseVert);
|
|
|
|
rp.first->weightVertex(os, *rp.second, se.first);
|
|
|
|
++nextVert;
|
|
|
|
++addedVerts;
|
2016-03-02 07:29:19 +00:00
|
|
|
}
|
2018-12-08 05:30:43 +00:00
|
|
|
}
|
2016-03-02 07:29:19 +00:00
|
|
|
}
|
2018-12-08 05:30:43 +00:00
|
|
|
}
|
|
|
|
return addedVerts;
|
2016-03-02 07:29:19 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
atUint16 lookupVertIdx(atUint16 pos, atInt16 skin) const {
|
|
|
|
auto search = m_extraVerts.find(pos);
|
|
|
|
if (search == m_extraVerts.end())
|
|
|
|
return -1;
|
|
|
|
const std::vector<std::pair<atInt16, atUint16>>& vertTrack = search->second;
|
|
|
|
if (vertTrack.front().first == skin)
|
|
|
|
return pos;
|
|
|
|
for (auto it = vertTrack.begin() + 1; it != vertTrack.end(); ++it)
|
|
|
|
if (it->first == skin)
|
|
|
|
return m_maxBasePos + it->second;
|
|
|
|
return -1;
|
2016-03-02 07:29:19 +00:00
|
|
|
}
|
2018-12-08 05:30:43 +00:00
|
|
|
};
|
2016-03-02 07:29:19 +00:00
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
private:
|
|
|
|
const VertexAttributes& m_va;
|
|
|
|
std::unique_ptr<atUint8[]> m_dl;
|
|
|
|
size_t m_dlSize;
|
|
|
|
ExtraVertTracker& m_evt;
|
|
|
|
const atInt16* m_bankIn;
|
|
|
|
atUint8* m_cur;
|
|
|
|
atUint16 readVal(GX::AttrType type) {
|
|
|
|
atUint16 retval = 0;
|
|
|
|
switch (type) {
|
|
|
|
case GX::DIRECT:
|
|
|
|
case GX::INDEX8:
|
|
|
|
if ((m_cur - m_dl.get()) >= intptr_t(m_dlSize))
|
|
|
|
return 0;
|
|
|
|
retval = *m_cur;
|
|
|
|
++m_cur;
|
|
|
|
break;
|
|
|
|
case GX::INDEX16:
|
|
|
|
if ((m_cur - m_dl.get() + 1) >= intptr_t(m_dlSize))
|
|
|
|
return 0;
|
|
|
|
retval = hecl::SBig(*(atUint16*)m_cur);
|
|
|
|
m_cur += 2;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
2016-03-02 07:29:19 +00:00
|
|
|
}
|
2018-12-08 05:30:43 +00:00
|
|
|
return retval;
|
|
|
|
}
|
2016-03-02 07:29:19 +00:00
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
public:
|
|
|
|
DLReader(const VertexAttributes& va, std::unique_ptr<atUint8[]>&& dl, size_t dlSize, ExtraVertTracker& evt,
|
|
|
|
const atInt16* bankIn = nullptr)
|
|
|
|
: m_va(va), m_dl(std::move(dl)), m_dlSize(dlSize), m_evt(evt), m_bankIn(bankIn) {
|
|
|
|
m_cur = m_dl.get();
|
|
|
|
}
|
|
|
|
|
2019-08-15 02:20:37 +00:00
|
|
|
explicit operator bool() const { return ((m_cur - m_dl.get()) < intptr_t(m_dlSize)) && *m_cur; }
|
2018-12-08 05:30:43 +00:00
|
|
|
|
|
|
|
GX::Primitive readPrimitive() { return GX::Primitive(*m_cur++ & 0xf8); }
|
|
|
|
|
|
|
|
GX::Primitive readPrimitiveAndVat(unsigned& vatOut) {
|
|
|
|
atUint8 val = *m_cur++;
|
|
|
|
vatOut = val & 0x7;
|
|
|
|
return GX::Primitive(val & 0xf8);
|
|
|
|
}
|
|
|
|
|
|
|
|
atUint16 readVertCount() {
|
|
|
|
atUint16 retval = hecl::SBig(*(atUint16*)m_cur);
|
|
|
|
m_cur += 2;
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct DLPrimVert {
|
|
|
|
atUint16 pos = 0;
|
|
|
|
atUint16 norm = 0;
|
|
|
|
atUint16 color[2] = {0};
|
|
|
|
atUint16 uvs[7] = {0};
|
|
|
|
atUint8 pnMtxIdx = 0;
|
|
|
|
atUint8 texMtxIdx[7] = {0};
|
|
|
|
};
|
|
|
|
|
|
|
|
DLPrimVert readVert(bool peek = false) {
|
|
|
|
atUint8* bakCur = m_cur;
|
|
|
|
DLPrimVert retval;
|
|
|
|
retval.pnMtxIdx = readVal(m_va.pnMtxIdx);
|
|
|
|
retval.texMtxIdx[0] = readVal(m_va.texMtxIdx[0]);
|
|
|
|
retval.texMtxIdx[1] = readVal(m_va.texMtxIdx[1]);
|
|
|
|
retval.texMtxIdx[2] = readVal(m_va.texMtxIdx[2]);
|
|
|
|
retval.texMtxIdx[3] = readVal(m_va.texMtxIdx[3]);
|
|
|
|
retval.texMtxIdx[4] = readVal(m_va.texMtxIdx[4]);
|
|
|
|
retval.texMtxIdx[5] = readVal(m_va.texMtxIdx[5]);
|
|
|
|
retval.texMtxIdx[6] = readVal(m_va.texMtxIdx[6]);
|
|
|
|
if (m_bankIn) {
|
|
|
|
atUint16 posIdx = readVal(m_va.pos);
|
|
|
|
atUint8 mtxIdx = retval.pnMtxIdx / 3;
|
|
|
|
atInt16 skinIdx = -1;
|
|
|
|
if (mtxIdx < 10)
|
|
|
|
skinIdx = m_bankIn[mtxIdx];
|
|
|
|
retval.pos = m_evt.lookupVertIdx(posIdx, skinIdx);
|
|
|
|
} else
|
|
|
|
retval.pos = readVal(m_va.pos);
|
|
|
|
retval.norm = readVal(m_va.norm);
|
|
|
|
retval.color[0] = readVal(m_va.color0);
|
|
|
|
retval.color[1] = readVal(m_va.color1);
|
|
|
|
retval.uvs[0] = readVal(m_va.uvs[0]);
|
|
|
|
retval.uvs[1] = readVal(m_va.uvs[1]);
|
|
|
|
retval.uvs[2] = readVal(m_va.uvs[2]);
|
|
|
|
retval.uvs[3] = readVal(m_va.uvs[3]);
|
|
|
|
retval.uvs[4] = readVal(m_va.uvs[4]);
|
|
|
|
retval.uvs[5] = readVal(m_va.uvs[5]);
|
|
|
|
retval.uvs[6] = readVal(m_va.uvs[6]);
|
|
|
|
if (peek)
|
|
|
|
m_cur = bakCur;
|
|
|
|
return retval;
|
|
|
|
}
|
|
|
|
|
|
|
|
void preReadMaxIdxs(DLPrimVert& out) {
|
|
|
|
atUint8* bakCur = m_cur;
|
|
|
|
while (*this) {
|
|
|
|
readPrimitive();
|
|
|
|
atUint16 vc = readVertCount();
|
|
|
|
for (atUint16 v = 0; v < vc; ++v) {
|
|
|
|
atUint16 val;
|
|
|
|
val = readVal(m_va.pnMtxIdx);
|
|
|
|
out.pnMtxIdx = std::max(out.pnMtxIdx, atUint8(val));
|
|
|
|
val = readVal(m_va.texMtxIdx[0]);
|
|
|
|
out.texMtxIdx[0] = std::max(out.texMtxIdx[0], atUint8(val));
|
|
|
|
val = readVal(m_va.texMtxIdx[1]);
|
|
|
|
out.texMtxIdx[1] = std::max(out.texMtxIdx[1], atUint8(val));
|
|
|
|
val = readVal(m_va.texMtxIdx[2]);
|
|
|
|
out.texMtxIdx[2] = std::max(out.texMtxIdx[2], atUint8(val));
|
|
|
|
val = readVal(m_va.texMtxIdx[3]);
|
|
|
|
out.texMtxIdx[3] = std::max(out.texMtxIdx[3], atUint8(val));
|
|
|
|
val = readVal(m_va.texMtxIdx[4]);
|
|
|
|
out.texMtxIdx[4] = std::max(out.texMtxIdx[4], atUint8(val));
|
|
|
|
val = readVal(m_va.texMtxIdx[5]);
|
|
|
|
out.texMtxIdx[5] = std::max(out.texMtxIdx[5], atUint8(val));
|
|
|
|
val = readVal(m_va.texMtxIdx[6]);
|
|
|
|
out.texMtxIdx[6] = std::max(out.texMtxIdx[6], atUint8(val));
|
|
|
|
val = readVal(m_va.pos);
|
|
|
|
out.pos = std::max(out.pos, val);
|
|
|
|
val = readVal(m_va.norm);
|
|
|
|
out.norm = std::max(out.norm, val);
|
|
|
|
val = readVal(m_va.color0);
|
|
|
|
out.color[0] = std::max(out.color[0], val);
|
|
|
|
val = readVal(m_va.color1);
|
|
|
|
out.color[1] = std::max(out.color[1], val);
|
|
|
|
val = readVal(m_va.uvs[0]);
|
|
|
|
out.uvs[0] = std::max(out.uvs[0], val);
|
|
|
|
val = readVal(m_va.uvs[1]);
|
|
|
|
out.uvs[1] = std::max(out.uvs[1], val);
|
|
|
|
val = readVal(m_va.uvs[2]);
|
|
|
|
out.uvs[2] = std::max(out.uvs[2], val);
|
|
|
|
val = readVal(m_va.uvs[3]);
|
|
|
|
out.uvs[3] = std::max(out.uvs[3], val);
|
|
|
|
val = readVal(m_va.uvs[4]);
|
|
|
|
out.uvs[4] = std::max(out.uvs[4], val);
|
|
|
|
val = readVal(m_va.uvs[5]);
|
|
|
|
out.uvs[5] = std::max(out.uvs[5], val);
|
|
|
|
val = readVal(m_va.uvs[6]);
|
|
|
|
out.uvs[6] = std::max(out.uvs[6], val);
|
|
|
|
}
|
2016-03-02 07:29:19 +00:00
|
|
|
}
|
2018-12-08 05:30:43 +00:00
|
|
|
m_cur = bakCur;
|
|
|
|
}
|
|
|
|
|
|
|
|
void preReadMaxIdxs(DLPrimVert& out, std::vector<atInt16>& skinOut) {
|
|
|
|
atUint8* bakCur = m_cur;
|
|
|
|
while (*this) {
|
|
|
|
readPrimitive();
|
|
|
|
atUint16 vc = readVertCount();
|
|
|
|
for (atUint16 v = 0; v < vc; ++v) {
|
|
|
|
atUint16 val;
|
|
|
|
atUint8 pnMtxVal = readVal(m_va.pnMtxIdx);
|
|
|
|
out.pnMtxIdx = std::max(out.pnMtxIdx, pnMtxVal);
|
|
|
|
val = readVal(m_va.texMtxIdx[0]);
|
|
|
|
out.texMtxIdx[0] = std::max(out.texMtxIdx[0], atUint8(val));
|
|
|
|
val = readVal(m_va.texMtxIdx[1]);
|
|
|
|
out.texMtxIdx[1] = std::max(out.texMtxIdx[1], atUint8(val));
|
|
|
|
val = readVal(m_va.texMtxIdx[2]);
|
|
|
|
out.texMtxIdx[2] = std::max(out.texMtxIdx[2], atUint8(val));
|
|
|
|
val = readVal(m_va.texMtxIdx[3]);
|
|
|
|
out.texMtxIdx[3] = std::max(out.texMtxIdx[3], atUint8(val));
|
|
|
|
val = readVal(m_va.texMtxIdx[4]);
|
|
|
|
out.texMtxIdx[4] = std::max(out.texMtxIdx[4], atUint8(val));
|
|
|
|
val = readVal(m_va.texMtxIdx[5]);
|
|
|
|
out.texMtxIdx[5] = std::max(out.texMtxIdx[5], atUint8(val));
|
|
|
|
val = readVal(m_va.texMtxIdx[6]);
|
|
|
|
out.texMtxIdx[6] = std::max(out.texMtxIdx[6], atUint8(val));
|
|
|
|
atUint16 posVal = readVal(m_va.pos);
|
|
|
|
out.pos = std::max(out.pos, posVal);
|
|
|
|
val = readVal(m_va.norm);
|
|
|
|
out.norm = std::max(out.norm, val);
|
|
|
|
val = readVal(m_va.color0);
|
|
|
|
out.color[0] = std::max(out.color[0], val);
|
|
|
|
val = readVal(m_va.color1);
|
|
|
|
out.color[1] = std::max(out.color[1], val);
|
|
|
|
val = readVal(m_va.uvs[0]);
|
|
|
|
out.uvs[0] = std::max(out.uvs[0], val);
|
|
|
|
val = readVal(m_va.uvs[1]);
|
|
|
|
out.uvs[1] = std::max(out.uvs[1], val);
|
|
|
|
val = readVal(m_va.uvs[2]);
|
|
|
|
out.uvs[2] = std::max(out.uvs[2], val);
|
|
|
|
val = readVal(m_va.uvs[3]);
|
|
|
|
out.uvs[3] = std::max(out.uvs[3], val);
|
|
|
|
val = readVal(m_va.uvs[4]);
|
|
|
|
out.uvs[4] = std::max(out.uvs[4], val);
|
|
|
|
val = readVal(m_va.uvs[5]);
|
|
|
|
out.uvs[5] = std::max(out.uvs[5], val);
|
|
|
|
val = readVal(m_va.uvs[6]);
|
|
|
|
out.uvs[6] = std::max(out.uvs[6], val);
|
|
|
|
|
|
|
|
atInt16 skinIdx = m_bankIn[pnMtxVal / 3];
|
|
|
|
skinOut[posVal] = m_evt.addPosSkinPair(posVal, skinIdx);
|
|
|
|
}
|
2016-03-02 07:29:19 +00:00
|
|
|
}
|
2018-12-08 05:30:43 +00:00
|
|
|
m_cur = bakCur;
|
|
|
|
}
|
2016-03-02 07:29:19 +00:00
|
|
|
};
|
|
|
|
|
2019-05-08 03:50:21 +00:00
|
|
|
void InitGeomBlenderContext(hecl::blender::PyOutStream& os, const hecl::ProjectPath& masterShaderPath) {
|
2018-12-08 05:30:43 +00:00
|
|
|
os << "import math\n"
|
|
|
|
"from mathutils import Vector\n"
|
|
|
|
"\n"
|
|
|
|
"# Clear Scene\n"
|
2019-06-12 02:05:17 +00:00
|
|
|
"if len(bpy.data.collections):\n"
|
|
|
|
" bpy.data.collections.remove(bpy.data.collections[0])\n"
|
2018-12-08 05:30:43 +00:00
|
|
|
"\n"
|
|
|
|
"def loop_from_facevert(bm, face, vert_idx):\n"
|
|
|
|
" for loop in face.loops:\n"
|
|
|
|
" if loop.vert[bm.verts.layers.int['CMDLOriginalPosIdxs']] == vert_idx:\n"
|
|
|
|
" return loop\n"
|
|
|
|
"\n"
|
|
|
|
"def loops_from_edgevert(bm, edge, vert):\n"
|
|
|
|
" ret = []\n"
|
|
|
|
" for face in edge.link_faces:\n"
|
|
|
|
" for loop in face.loops:\n"
|
|
|
|
" if loop.vert == vert:\n"
|
|
|
|
" ret.append(loop)\n"
|
|
|
|
" return ret\n"
|
|
|
|
"\n"
|
|
|
|
"def add_triangle(bm, vert_seq, vert_indices, norm_seq, norm_indices, mat_nr, od_list, two_face_vert):\n"
|
|
|
|
" if len(set(vert_indices)) != 3:\n"
|
|
|
|
" return None, None\n"
|
|
|
|
"\n"
|
|
|
|
" ret_mesh = bm\n"
|
|
|
|
" vert_seq.ensure_lookup_table()\n"
|
|
|
|
" verts = [vert_seq[i] for i in vert_indices]\n"
|
|
|
|
"\n"
|
|
|
|
" # Try getting existing face\n"
|
|
|
|
" face = bm.faces.get(verts)\n"
|
|
|
|
"\n"
|
|
|
|
" if face is not None and face.material_index != mat_nr: # Same poly, new material\n"
|
|
|
|
" # Overdraw detected; track copy\n"
|
|
|
|
" od_entry = None\n"
|
|
|
|
" for entry in od_list:\n"
|
|
|
|
" if entry['material'] == mat_nr:\n"
|
|
|
|
" od_entry = entry\n"
|
|
|
|
" if od_entry is None:\n"
|
|
|
|
" bm_cpy = bm.copy()\n"
|
|
|
|
" od_entry = {'material':mat_nr, 'bm':bm_cpy}\n"
|
2019-05-08 03:50:21 +00:00
|
|
|
" bmesh.ops.delete(od_entry['bm'], geom=od_entry['bm'].faces, context='FACES_ONLY')\n"
|
2018-12-08 05:30:43 +00:00
|
|
|
" od_list.append(od_entry)\n"
|
|
|
|
" od_entry['bm'].verts.ensure_lookup_table()\n"
|
|
|
|
" verts = [od_entry['bm'].verts[i] for i in vert_indices]\n"
|
|
|
|
" face = od_entry['bm'].faces.get(verts)\n"
|
|
|
|
" if face is None:\n"
|
|
|
|
" face = od_entry['bm'].faces.new(verts)\n"
|
|
|
|
" else: # Probably a double-sided surface\n"
|
|
|
|
" verts = [od_entry['bm'].verts[i + two_face_vert] for i in vert_indices]\n"
|
|
|
|
" face = od_entry['bm'].faces.get(verts)\n"
|
|
|
|
" if face is None:\n"
|
|
|
|
" face = od_entry['bm'].faces.new(verts)\n"
|
|
|
|
" ret_mesh = od_entry['bm']\n"
|
|
|
|
"\n"
|
|
|
|
" elif face is not None: # Same material, probably double-sided\n"
|
|
|
|
" verts = [vert_seq[i + two_face_vert] for i in vert_indices]\n"
|
|
|
|
" face = bm.faces.get(verts)\n"
|
|
|
|
" if face is None:\n"
|
|
|
|
" face = bm.faces.new(verts)\n"
|
|
|
|
"\n"
|
|
|
|
" else: # Make totally new face\n"
|
|
|
|
" face = bm.faces.new(verts)\n"
|
|
|
|
"\n"
|
|
|
|
" for i in range(3):\n"
|
|
|
|
" face.verts[i][ret_mesh.verts.layers.int['CMDLOriginalPosIdxs']] = vert_indices[i]\n"
|
|
|
|
" face.loops[i][ret_mesh.loops.layers.int['CMDLOriginalNormIdxs']] = norm_indices[i]\n"
|
|
|
|
" face.material_index = mat_nr\n"
|
|
|
|
" face.smooth = True\n"
|
|
|
|
"\n"
|
|
|
|
" return face, ret_mesh\n"
|
|
|
|
"\n"
|
|
|
|
"def expand_lightmap_triangle(lightmap_tri_tracker, uva, uvb, uvc):\n"
|
|
|
|
" result = ([uva[0],uva[1]], [uvb[0],uvb[1]], [uvc[0],uvc[1]])\n"
|
|
|
|
" inst = 0\n"
|
|
|
|
" if uva in lightmap_tri_tracker:\n"
|
|
|
|
" inst = lightmap_tri_tracker[uva]\n"
|
|
|
|
" lightmap_tri_tracker[uva] = inst + 1\n"
|
|
|
|
" if uva == uvb:\n"
|
|
|
|
" result[1][0] += 0.005\n"
|
|
|
|
" if uva == uvc:\n"
|
|
|
|
" result[2][1] -= 0.005\n"
|
|
|
|
" if inst & 0x1 and uva == uvb and uva == uvc:\n"
|
|
|
|
" result[0][0] += 0.005\n"
|
|
|
|
" result[0][1] -= 0.005\n"
|
|
|
|
" return result\n"
|
|
|
|
"\n";
|
|
|
|
|
|
|
|
/* Link master shader library */
|
2019-07-20 04:27:21 +00:00
|
|
|
os.format(fmt(
|
2018-12-08 05:30:43 +00:00
|
|
|
"# Master shader library\n"
|
2019-07-20 04:27:21 +00:00
|
|
|
"with bpy.data.libraries.load('{}', link=True, relative=True) as (data_from, data_to):\n"
|
2018-12-08 05:30:43 +00:00
|
|
|
" data_to.node_groups = data_from.node_groups\n"
|
2019-07-20 04:27:21 +00:00
|
|
|
"\n"),
|
|
|
|
masterShaderPath.getAbsolutePathUTF8());
|
2015-09-07 04:05:44 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
void FinishBlenderMesh(hecl::blender::PyOutStream& os, unsigned matSetCount, int meshIdx) {
|
2019-05-08 03:50:21 +00:00
|
|
|
os << "if 'Render' not in bpy.data.collections:\n"
|
|
|
|
" coll = bpy.data.collections.new('Render')\n"
|
|
|
|
" bpy.context.scene.collection.children.link(coll)\n"
|
|
|
|
"else:\n"
|
|
|
|
" coll = bpy.data.collections['Render']\n";
|
|
|
|
if (meshIdx < 0) {
|
|
|
|
os << "mesh = bpy.data.meshes.new(bpy.context.scene.name)\n"
|
|
|
|
"obj = bpy.data.objects.new(mesh.name, mesh)\n"
|
|
|
|
"obj.show_transparent = True\n"
|
|
|
|
"coll.objects.link(obj)\n";
|
2019-07-20 04:27:21 +00:00
|
|
|
os.format(fmt("mesh.hecl_material_count = {}\n"), matSetCount);
|
2019-05-08 03:50:21 +00:00
|
|
|
} else {
|
2019-07-20 04:27:21 +00:00
|
|
|
os.format(fmt("mesh = bpy.data.meshes.new(bpy.context.scene.name + '_{:03d}')\n"), meshIdx);
|
2019-05-08 03:50:21 +00:00
|
|
|
os << "obj = bpy.data.objects.new(mesh.name, mesh)\n"
|
|
|
|
"obj.show_transparent = True\n"
|
|
|
|
"coll.objects.link(obj)\n";
|
2019-07-20 04:27:21 +00:00
|
|
|
os.format(fmt("mesh.hecl_material_count = {}\n"), matSetCount);
|
2019-05-08 03:50:21 +00:00
|
|
|
}
|
2018-12-08 05:30:43 +00:00
|
|
|
|
|
|
|
os << "mesh.use_auto_smooth = True\n"
|
|
|
|
"mesh.auto_smooth_angle = math.pi\n"
|
|
|
|
"\n"
|
|
|
|
"for material in materials:\n"
|
|
|
|
" mesh.materials.append(material)\n"
|
|
|
|
"\n"
|
|
|
|
"# Merge OD meshes\n"
|
|
|
|
"for od_entry in od_list:\n"
|
|
|
|
" vert_dict = [{},{}]\n"
|
|
|
|
"\n"
|
|
|
|
" for vert in od_entry['bm'].verts:\n"
|
|
|
|
" if len(vert.link_faces):\n"
|
|
|
|
" if vert.index >= two_face_vert:\n"
|
|
|
|
" use_vert_dict = vert_dict[1]\n"
|
|
|
|
" else:\n"
|
|
|
|
" use_vert_dict = vert_dict[0]\n"
|
|
|
|
" copy_vert = bm.verts.new(vert.co, vert)\n"
|
|
|
|
" use_vert_dict[vert[od_entry['bm'].verts.layers.int['CMDLOriginalPosIdxs']]] = copy_vert\n"
|
|
|
|
" copy_vert[orig_pidx_lay] = vert[od_entry['bm'].verts.layers.int['CMDLOriginalPosIdxs']]\n"
|
|
|
|
"\n"
|
|
|
|
" for face in od_entry['bm'].faces:\n"
|
|
|
|
" if face.verts[0].index >= two_face_vert:\n"
|
|
|
|
" use_vert_dict = vert_dict[1]\n"
|
|
|
|
" else:\n"
|
|
|
|
" use_vert_dict = vert_dict[0]\n"
|
|
|
|
" merge_verts = [use_vert_dict[fv[od_entry['bm'].verts.layers.int['CMDLOriginalPosIdxs']]] for fv in "
|
|
|
|
"face.verts]\n"
|
|
|
|
" if bm.faces.get(merge_verts) is not None:\n"
|
|
|
|
" continue\n"
|
|
|
|
" merge_face = bm.faces.new(merge_verts)\n"
|
|
|
|
" for i in range(len(face.loops)):\n"
|
|
|
|
" old = face.loops[i]\n"
|
|
|
|
" new = merge_face.loops[i]\n"
|
|
|
|
" for j in range(len(od_entry['bm'].loops.layers.uv)):\n"
|
|
|
|
" new[bm.loops.layers.uv[j]] = old[od_entry['bm'].loops.layers.uv[j]]\n"
|
|
|
|
" new[orig_nidx_lay] = old[od_entry['bm'].loops.layers.int['CMDLOriginalNormIdxs']]\n"
|
|
|
|
" merge_face.smooth = True\n"
|
|
|
|
" merge_face.material_index = face.material_index\n"
|
|
|
|
"\n"
|
|
|
|
" od_entry['bm'].free()\n"
|
|
|
|
"\n"
|
|
|
|
"verts_to_del = []\n"
|
|
|
|
"for v in bm.verts:\n"
|
|
|
|
" if len(v.link_faces) == 0:\n"
|
|
|
|
" verts_to_del.append(v)\n"
|
2019-05-08 03:50:21 +00:00
|
|
|
"bmesh.ops.delete(bm, geom=verts_to_del, context='VERTS')\n"
|
2018-12-08 05:30:43 +00:00
|
|
|
"\n"
|
|
|
|
"for edge in bm.edges:\n"
|
|
|
|
" if edge.is_manifold:\n"
|
|
|
|
" pass_count = 0\n"
|
|
|
|
" for vert in edge.verts:\n"
|
|
|
|
" loops = loops_from_edgevert(bm, edge, vert)\n"
|
|
|
|
" norm0 = Vector(norm_list[loops[0][orig_nidx_lay]])\n"
|
|
|
|
" norm1 = Vector(norm_list[loops[1][orig_nidx_lay]])\n"
|
|
|
|
" if norm0.dot(norm1) < 0.9:\n"
|
|
|
|
" pass_count += 1\n"
|
|
|
|
" if pass_count > 0:\n"
|
|
|
|
" edge.smooth = False\n"
|
|
|
|
"\n"
|
|
|
|
"bm.to_mesh(mesh)\n"
|
|
|
|
"bm.free()\n"
|
|
|
|
"\n"
|
|
|
|
"# Remove redundant materials\n"
|
|
|
|
"present_mats = set()\n"
|
|
|
|
"for poly in mesh.polygons:\n"
|
|
|
|
" present_mats.add(poly.material_index)\n"
|
|
|
|
"for mat_idx in reversed(range(len(mesh.materials))):\n"
|
|
|
|
" if mat_idx not in present_mats:\n"
|
|
|
|
" mesh.materials.pop(index=mat_idx, update_data=True)\n"
|
|
|
|
"\n"
|
|
|
|
"mesh.update()\n"
|
|
|
|
"\n";
|
2015-09-07 04:05:44 +00:00
|
|
|
}
|
|
|
|
|
2016-03-02 07:29:19 +00:00
|
|
|
template <class PAKRouter, class MaterialSet, class RigPair, class SurfaceHeader>
|
2018-12-08 05:30:43 +00:00
|
|
|
atUint32 ReadGeomSectionsToBlender(hecl::blender::PyOutStream& os, athena::io::IStreamReader& reader,
|
|
|
|
PAKRouter& pakRouter, const typename PAKRouter::EntryType& entry, const RigPair& rp,
|
|
|
|
bool shortNormals, bool shortUVs, std::vector<VertexAttributes>& vertAttribs,
|
|
|
|
int meshIdx, atUint32 secCount, atUint32 matSetCount, const atUint32* secSizes,
|
|
|
|
atUint32 surfaceCount) {
|
|
|
|
os << "# Begin bmesh\n"
|
|
|
|
"bm = bmesh.new()\n"
|
|
|
|
"\n"
|
|
|
|
"# Overdraw-tracking\n"
|
|
|
|
"od_list = []\n"
|
|
|
|
"\n"
|
|
|
|
"orig_pidx_lay = bm.verts.layers.int.new('CMDLOriginalPosIdxs')\n"
|
|
|
|
"orig_nidx_lay = bm.loops.layers.int.new('CMDLOriginalNormIdxs')\n"
|
|
|
|
"\n"
|
|
|
|
"lightmap_tri_tracker = {}\n";
|
|
|
|
|
|
|
|
if (rp.first)
|
|
|
|
os << "dvert_lay = bm.verts.layers.deform.verify()\n";
|
|
|
|
|
|
|
|
/* Pre-read pass to determine maximum used vert indices */
|
|
|
|
atUint32 matSecCount = 0;
|
|
|
|
if (matSetCount)
|
|
|
|
matSecCount = MaterialSet::OneSection() ? 1 : matSetCount;
|
|
|
|
bool visitedDLOffsets = false;
|
|
|
|
atUint32 lastDlSec = secCount;
|
|
|
|
atUint64 afterHeaderPos = reader.position();
|
|
|
|
DLReader::DLPrimVert maxIdxs;
|
|
|
|
std::vector<atInt16> skinIndices;
|
|
|
|
DLReader::ExtraVertTracker extraTracker;
|
|
|
|
for (size_t s = 0; s < lastDlSec; ++s) {
|
|
|
|
atUint64 secStart = reader.position();
|
|
|
|
if (s < matSecCount) {
|
|
|
|
if (!s) {
|
|
|
|
MaterialSet matSet;
|
|
|
|
matSet.read(reader);
|
|
|
|
matSet.ensureTexturesExtracted(pakRouter);
|
|
|
|
GetVertexAttributes(matSet, vertAttribs);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
switch (s - matSecCount) {
|
|
|
|
case 0: {
|
|
|
|
/* Positions */
|
|
|
|
if (SurfaceHeader::UseMatrixSkinning() && rp.first)
|
|
|
|
skinIndices.assign(secSizes[s] / 12, -1);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 1: {
|
|
|
|
/* Normals */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 2: {
|
|
|
|
/* Colors */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 3: {
|
|
|
|
/* Float UVs */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 4: {
|
|
|
|
if (surfaceCount) {
|
|
|
|
/* MP3 MREA case */
|
|
|
|
visitedDLOffsets = true;
|
|
|
|
lastDlSec = 4 + surfaceCount;
|
|
|
|
} else {
|
|
|
|
/* Short UVs */
|
|
|
|
if (shortUVs)
|
|
|
|
break;
|
2016-03-02 07:29:19 +00:00
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
/* DL Offsets (here or next section) */
|
|
|
|
visitedDLOffsets = true;
|
|
|
|
lastDlSec = s + reader.readUint32Big() + 1;
|
|
|
|
break;
|
|
|
|
}
|
2019-02-18 05:47:46 +00:00
|
|
|
[[fallthrough]];
|
2018-12-08 05:30:43 +00:00
|
|
|
}
|
|
|
|
default: {
|
|
|
|
if (!visitedDLOffsets) {
|
|
|
|
visitedDLOffsets = true;
|
|
|
|
lastDlSec = s + reader.readUint32Big() + 1;
|
|
|
|
break;
|
2016-03-02 07:29:19 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
/* GX Display List (surface) */
|
|
|
|
SurfaceHeader sHead;
|
|
|
|
sHead.read(reader);
|
|
|
|
const atInt16* bankIn = nullptr;
|
|
|
|
if (SurfaceHeader::UseMatrixSkinning() && rp.first)
|
|
|
|
bankIn = rp.first->getMatrixBank(sHead.skinMatrixBankIdx());
|
|
|
|
|
|
|
|
/* Do max index pre-read */
|
|
|
|
atUint32 realDlSize = secSizes[s] - (reader.position() - secStart);
|
|
|
|
DLReader dl(vertAttribs[sHead.matIdx], reader.readUBytes(realDlSize), realDlSize, extraTracker, bankIn);
|
|
|
|
if (SurfaceHeader::UseMatrixSkinning() && rp.first)
|
|
|
|
dl.preReadMaxIdxs(maxIdxs, skinIndices);
|
|
|
|
else
|
|
|
|
dl.preReadMaxIdxs(maxIdxs);
|
|
|
|
}
|
|
|
|
}
|
2016-03-02 07:29:19 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
if (s < secCount - 1)
|
|
|
|
reader.seek(secStart + secSizes[s], athena::Begin);
|
|
|
|
}
|
|
|
|
|
|
|
|
reader.seek(afterHeaderPos, athena::Begin);
|
|
|
|
|
|
|
|
visitedDLOffsets = false;
|
|
|
|
unsigned createdUVLayers = 0;
|
|
|
|
unsigned surfIdx = 0;
|
|
|
|
|
|
|
|
for (size_t s = 0; s < lastDlSec; ++s) {
|
|
|
|
atUint64 secStart = reader.position();
|
|
|
|
if (s < matSecCount) {
|
|
|
|
MaterialSet matSet;
|
|
|
|
matSet.read(reader);
|
|
|
|
matSet.readToBlender(os, pakRouter, entry, s);
|
|
|
|
if (!s)
|
|
|
|
GetVertexAttributes(matSet, vertAttribs);
|
|
|
|
} else {
|
|
|
|
switch (s - matSecCount) {
|
|
|
|
case 0: {
|
|
|
|
/* Positions */
|
|
|
|
atUint32 vertCount = maxIdxs.pos + 1;
|
|
|
|
std::vector<atVec3f> positions;
|
|
|
|
positions.reserve(vertCount);
|
|
|
|
for (size_t i = 0; i <= maxIdxs.pos; ++i) {
|
|
|
|
positions.push_back(reader.readVec3fBig());
|
|
|
|
const atVec3f& pos = positions.back();
|
2019-07-20 04:27:21 +00:00
|
|
|
os.format(fmt("vert = bm.verts.new(({},{},{}))\n"), pos.simd[0], pos.simd[1], pos.simd[2]);
|
2018-12-08 05:30:43 +00:00
|
|
|
if (rp.first) {
|
|
|
|
if (SurfaceHeader::UseMatrixSkinning() && !skinIndices.empty())
|
|
|
|
rp.first->weightVertex(os, *rp.second, skinIndices[i]);
|
|
|
|
else if (!SurfaceHeader::UseMatrixSkinning())
|
|
|
|
rp.first->weightVertex(os, *rp.second, i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (rp.first && SurfaceHeader::UseMatrixSkinning() && !skinIndices.empty())
|
|
|
|
vertCount += extraTracker.sendAdditionalVertsToBlender(os, rp, 0);
|
2019-07-20 04:27:21 +00:00
|
|
|
os.format(fmt("two_face_vert = {}\n"), vertCount);
|
2018-12-08 05:30:43 +00:00
|
|
|
for (size_t i = 0; i <= maxIdxs.pos; ++i) {
|
|
|
|
const atVec3f& pos = positions[i];
|
2019-07-20 04:27:21 +00:00
|
|
|
os.format(fmt("vert = bm.verts.new(({},{},{}))\n"), pos.simd[0], pos.simd[1], pos.simd[2]);
|
2018-12-08 05:30:43 +00:00
|
|
|
if (rp.first) {
|
|
|
|
if (SurfaceHeader::UseMatrixSkinning() && !skinIndices.empty())
|
|
|
|
rp.first->weightVertex(os, *rp.second, skinIndices[i]);
|
|
|
|
else if (!SurfaceHeader::UseMatrixSkinning())
|
|
|
|
rp.first->weightVertex(os, *rp.second, i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (rp.first && SurfaceHeader::UseMatrixSkinning() && !skinIndices.empty())
|
|
|
|
extraTracker.sendAdditionalVertsToBlender(os, rp, vertCount);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 1: {
|
|
|
|
/* Normals */
|
|
|
|
os << "norm_list = []\n";
|
|
|
|
if (shortNormals) {
|
|
|
|
size_t normCount = secSizes[s] / 6;
|
|
|
|
for (size_t i = 0; i < normCount; ++i) {
|
|
|
|
float x = reader.readInt16Big() / 16384.0f;
|
|
|
|
float y = reader.readInt16Big() / 16384.0f;
|
|
|
|
float z = reader.readInt16Big() / 16384.0f;
|
2019-07-20 04:27:21 +00:00
|
|
|
os.format(fmt("norm_list.append(({},{},{}))\n"), x, y, z);
|
2018-12-08 05:30:43 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
size_t normCount = secSizes[s] / 12;
|
|
|
|
for (size_t i = 0; i < normCount; ++i) {
|
|
|
|
const atVec3f norm = reader.readVec3fBig();
|
2019-07-20 04:27:21 +00:00
|
|
|
os.format(fmt("norm_list.append(({},{},{}))\n"), norm.simd[0], norm.simd[1], norm.simd[2]);
|
2018-12-08 05:30:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 2: {
|
|
|
|
/* Colors */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 3: {
|
|
|
|
/* Float UVs */
|
|
|
|
os << "uv_list = []\n";
|
|
|
|
size_t uvCount = secSizes[s] / 8;
|
|
|
|
for (size_t i = 0; i < uvCount; ++i) {
|
|
|
|
const atVec2f uv = reader.readVec2fBig();
|
2019-07-20 04:27:21 +00:00
|
|
|
os.format(fmt("uv_list.append(({},{}))\n"), uv.simd[0], uv.simd[1]);
|
2018-12-08 05:30:43 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 4: {
|
|
|
|
if (surfaceCount) {
|
|
|
|
/* MP3 MREA case */
|
|
|
|
visitedDLOffsets = true;
|
|
|
|
} else {
|
|
|
|
/* Short UVs */
|
|
|
|
os << "suv_list = []\n";
|
|
|
|
if (shortUVs) {
|
|
|
|
size_t uvCount = secSizes[s] / 4;
|
|
|
|
for (size_t i = 0; i < uvCount; ++i) {
|
|
|
|
float x = reader.readInt16Big() / 32768.0f;
|
|
|
|
float y = reader.readInt16Big() / 32768.0f;
|
2019-07-20 04:27:21 +00:00
|
|
|
os.format(fmt("suv_list.append(({},{}))\n"), x, y);
|
2018-12-08 05:30:43 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2016-03-02 07:29:19 +00:00
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
/* DL Offsets (here or next section) */
|
|
|
|
visitedDLOffsets = true;
|
|
|
|
break;
|
|
|
|
}
|
2019-02-18 05:47:46 +00:00
|
|
|
[[fallthrough]];
|
2018-12-08 05:30:43 +00:00
|
|
|
}
|
|
|
|
default: {
|
|
|
|
if (!visitedDLOffsets) {
|
|
|
|
visitedDLOffsets = true;
|
|
|
|
break;
|
|
|
|
}
|
2016-03-02 07:29:19 +00:00
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
/* GX Display List (surface) */
|
|
|
|
SurfaceHeader sHead;
|
|
|
|
sHead.read(reader);
|
|
|
|
VertexAttributes& curVA = vertAttribs[sHead.matIdx];
|
|
|
|
unsigned matUVCount = curVA.uvCount;
|
|
|
|
bool matShortUVs = curVA.shortUVs;
|
|
|
|
const atInt16* bankIn = nullptr;
|
|
|
|
if (SurfaceHeader::UseMatrixSkinning() && rp.first)
|
|
|
|
bankIn = rp.first->getMatrixBank(sHead.skinMatrixBankIdx());
|
|
|
|
|
2019-07-20 04:27:21 +00:00
|
|
|
os.format(fmt("materials[{}].pass_index = {}\n"), sHead.matIdx, surfIdx++);
|
2018-12-08 05:30:43 +00:00
|
|
|
if (matUVCount > createdUVLayers) {
|
|
|
|
for (unsigned l = createdUVLayers; l < matUVCount; ++l)
|
2019-07-20 04:27:21 +00:00
|
|
|
os.format(fmt("bm.loops.layers.uv.new('UV_{}')\n"), l);
|
2018-12-08 05:30:43 +00:00
|
|
|
createdUVLayers = matUVCount;
|
2016-03-02 07:29:19 +00:00
|
|
|
}
|
2018-12-08 05:30:43 +00:00
|
|
|
|
|
|
|
atUint32 realDlSize = secSizes[s] - (reader.position() - secStart);
|
|
|
|
DLReader dl(vertAttribs[sHead.matIdx], reader.readUBytes(realDlSize), realDlSize, extraTracker, bankIn);
|
|
|
|
|
|
|
|
while (dl) {
|
|
|
|
GX::Primitive ptype = dl.readPrimitive();
|
|
|
|
atUint16 vertCount = dl.readVertCount();
|
|
|
|
|
|
|
|
/* First vert */
|
|
|
|
DLReader::DLPrimVert firstPrimVert = dl.readVert(true);
|
|
|
|
|
|
|
|
/* 3 Prim Verts to start */
|
|
|
|
int c = 0;
|
|
|
|
DLReader::DLPrimVert primVerts[3] = {dl.readVert(), dl.readVert(), dl.readVert()};
|
|
|
|
|
|
|
|
if (ptype == GX::TRIANGLESTRIP) {
|
|
|
|
atUint8 flip = 0;
|
|
|
|
for (int v = 0; v < vertCount - 2; ++v) {
|
|
|
|
if (flip) {
|
2019-07-20 04:27:21 +00:00
|
|
|
os.format(fmt(
|
|
|
|
"last_face, last_mesh = add_triangle(bm, bm.verts, ({},{},{}), norm_list, ({},{},{}), {}, od_list, "
|
|
|
|
"two_face_vert)\n"),
|
2018-12-08 05:30:43 +00:00
|
|
|
primVerts[c % 3].pos, primVerts[(c + 2) % 3].pos, primVerts[(c + 1) % 3].pos, primVerts[c % 3].norm,
|
|
|
|
primVerts[(c + 2) % 3].norm, primVerts[(c + 1) % 3].norm, sHead.matIdx);
|
|
|
|
if (matUVCount) {
|
|
|
|
os << "if last_face is not None:\n";
|
|
|
|
for (unsigned j = 0; j < matUVCount; ++j) {
|
|
|
|
if (j == 0 && matShortUVs)
|
2019-07-20 04:27:21 +00:00
|
|
|
os.format(fmt(
|
|
|
|
" uv_tri = expand_lightmap_triangle(lightmap_tri_tracker, suv_list[{}], suv_list[{}], "
|
|
|
|
"suv_list[{}])\n"
|
|
|
|
" loop_from_facevert(last_mesh, last_face, {})[last_mesh.loops.layers.uv[{}]].uv = "
|
2018-12-08 05:30:43 +00:00
|
|
|
"uv_tri[0]\n"
|
2019-07-20 04:27:21 +00:00
|
|
|
" loop_from_facevert(last_mesh, last_face, {})[last_mesh.loops.layers.uv[{}]].uv = "
|
2018-12-08 05:30:43 +00:00
|
|
|
"uv_tri[1]\n"
|
2019-07-20 04:27:21 +00:00
|
|
|
" loop_from_facevert(last_mesh, last_face, {})[last_mesh.loops.layers.uv[{}]].uv = "
|
|
|
|
"uv_tri[2]\n"),
|
2018-12-08 05:30:43 +00:00
|
|
|
primVerts[c % 3].uvs[j], primVerts[(c + 2) % 3].uvs[j], primVerts[(c + 1) % 3].uvs[j],
|
|
|
|
primVerts[c % 3].pos, j, primVerts[(c + 2) % 3].pos, j, primVerts[(c + 1) % 3].pos, j);
|
|
|
|
else
|
2019-07-20 04:27:21 +00:00
|
|
|
os.format(fmt(
|
|
|
|
" loop_from_facevert(last_mesh, last_face, {})[last_mesh.loops.layers.uv[{}]].uv = "
|
|
|
|
"uv_list[{}]\n"
|
|
|
|
" loop_from_facevert(last_mesh, last_face, {})[last_mesh.loops.layers.uv[{}]].uv = "
|
|
|
|
"uv_list[{}]\n"
|
|
|
|
" loop_from_facevert(last_mesh, last_face, {})[last_mesh.loops.layers.uv[{}]].uv = "
|
|
|
|
"uv_list[{}]\n"),
|
2018-12-08 05:30:43 +00:00
|
|
|
primVerts[c % 3].pos, j, primVerts[c % 3].uvs[j], primVerts[(c + 2) % 3].pos, j,
|
|
|
|
primVerts[(c + 2) % 3].uvs[j], primVerts[(c + 1) % 3].pos, j, primVerts[(c + 1) % 3].uvs[j]);
|
|
|
|
}
|
2016-03-02 07:29:19 +00:00
|
|
|
}
|
2018-12-08 05:30:43 +00:00
|
|
|
} else {
|
2019-07-20 04:27:21 +00:00
|
|
|
os.format(fmt(
|
|
|
|
"last_face, last_mesh = add_triangle(bm, bm.verts, ({},{},{}), norm_list, ({},{},{}), {}, od_list, "
|
|
|
|
"two_face_vert)\n"),
|
2018-12-08 05:30:43 +00:00
|
|
|
primVerts[c % 3].pos, primVerts[(c + 1) % 3].pos, primVerts[(c + 2) % 3].pos, primVerts[c % 3].norm,
|
|
|
|
primVerts[(c + 1) % 3].norm, primVerts[(c + 2) % 3].norm, sHead.matIdx);
|
|
|
|
if (matUVCount) {
|
|
|
|
os << "if last_face is not None:\n";
|
|
|
|
for (unsigned j = 0; j < matUVCount; ++j) {
|
|
|
|
if (j == 0 && matShortUVs)
|
2019-07-20 04:27:21 +00:00
|
|
|
os.format(fmt(
|
|
|
|
" uv_tri = expand_lightmap_triangle(lightmap_tri_tracker, suv_list[{}], suv_list[{}], "
|
|
|
|
"suv_list[{}])\n"
|
|
|
|
" loop_from_facevert(last_mesh, last_face, {})[last_mesh.loops.layers.uv[{}]].uv = "
|
2018-12-08 05:30:43 +00:00
|
|
|
"uv_tri[0]\n"
|
2019-07-20 04:27:21 +00:00
|
|
|
" loop_from_facevert(last_mesh, last_face, {})[last_mesh.loops.layers.uv[{}]].uv = "
|
2018-12-08 05:30:43 +00:00
|
|
|
"uv_tri[1]\n"
|
2019-07-20 04:27:21 +00:00
|
|
|
" loop_from_facevert(last_mesh, last_face, {})[last_mesh.loops.layers.uv[{}]].uv = "
|
|
|
|
"uv_tri[2]\n"),
|
2018-12-08 05:30:43 +00:00
|
|
|
primVerts[c % 3].uvs[j], primVerts[(c + 1) % 3].uvs[j], primVerts[(c + 2) % 3].uvs[j],
|
|
|
|
primVerts[c % 3].pos, j, primVerts[(c + 1) % 3].pos, j, primVerts[(c + 2) % 3].pos, j);
|
|
|
|
else
|
2019-07-20 04:27:21 +00:00
|
|
|
os.format(fmt(
|
|
|
|
" loop_from_facevert(last_mesh, last_face, {})[last_mesh.loops.layers.uv[{}]].uv = "
|
|
|
|
"uv_list[{}]\n"
|
|
|
|
" loop_from_facevert(last_mesh, last_face, {})[last_mesh.loops.layers.uv[{}]].uv = "
|
|
|
|
"uv_list[{}]\n"
|
|
|
|
" loop_from_facevert(last_mesh, last_face, {})[last_mesh.loops.layers.uv[{}]].uv = "
|
|
|
|
"uv_list[{}]\n"),
|
2018-12-08 05:30:43 +00:00
|
|
|
primVerts[c % 3].pos, j, primVerts[c % 3].uvs[j], primVerts[(c + 1) % 3].pos, j,
|
|
|
|
primVerts[(c + 1) % 3].uvs[j], primVerts[(c + 2) % 3].pos, j, primVerts[(c + 2) % 3].uvs[j]);
|
|
|
|
}
|
2016-03-02 07:29:19 +00:00
|
|
|
}
|
2018-12-08 05:30:43 +00:00
|
|
|
}
|
|
|
|
flip ^= 1;
|
|
|
|
|
|
|
|
bool peek = (v >= vertCount - 3);
|
|
|
|
|
|
|
|
/* Advance one prim vert */
|
|
|
|
primVerts[c % 3] = dl.readVert(peek);
|
|
|
|
++c;
|
2016-03-02 07:29:19 +00:00
|
|
|
}
|
2018-12-08 05:30:43 +00:00
|
|
|
} else if (ptype == GX::TRIANGLES) {
|
|
|
|
for (int v = 0; v < vertCount; v += 3) {
|
|
|
|
|
2019-07-20 04:27:21 +00:00
|
|
|
os.format(fmt(
|
|
|
|
"last_face, last_mesh = add_triangle(bm, bm.verts, ({},{},{}), norm_list, ({},{},{}), {}, od_list, "
|
|
|
|
"two_face_vert)\n"),
|
2018-12-08 05:30:43 +00:00
|
|
|
primVerts[0].pos, primVerts[1].pos, primVerts[2].pos, primVerts[0].norm, primVerts[1].norm,
|
|
|
|
primVerts[2].norm, sHead.matIdx);
|
|
|
|
if (matUVCount) {
|
|
|
|
os << "if last_face is not None:\n";
|
|
|
|
for (unsigned j = 0; j < matUVCount; ++j) {
|
|
|
|
if (j == 0 && matShortUVs)
|
2019-07-20 04:27:21 +00:00
|
|
|
os.format(fmt(
|
|
|
|
" uv_tri = expand_lightmap_triangle(lightmap_tri_tracker, suv_list[{}], suv_list[{}], "
|
|
|
|
"suv_list[{}])\n"
|
|
|
|
" loop_from_facevert(last_mesh, last_face, {})[last_mesh.loops.layers.uv[{}]].uv = "
|
2018-12-08 05:30:43 +00:00
|
|
|
"uv_tri[0]\n"
|
2019-07-20 04:27:21 +00:00
|
|
|
" loop_from_facevert(last_mesh, last_face, {})[last_mesh.loops.layers.uv[{}]].uv = "
|
2018-12-08 05:30:43 +00:00
|
|
|
"uv_tri[1]\n"
|
2019-07-20 04:27:21 +00:00
|
|
|
" loop_from_facevert(last_mesh, last_face, {})[last_mesh.loops.layers.uv[{}]].uv = "
|
|
|
|
"uv_tri[2]\n"),
|
2018-12-08 05:30:43 +00:00
|
|
|
primVerts[0].uvs[j], primVerts[1].uvs[j], primVerts[2].uvs[j], primVerts[0].pos, j,
|
|
|
|
primVerts[1].pos, j, primVerts[2].pos, j);
|
|
|
|
else
|
2019-07-20 04:27:21 +00:00
|
|
|
os.format(fmt(
|
|
|
|
" loop_from_facevert(last_mesh, last_face, {})[last_mesh.loops.layers.uv[{}]].uv = "
|
|
|
|
"uv_list[{}]\n"
|
|
|
|
" loop_from_facevert(last_mesh, last_face, {})[last_mesh.loops.layers.uv[{}]].uv = "
|
|
|
|
"uv_list[{}]\n"
|
|
|
|
" loop_from_facevert(last_mesh, last_face, {})[last_mesh.loops.layers.uv[{}]].uv = "
|
|
|
|
"uv_list[{}]\n"),
|
2018-12-08 05:30:43 +00:00
|
|
|
primVerts[0].pos, j, primVerts[0].uvs[j], primVerts[1].pos, j, primVerts[1].uvs[j],
|
|
|
|
primVerts[2].pos, j, primVerts[2].uvs[j]);
|
2016-03-02 07:29:19 +00:00
|
|
|
}
|
2018-12-08 05:30:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Break if done */
|
|
|
|
if (v + 3 >= vertCount)
|
2016-03-02 07:29:19 +00:00
|
|
|
break;
|
2018-12-08 05:30:43 +00:00
|
|
|
|
|
|
|
/* Advance 3 Prim Verts */
|
|
|
|
for (int pv = 0; pv < 3; ++pv)
|
|
|
|
primVerts[pv] = dl.readVert();
|
2016-03-02 07:29:19 +00:00
|
|
|
}
|
2018-12-08 05:30:43 +00:00
|
|
|
} else if (ptype == GX::TRIANGLEFAN) {
|
|
|
|
++c;
|
|
|
|
for (int v = 0; v < vertCount - 2; ++v) {
|
2019-07-20 04:27:21 +00:00
|
|
|
os.format(fmt(
|
|
|
|
"last_face, last_mesh = add_triangle(bm, bm.verts, ({},{},{}), norm_list, ({},{},{}), {}, od_list, "
|
|
|
|
"two_face_vert)\n"),
|
2018-12-08 05:30:43 +00:00
|
|
|
firstPrimVert.pos, primVerts[c % 3].pos, primVerts[(c + 1) % 3].pos, firstPrimVert.norm,
|
|
|
|
primVerts[c % 3].norm, primVerts[(c + 1) % 3].norm, sHead.matIdx);
|
|
|
|
if (matUVCount) {
|
|
|
|
os << "if last_face is not None:\n";
|
|
|
|
for (unsigned j = 0; j < matUVCount; ++j) {
|
|
|
|
if (j == 0 && matShortUVs)
|
2019-07-20 04:27:21 +00:00
|
|
|
os.format(fmt(
|
|
|
|
" uv_tri = expand_lightmap_triangle(lightmap_tri_tracker, suv_list[{}], suv_list[{}], "
|
|
|
|
"suv_list[{}])\n"
|
|
|
|
" loop_from_facevert(last_mesh, last_face, {})[last_mesh.loops.layers.uv[{}]].uv = "
|
2018-12-08 05:30:43 +00:00
|
|
|
"uv_tri[0]\n"
|
2019-07-20 04:27:21 +00:00
|
|
|
" loop_from_facevert(last_mesh, last_face, {})[last_mesh.loops.layers.uv[{}]].uv = "
|
2018-12-08 05:30:43 +00:00
|
|
|
"uv_tri[1]\n"
|
2019-07-20 04:27:21 +00:00
|
|
|
" loop_from_facevert(last_mesh, last_face, {})[last_mesh.loops.layers.uv[{}]].uv = "
|
|
|
|
"uv_tri[2]\n"),
|
2018-12-08 05:30:43 +00:00
|
|
|
firstPrimVert.uvs[j], primVerts[c % 3].uvs[j], primVerts[(c + 1) % 3].uvs[j], firstPrimVert.pos,
|
|
|
|
j, primVerts[c % 3].pos, j, primVerts[(c + 1) % 3].pos, j);
|
|
|
|
else
|
2019-07-20 04:27:21 +00:00
|
|
|
os.format(fmt(
|
|
|
|
" loop_from_facevert(last_mesh, last_face, {})[last_mesh.loops.layers.uv[{}]].uv = "
|
|
|
|
"uv_list[{}]\n"
|
|
|
|
" loop_from_facevert(last_mesh, last_face, {})[last_mesh.loops.layers.uv[{}]].uv = "
|
|
|
|
"uv_list[{}]\n"
|
|
|
|
" loop_from_facevert(last_mesh, last_face, {})[last_mesh.loops.layers.uv[{}]].uv = "
|
|
|
|
"uv_list[{}]\n"),
|
2018-12-08 05:30:43 +00:00
|
|
|
firstPrimVert.pos, j, firstPrimVert.uvs[j], primVerts[c % 3].pos, j, primVerts[c % 3].uvs[j],
|
|
|
|
primVerts[(c + 1) % 3].pos, j, primVerts[(c + 1) % 3].uvs[j]);
|
2016-03-02 07:29:19 +00:00
|
|
|
}
|
2018-12-08 05:30:43 +00:00
|
|
|
}
|
2016-03-02 07:29:19 +00:00
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
/* Break if done */
|
|
|
|
if (v + 3 >= vertCount)
|
|
|
|
break;
|
2016-03-02 07:29:19 +00:00
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
/* Advance one prim vert */
|
|
|
|
primVerts[(c + 2) % 3] = dl.readVert();
|
|
|
|
++c;
|
2016-03-02 07:29:19 +00:00
|
|
|
}
|
2018-12-08 05:30:43 +00:00
|
|
|
}
|
|
|
|
os << "\n";
|
2016-03-02 07:29:19 +00:00
|
|
|
}
|
2018-12-08 05:30:43 +00:00
|
|
|
}
|
|
|
|
}
|
2016-03-02 07:29:19 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
if (s < secCount - 1)
|
|
|
|
reader.seek(secStart + secSizes[s], athena::Begin);
|
|
|
|
}
|
2016-03-02 07:29:19 +00:00
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
/* Finish Mesh */
|
|
|
|
FinishBlenderMesh(os, matSetCount, meshIdx);
|
2016-03-02 07:29:19 +00:00
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
if (rp.first)
|
|
|
|
rp.second->sendVertexGroupsToBlender(os);
|
|
|
|
|
|
|
|
return lastDlSec;
|
2016-03-02 07:29:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
template <class PAKRouter, class MaterialSet, class RigPair, class SurfaceHeader, atUint32 Version>
|
2018-12-08 05:30:43 +00:00
|
|
|
bool ReadCMDLToBlender(hecl::blender::Connection& conn, athena::io::IStreamReader& reader, PAKRouter& pakRouter,
|
|
|
|
const typename PAKRouter::EntryType& entry, const SpecBase& dataspec, const RigPair& rp) {
|
|
|
|
Header head;
|
|
|
|
head.read(reader);
|
|
|
|
|
|
|
|
if (head.magic != 0xDEADBABE) {
|
2019-07-20 04:27:21 +00:00
|
|
|
LogDNACommon.report(logvisor::Error, fmt("invalid CMDL magic"));
|
2018-12-08 05:30:43 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (head.version != Version) {
|
2019-07-20 04:27:21 +00:00
|
|
|
LogDNACommon.report(logvisor::Error, fmt("invalid CMDL version"));
|
2018-12-08 05:30:43 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Open Py Stream and read sections */
|
|
|
|
hecl::blender::PyOutStream os = conn.beginPythonOut(true);
|
2019-07-20 04:27:21 +00:00
|
|
|
os.format(fmt(
|
2018-12-08 05:30:43 +00:00
|
|
|
"import bpy\n"
|
|
|
|
"import bmesh\n"
|
|
|
|
"\n"
|
2019-07-20 04:27:21 +00:00
|
|
|
"bpy.context.scene.name = '{}'\n"
|
|
|
|
"bpy.context.scene.hecl_mesh_obj = bpy.context.scene.name\n"),
|
|
|
|
pakRouter.getBestEntryName(entry));
|
2019-05-08 03:50:21 +00:00
|
|
|
InitGeomBlenderContext(os, dataspec.getMasterShaderPath());
|
2018-12-08 05:30:43 +00:00
|
|
|
MaterialSet::RegisterMaterialProps(os);
|
|
|
|
|
|
|
|
os << "# Materials\n"
|
|
|
|
"materials = []\n"
|
|
|
|
"\n";
|
|
|
|
|
|
|
|
std::vector<VertexAttributes> vertAttribs;
|
|
|
|
ReadGeomSectionsToBlender<PAKRouter, MaterialSet, RigPair, SurfaceHeader>(
|
|
|
|
os, reader, pakRouter, entry, rp, head.flags.shortNormals(), head.flags.shortUVs(), vertAttribs, -1,
|
|
|
|
head.secCount, head.matSetCount, head.secSizes.data());
|
|
|
|
|
|
|
|
return true;
|
2016-03-02 07:29:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
template bool ReadCMDLToBlender<PAKRouter<DNAMP1::PAKBridge>, DNAMP1::MaterialSet,
|
2018-12-08 05:30:43 +00:00
|
|
|
std::pair<DNAMP1::CSKR*, DNAMP1::CINF*>, DNACMDL::SurfaceHeader_1, 2>(
|
|
|
|
hecl::blender::Connection& conn, athena::io::IStreamReader& reader, PAKRouter<DNAMP1::PAKBridge>& pakRouter,
|
|
|
|
const PAKRouter<DNAMP1::PAKBridge>::EntryType& entry, const SpecBase& dataspec,
|
|
|
|
const std::pair<DNAMP1::CSKR*, DNAMP1::CINF*>& rp);
|
2016-03-02 07:29:19 +00:00
|
|
|
|
|
|
|
template bool ReadCMDLToBlender<PAKRouter<DNAMP2::PAKBridge>, DNAMP2::MaterialSet,
|
2018-12-08 05:30:43 +00:00
|
|
|
std::pair<DNAMP2::CSKR*, DNAMP2::CINF*>, DNACMDL::SurfaceHeader_2, 4>(
|
|
|
|
hecl::blender::Connection& conn, athena::io::IStreamReader& reader, PAKRouter<DNAMP2::PAKBridge>& pakRouter,
|
|
|
|
const PAKRouter<DNAMP2::PAKBridge>::EntryType& entry, const SpecBase& dataspec,
|
|
|
|
const std::pair<DNAMP2::CSKR*, DNAMP2::CINF*>& rp);
|
2016-03-02 07:29:19 +00:00
|
|
|
|
|
|
|
template bool ReadCMDLToBlender<PAKRouter<DNAMP3::PAKBridge>, DNAMP3::MaterialSet,
|
2018-12-08 05:30:43 +00:00
|
|
|
std::pair<DNAMP3::CSKR*, DNAMP3::CINF*>, DNACMDL::SurfaceHeader_3, 4>(
|
|
|
|
hecl::blender::Connection& conn, athena::io::IStreamReader& reader, PAKRouter<DNAMP3::PAKBridge>& pakRouter,
|
|
|
|
const PAKRouter<DNAMP3::PAKBridge>::EntryType& entry, const SpecBase& dataspec,
|
|
|
|
const std::pair<DNAMP3::CSKR*, DNAMP3::CINF*>& rp);
|
2016-03-02 07:29:19 +00:00
|
|
|
|
|
|
|
template bool ReadCMDLToBlender<PAKRouter<DNAMP3::PAKBridge>, DNAMP3::MaterialSet,
|
2018-12-08 05:30:43 +00:00
|
|
|
std::pair<DNAMP3::CSKR*, DNAMP3::CINF*>, DNACMDL::SurfaceHeader_3, 5>(
|
|
|
|
hecl::blender::Connection& conn, athena::io::IStreamReader& reader, PAKRouter<DNAMP3::PAKBridge>& pakRouter,
|
|
|
|
const PAKRouter<DNAMP3::PAKBridge>::EntryType& entry, const SpecBase& dataspec,
|
|
|
|
const std::pair<DNAMP3::CSKR*, DNAMP3::CINF*>& rp);
|
2016-03-02 07:29:19 +00:00
|
|
|
|
|
|
|
template <class PAKRouter, class MaterialSet>
|
2018-12-08 05:30:43 +00:00
|
|
|
void NameCMDL(athena::io::IStreamReader& reader, PAKRouter& pakRouter, typename PAKRouter::EntryType& entry,
|
|
|
|
const SpecBase& dataspec) {
|
|
|
|
Header head;
|
|
|
|
head.read(reader);
|
2019-07-20 04:27:21 +00:00
|
|
|
std::string bestName = fmt::format(fmt("CMDL_{}"), entry.id);
|
2018-12-08 05:30:43 +00:00
|
|
|
|
|
|
|
/* Pre-read pass to determine maximum used vert indices */
|
|
|
|
atUint32 matSecCount = 0;
|
|
|
|
if (head.matSetCount)
|
|
|
|
matSecCount = MaterialSet::OneSection() ? 1 : head.matSetCount;
|
|
|
|
atUint32 lastDlSec = head.secCount;
|
|
|
|
for (size_t s = 0; s < lastDlSec; ++s) {
|
|
|
|
atUint64 secStart = reader.position();
|
|
|
|
if (s < matSecCount) {
|
|
|
|
MaterialSet matSet;
|
|
|
|
matSet.read(reader);
|
|
|
|
matSet.nameTextures(pakRouter, bestName.c_str(), s);
|
2016-03-02 07:29:19 +00:00
|
|
|
}
|
2018-12-08 05:30:43 +00:00
|
|
|
|
|
|
|
if (s < head.secCount - 1)
|
|
|
|
reader.seek(secStart + head.secSizes[s], athena::Begin);
|
|
|
|
}
|
2016-03-02 07:29:19 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
template void NameCMDL<PAKRouter<DNAMP1::PAKBridge>, DNAMP1::MaterialSet>(
|
|
|
|
athena::io::IStreamReader& reader, PAKRouter<DNAMP1::PAKBridge>& pakRouter,
|
|
|
|
PAKRouter<DNAMP1::PAKBridge>::EntryType& entry, const SpecBase& dataspec);
|
2016-03-02 07:29:19 +00:00
|
|
|
|
2018-03-28 08:09:41 +00:00
|
|
|
template <typename W>
|
2018-12-08 05:30:43 +00:00
|
|
|
static void WriteDLVal(W& writer, GX::AttrType type, atUint32 val) {
|
|
|
|
switch (type) {
|
|
|
|
case GX::DIRECT:
|
|
|
|
case GX::INDEX8:
|
|
|
|
writer.writeUByte(atUint8(val));
|
|
|
|
break;
|
|
|
|
case GX::INDEX16:
|
|
|
|
writer.writeUint16Big(atUint16(val));
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
2016-03-02 07:29:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
template <class MaterialSet, class SurfaceHeader, atUint32 Version>
|
2018-12-08 05:30:43 +00:00
|
|
|
bool WriteCMDL(const hecl::ProjectPath& outPath, const hecl::ProjectPath& inPath, const Mesh& mesh) {
|
|
|
|
bool skinned = !mesh.skins.empty();
|
|
|
|
|
|
|
|
Header head;
|
|
|
|
head.magic = 0xDEADBABE;
|
|
|
|
head.version = Version;
|
|
|
|
head.flags.setSkinned(skinned);
|
|
|
|
head.flags.setShortNormals(!skinned);
|
|
|
|
head.flags.setShortUVs(true); /* This just means there's an (empty) short UV section */
|
|
|
|
head.aabbMin = mesh.aabbMin.val;
|
|
|
|
head.aabbMax = mesh.aabbMax.val;
|
|
|
|
head.matSetCount = mesh.materialSets.size();
|
|
|
|
head.secCount = head.matSetCount + 6 + mesh.surfaces.size();
|
|
|
|
head.secSizes.reserve(head.secCount);
|
|
|
|
|
|
|
|
/* Lengths of padding to insert while writing */
|
|
|
|
std::vector<size_t> paddingSizes;
|
|
|
|
paddingSizes.reserve(head.secCount);
|
|
|
|
|
|
|
|
/* Build material sets */
|
|
|
|
std::vector<MaterialSet> matSets;
|
2019-05-08 03:50:21 +00:00
|
|
|
#if 0
|
2018-12-08 05:30:43 +00:00
|
|
|
matSets.reserve(mesh.materialSets.size());
|
|
|
|
{
|
|
|
|
for (const std::vector<Material>& mset : mesh.materialSets) {
|
|
|
|
matSets.emplace_back();
|
|
|
|
MaterialSet& targetMSet = matSets.back();
|
|
|
|
std::vector<hecl::ProjectPath> texPaths;
|
|
|
|
std::vector<hecl::Backend::GX> setBackends;
|
|
|
|
setBackends.reserve(mset.size());
|
|
|
|
|
|
|
|
size_t endOff = 0;
|
|
|
|
for (const Material& mat : mset) {
|
2019-07-20 04:27:21 +00:00
|
|
|
std::string diagName = fmt::format(fmt("{}:{}"), inPath.getLastComponentUTF8(), mat.name);
|
2018-12-08 05:30:43 +00:00
|
|
|
hecl::Frontend::IR matIR = FE.compileSource(mat.source, diagName);
|
|
|
|
setBackends.emplace_back();
|
|
|
|
hecl::Backend::GX& matGX = setBackends.back();
|
|
|
|
matGX.reset(matIR, FE.getDiagnostics());
|
|
|
|
|
|
|
|
targetMSet.materials.emplace_back(matGX, mat.iprops, mat.texs, texPaths, mesh.colorLayerCount, false, false);
|
|
|
|
|
|
|
|
targetMSet.materials.back().binarySize(endOff);
|
|
|
|
targetMSet.head.addMaterialEndOff(endOff);
|
|
|
|
}
|
|
|
|
|
|
|
|
texPaths.reserve(mset.size() * 4);
|
|
|
|
for (const Material& mat : mset) {
|
|
|
|
for (const hecl::ProjectPath& path : mat.texs) {
|
|
|
|
bool found = false;
|
|
|
|
for (const hecl::ProjectPath& ePath : texPaths) {
|
|
|
|
if (path == ePath) {
|
|
|
|
found = true;
|
|
|
|
break;
|
2016-03-02 07:29:19 +00:00
|
|
|
}
|
2018-12-08 05:30:43 +00:00
|
|
|
}
|
|
|
|
if (!found)
|
|
|
|
texPaths.push_back(path);
|
|
|
|
}
|
|
|
|
}
|
2016-03-02 07:29:19 +00:00
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
for (const hecl::ProjectPath& path : texPaths)
|
|
|
|
targetMSet.head.addTexture(path);
|
2016-07-31 01:43:34 +00:00
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
size_t secSz = 0;
|
|
|
|
targetMSet.binarySize(secSz);
|
|
|
|
size_t secSz32 = ROUND_UP_32(secSz);
|
|
|
|
head.secSizes.push_back(secSz32);
|
|
|
|
paddingSizes.push_back(secSz32 - secSz);
|
|
|
|
}
|
|
|
|
}
|
2019-05-08 03:50:21 +00:00
|
|
|
#endif
|
2018-12-08 05:30:43 +00:00
|
|
|
|
|
|
|
/* Vertex Positions */
|
|
|
|
size_t secSz = mesh.pos.size() * 12;
|
|
|
|
size_t secSz32 = ROUND_UP_32(secSz);
|
|
|
|
if (secSz32 == 0)
|
|
|
|
secSz32 = 32;
|
|
|
|
head.secSizes.push_back(secSz32);
|
|
|
|
paddingSizes.push_back(secSz32 - secSz);
|
|
|
|
|
|
|
|
/* Vertex Normals */
|
|
|
|
secSz = mesh.norm.size() * (skinned ? 12 : 6);
|
|
|
|
secSz32 = ROUND_UP_32(secSz);
|
|
|
|
if (secSz32 == 0)
|
|
|
|
secSz32 = 32;
|
|
|
|
head.secSizes.push_back(secSz32);
|
|
|
|
paddingSizes.push_back(secSz32 - secSz);
|
|
|
|
|
|
|
|
/* Vertex Colors */
|
|
|
|
secSz = mesh.color.size() * 4;
|
|
|
|
secSz32 = ROUND_UP_32(secSz);
|
|
|
|
if (secSz32 == 0)
|
|
|
|
secSz32 = 32;
|
|
|
|
head.secSizes.push_back(secSz32);
|
|
|
|
paddingSizes.push_back(secSz32 - secSz);
|
|
|
|
|
|
|
|
/* UV coords */
|
|
|
|
secSz = mesh.uv.size() * 8;
|
|
|
|
secSz32 = ROUND_UP_32(secSz);
|
|
|
|
if (secSz32 == 0)
|
|
|
|
secSz32 = 32;
|
|
|
|
head.secSizes.push_back(secSz32);
|
|
|
|
paddingSizes.push_back(secSz32 - secSz);
|
|
|
|
|
|
|
|
/* LUV coords */
|
|
|
|
secSz = 0;
|
|
|
|
secSz32 = ROUND_UP_32(secSz);
|
|
|
|
head.secSizes.push_back(secSz32);
|
|
|
|
paddingSizes.push_back(secSz32 - secSz);
|
|
|
|
|
|
|
|
/* Surface index */
|
|
|
|
std::vector<size_t> surfEndOffs;
|
|
|
|
surfEndOffs.reserve(mesh.surfaces.size());
|
|
|
|
secSz = mesh.surfaces.size() * 4 + 4;
|
|
|
|
secSz32 = ROUND_UP_32(secSz);
|
|
|
|
if (secSz32 == 0)
|
|
|
|
secSz32 = 32;
|
|
|
|
head.secSizes.push_back(secSz32);
|
|
|
|
paddingSizes.push_back(secSz32 - secSz);
|
|
|
|
|
|
|
|
/* Surfaces */
|
|
|
|
size_t endOff = 0;
|
|
|
|
size_t firstSurfSec = head.secSizes.size();
|
|
|
|
for (const Mesh::Surface& surf : mesh.surfaces) {
|
|
|
|
size_t vertSz = matSets.at(0).materials.at(surf.materialIdx).getVAFlags().vertDLSize();
|
|
|
|
if (surf.verts.size() > 65536)
|
2019-07-20 04:27:21 +00:00
|
|
|
LogDNACommon.report(logvisor::Fatal, fmt("GX DisplayList overflow"));
|
2018-12-08 05:30:43 +00:00
|
|
|
size_t secSz = 64;
|
|
|
|
for (auto it = surf.verts.cbegin(); it != surf.verts.cend();) {
|
|
|
|
atUint16 vertCount = 0;
|
|
|
|
auto itEnd = surf.verts.cend();
|
|
|
|
for (auto it2 = it; it2 != surf.verts.cend(); ++it2, ++vertCount)
|
|
|
|
if (it2->iPos == 0xffffffff) {
|
|
|
|
if (vertCount == 3) {
|
|
|
|
/* All primitives here on out are triangles */
|
|
|
|
vertCount = atUint16((surf.verts.cend() - it + 1) * 3 / 4);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
itEnd = it2;
|
|
|
|
break;
|
2016-03-02 07:29:19 +00:00
|
|
|
}
|
2018-12-08 05:30:43 +00:00
|
|
|
secSz += 3 + vertCount * vertSz;
|
|
|
|
if (itEnd == surf.verts.cend())
|
|
|
|
break;
|
|
|
|
it = itEnd + 1;
|
2016-03-02 07:29:19 +00:00
|
|
|
}
|
|
|
|
secSz32 = ROUND_UP_32(secSz);
|
|
|
|
if (secSz32 == 0)
|
2018-12-08 05:30:43 +00:00
|
|
|
secSz32 = 32;
|
2016-03-02 07:29:19 +00:00
|
|
|
head.secSizes.push_back(secSz32);
|
|
|
|
paddingSizes.push_back(secSz32 - secSz);
|
2018-12-08 05:30:43 +00:00
|
|
|
endOff += secSz32;
|
|
|
|
surfEndOffs.push_back(endOff);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Write sections */
|
|
|
|
athena::io::FileWriter writer(outPath.getAbsolutePath());
|
|
|
|
head.write(writer);
|
|
|
|
std::vector<size_t>::const_iterator padIt = paddingSizes.cbegin();
|
|
|
|
|
|
|
|
/* Material Sets */
|
|
|
|
for (const MaterialSet& mset : matSets) {
|
|
|
|
mset.write(writer);
|
|
|
|
writer.fill(atUint8(0), *padIt);
|
|
|
|
++padIt;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Vertex Positions */
|
|
|
|
for (const atVec3f& pos : mesh.pos)
|
|
|
|
writer.writeVec3fBig(pos);
|
|
|
|
writer.fill(atUint8(0), *padIt);
|
|
|
|
++padIt;
|
|
|
|
|
|
|
|
/* Vertex Normals */
|
|
|
|
for (const atVec3f& norm : mesh.norm) {
|
|
|
|
if (skinned) {
|
|
|
|
writer.writeVec3fBig(norm);
|
|
|
|
} else {
|
|
|
|
for (int i = 0; i < 3; ++i) {
|
|
|
|
int tmpV = int(norm.simd[i] * 16384.f);
|
|
|
|
tmpV = zeus::clamp(-32768, tmpV, 32767);
|
|
|
|
writer.writeInt16Big(atInt16(tmpV));
|
|
|
|
}
|
2016-03-02 07:29:19 +00:00
|
|
|
}
|
2018-12-08 05:30:43 +00:00
|
|
|
}
|
|
|
|
writer.fill(atUint8(0), *padIt);
|
|
|
|
++padIt;
|
|
|
|
|
|
|
|
/* Vertex Colors */
|
|
|
|
for (const atVec3f& col : mesh.color) {
|
|
|
|
GX::Color qCol(col);
|
|
|
|
qCol.write(writer);
|
|
|
|
}
|
|
|
|
writer.fill(atUint8(0), *padIt);
|
|
|
|
++padIt;
|
|
|
|
|
|
|
|
/* UV coords */
|
|
|
|
for (const atVec2f& uv : mesh.uv)
|
|
|
|
writer.writeVec2fBig(uv);
|
|
|
|
writer.fill(atUint8(0), *padIt);
|
|
|
|
++padIt;
|
|
|
|
|
|
|
|
/* LUV coords */
|
|
|
|
writer.fill(atUint8(0), *padIt);
|
|
|
|
++padIt;
|
|
|
|
|
|
|
|
/* Surface index */
|
|
|
|
writer.writeUint32Big(surfEndOffs.size());
|
|
|
|
for (size_t off : surfEndOffs)
|
|
|
|
writer.writeUint32Big(off);
|
|
|
|
writer.fill(atUint8(0), *padIt);
|
|
|
|
++padIt;
|
|
|
|
|
|
|
|
/* Surfaces */
|
|
|
|
GX::Primitive prim = GX::TRIANGLES;
|
|
|
|
if (mesh.topology == hecl::HMDLTopology::Triangles)
|
|
|
|
prim = GX::TRIANGLES;
|
|
|
|
else if (mesh.topology == hecl::HMDLTopology::TriStrips)
|
|
|
|
prim = GX::TRIANGLESTRIP;
|
|
|
|
else
|
2019-07-20 04:27:21 +00:00
|
|
|
LogDNACommon.report(logvisor::Fatal, fmt("unrecognized mesh output mode"));
|
2018-12-08 05:30:43 +00:00
|
|
|
auto surfSizeIt = head.secSizes.begin() + firstSurfSec;
|
|
|
|
for (const Mesh::Surface& surf : mesh.surfaces) {
|
|
|
|
const typename MaterialSet::Material::VAFlags& vaFlags = matSets.at(0).materials.at(surf.materialIdx).getVAFlags();
|
|
|
|
|
|
|
|
SurfaceHeader header;
|
|
|
|
header.centroid = surf.centroid;
|
|
|
|
header.matIdx = surf.materialIdx;
|
|
|
|
header.dlSize = (*surfSizeIt++ - 64) | 0x80000000;
|
|
|
|
header.reflectionNormal = surf.reflectionNormal;
|
|
|
|
header.write(writer);
|
|
|
|
|
|
|
|
GX::Primitive usePrim = prim;
|
|
|
|
for (auto it = surf.verts.cbegin(); it != surf.verts.cend();) {
|
|
|
|
atUint16 vertCount = 0;
|
|
|
|
auto itEnd = surf.verts.cend();
|
|
|
|
for (auto it2 = it; it2 != surf.verts.cend(); ++it2, ++vertCount)
|
|
|
|
if (it2->iPos == 0xffffffff) {
|
|
|
|
if (vertCount == 3) {
|
|
|
|
/* All primitives here on out are triangles */
|
|
|
|
usePrim = GX::TRIANGLES;
|
|
|
|
vertCount = atUint16((surf.verts.cend() - it + 1) * 3 / 4);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
itEnd = it2;
|
|
|
|
break;
|
|
|
|
}
|
2016-03-02 07:29:19 +00:00
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
/* VAT0 = float normals, float UVs
|
|
|
|
* VAT1 = short normals, float UVs */
|
|
|
|
writer.writeUByte(usePrim | (skinned ? 0x0 : 0x1));
|
|
|
|
writer.writeUint16Big(vertCount);
|
|
|
|
|
|
|
|
for (auto it2 = it; it2 != itEnd; ++it2) {
|
|
|
|
const Mesh::Surface::Vert& vert = *it2;
|
|
|
|
if (vert.iPos == 0xffffffff)
|
|
|
|
continue;
|
|
|
|
atUint32 skinIdx = vert.iBankSkin * 3;
|
|
|
|
WriteDLVal(writer, vaFlags.pnMatIdx(), skinIdx);
|
|
|
|
WriteDLVal(writer, vaFlags.tex0MatIdx(), skinIdx);
|
|
|
|
WriteDLVal(writer, vaFlags.tex1MatIdx(), skinIdx);
|
|
|
|
WriteDLVal(writer, vaFlags.tex2MatIdx(), skinIdx);
|
|
|
|
WriteDLVal(writer, vaFlags.tex3MatIdx(), skinIdx);
|
|
|
|
WriteDLVal(writer, vaFlags.tex4MatIdx(), skinIdx);
|
|
|
|
WriteDLVal(writer, vaFlags.tex5MatIdx(), skinIdx);
|
|
|
|
WriteDLVal(writer, vaFlags.tex6MatIdx(), skinIdx);
|
|
|
|
WriteDLVal(writer, vaFlags.position(), vert.iPos);
|
|
|
|
WriteDLVal(writer, vaFlags.normal(), vert.iNorm);
|
|
|
|
WriteDLVal(writer, vaFlags.color0(), vert.iColor[0]);
|
|
|
|
WriteDLVal(writer, vaFlags.color1(), vert.iColor[1]);
|
|
|
|
WriteDLVal(writer, vaFlags.tex0(), vert.iUv[0]);
|
|
|
|
WriteDLVal(writer, vaFlags.tex1(), vert.iUv[1]);
|
|
|
|
WriteDLVal(writer, vaFlags.tex2(), vert.iUv[2]);
|
|
|
|
WriteDLVal(writer, vaFlags.tex3(), vert.iUv[3]);
|
|
|
|
WriteDLVal(writer, vaFlags.tex4(), vert.iUv[4]);
|
|
|
|
WriteDLVal(writer, vaFlags.tex5(), vert.iUv[5]);
|
|
|
|
WriteDLVal(writer, vaFlags.tex6(), vert.iUv[6]);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (itEnd == surf.verts.cend())
|
|
|
|
break;
|
|
|
|
it = itEnd + 1;
|
2016-03-02 07:29:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
writer.fill(atUint8(0), *padIt);
|
|
|
|
++padIt;
|
2018-12-08 05:30:43 +00:00
|
|
|
}
|
2016-03-02 07:29:19 +00:00
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
writer.close();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
template bool WriteCMDL<DNAMP1::MaterialSet, DNACMDL::SurfaceHeader_1, 2>(const hecl::ProjectPath& outPath,
|
|
|
|
const hecl::ProjectPath& inPath,
|
|
|
|
const Mesh& mesh);
|
|
|
|
|
|
|
|
template <class MaterialSet, class SurfaceHeader, atUint32 Version>
|
|
|
|
bool WriteHMDLCMDL(const hecl::ProjectPath& outPath, const hecl::ProjectPath& inPath, const Mesh& mesh,
|
|
|
|
hecl::blender::PoolSkinIndex& poolSkinIndex) {
|
|
|
|
Header head;
|
|
|
|
head.magic = 0xDEADBABE;
|
|
|
|
head.version = 0x10000 | Version;
|
|
|
|
head.aabbMin = mesh.aabbMin.val;
|
|
|
|
head.aabbMax = mesh.aabbMax.val;
|
|
|
|
head.matSetCount = mesh.materialSets.size();
|
|
|
|
head.secCount = head.matSetCount + 4 + mesh.surfaces.size();
|
|
|
|
head.secSizes.reserve(head.secCount);
|
|
|
|
|
|
|
|
/* Lengths of padding to insert while writing */
|
|
|
|
std::vector<size_t> paddingSizes;
|
|
|
|
paddingSizes.reserve(head.secCount);
|
|
|
|
|
|
|
|
/* Build material sets */
|
|
|
|
std::vector<MaterialSet> matSets;
|
|
|
|
matSets.reserve(mesh.materialSets.size());
|
|
|
|
|
2019-05-08 03:50:21 +00:00
|
|
|
for (const std::vector<Material>& mset : mesh.materialSets) {
|
|
|
|
matSets.emplace_back();
|
|
|
|
MaterialSet& targetMSet = matSets.back();
|
2018-12-08 05:30:43 +00:00
|
|
|
|
2019-05-08 03:50:21 +00:00
|
|
|
size_t endOff = 0;
|
|
|
|
for (const Material& mat : mset) {
|
|
|
|
++targetMSet.materialCount;
|
|
|
|
targetMSet.materials.emplace_back(mat);
|
|
|
|
targetMSet.materials.back().binarySize(endOff);
|
|
|
|
targetMSet.materialEndOffs.push_back(endOff);
|
2018-04-02 04:27:24 +00:00
|
|
|
}
|
2019-05-08 03:50:21 +00:00
|
|
|
|
|
|
|
size_t secSz = 0;
|
|
|
|
targetMSet.binarySize(secSz);
|
|
|
|
size_t secSz32 = ROUND_UP_32(secSz);
|
|
|
|
head.secSizes.push_back(secSz32);
|
|
|
|
paddingSizes.push_back(secSz32 - secSz);
|
2018-12-08 05:30:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
hecl::blender::HMDLBuffers bufs = mesh.getHMDLBuffers(false, poolSkinIndex);
|
|
|
|
|
|
|
|
/* Metadata */
|
|
|
|
size_t secSz = 0;
|
|
|
|
bufs.m_meta.binarySize(secSz);
|
|
|
|
size_t secSz32 = ROUND_UP_32(secSz);
|
|
|
|
if (secSz32 == 0)
|
|
|
|
secSz32 = 32;
|
|
|
|
head.secSizes.push_back(secSz32);
|
|
|
|
paddingSizes.push_back(secSz32 - secSz);
|
|
|
|
|
|
|
|
/* VBO */
|
|
|
|
secSz = bufs.m_vboSz;
|
|
|
|
secSz32 = ROUND_UP_32(secSz);
|
|
|
|
if (secSz32 == 0)
|
|
|
|
secSz32 = 32;
|
|
|
|
head.secSizes.push_back(secSz32);
|
|
|
|
paddingSizes.push_back(secSz32 - secSz);
|
|
|
|
|
|
|
|
/* IBO */
|
|
|
|
secSz = bufs.m_iboSz;
|
|
|
|
secSz32 = ROUND_UP_32(secSz);
|
|
|
|
if (secSz32 == 0)
|
|
|
|
secSz32 = 32;
|
|
|
|
head.secSizes.push_back(secSz32);
|
|
|
|
paddingSizes.push_back(secSz32 - secSz);
|
|
|
|
|
|
|
|
/* Surface index */
|
|
|
|
std::vector<size_t> surfEndOffs;
|
|
|
|
surfEndOffs.reserve(bufs.m_surfaces.size());
|
|
|
|
secSz = bufs.m_surfaces.size() * 4 + 4;
|
|
|
|
secSz32 = ROUND_UP_32(secSz);
|
|
|
|
if (secSz32 == 0)
|
|
|
|
secSz32 = 32;
|
|
|
|
head.secSizes.push_back(secSz32);
|
|
|
|
paddingSizes.push_back(secSz32 - secSz);
|
|
|
|
|
|
|
|
/* Surfaces */
|
|
|
|
size_t endOff = 0;
|
|
|
|
for (const hecl::blender::HMDLBuffers::Surface& surf : bufs.m_surfaces) {
|
|
|
|
(void)surf;
|
|
|
|
head.secSizes.push_back(64);
|
|
|
|
paddingSizes.push_back(0);
|
|
|
|
endOff += 64;
|
|
|
|
surfEndOffs.push_back(endOff);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Write sections */
|
|
|
|
athena::io::FileWriter writer(outPath.getAbsolutePath());
|
|
|
|
head.write(writer);
|
|
|
|
std::vector<size_t>::const_iterator padIt = paddingSizes.cbegin();
|
|
|
|
|
|
|
|
/* Material Sets */
|
|
|
|
for (const MaterialSet& mset : matSets) {
|
|
|
|
mset.write(writer);
|
2016-03-02 07:29:19 +00:00
|
|
|
writer.fill(atUint8(0), *padIt);
|
|
|
|
++padIt;
|
2018-12-08 05:30:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Metadata */
|
|
|
|
bufs.m_meta.write(writer);
|
|
|
|
writer.fill(atUint8(0), *padIt);
|
|
|
|
++padIt;
|
|
|
|
|
|
|
|
/* VBO */
|
|
|
|
writer.writeUBytes(bufs.m_vboData.get(), bufs.m_vboSz);
|
|
|
|
writer.fill(atUint8(0), *padIt);
|
|
|
|
++padIt;
|
|
|
|
|
|
|
|
/* IBO */
|
|
|
|
writer.writeUBytes(bufs.m_iboData.get(), bufs.m_iboSz);
|
|
|
|
writer.fill(atUint8(0), *padIt);
|
|
|
|
++padIt;
|
|
|
|
|
|
|
|
/* Surface index */
|
|
|
|
writer.writeUint32Big(surfEndOffs.size());
|
|
|
|
for (size_t off : surfEndOffs)
|
|
|
|
writer.writeUint32Big(off);
|
|
|
|
writer.fill(atUint8(0), *padIt);
|
|
|
|
++padIt;
|
|
|
|
|
|
|
|
/* Surfaces */
|
|
|
|
for (const hecl::blender::HMDLBuffers::Surface& surf : bufs.m_surfaces) {
|
|
|
|
const Mesh::Surface& osurf = surf.m_origSurf;
|
|
|
|
|
|
|
|
SurfaceHeader header;
|
|
|
|
header.centroid = osurf.centroid;
|
|
|
|
header.matIdx = osurf.materialIdx;
|
|
|
|
header.reflectionNormal = osurf.reflectionNormal;
|
|
|
|
header.idxStart = surf.m_start;
|
|
|
|
header.idxCount = surf.m_count;
|
|
|
|
header.skinMtxBankIdx = osurf.skinBankIdx;
|
|
|
|
header.write(writer);
|
2016-03-02 07:29:19 +00:00
|
|
|
|
|
|
|
writer.fill(atUint8(0), *padIt);
|
|
|
|
++padIt;
|
2018-12-08 05:30:43 +00:00
|
|
|
}
|
2016-03-02 07:29:19 +00:00
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
/* Ensure final surface's alignment writes zeros */
|
|
|
|
writer.seek(-1, athena::Current);
|
|
|
|
writer.writeUByte(0);
|
|
|
|
writer.close();
|
|
|
|
return true;
|
|
|
|
}
|
2016-03-02 07:29:19 +00:00
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
template bool WriteHMDLCMDL<DNAMP1::HMDLMaterialSet, DNACMDL::SurfaceHeader_2, 2>(
|
|
|
|
const hecl::ProjectPath& outPath, const hecl::ProjectPath& inPath, const Mesh& mesh,
|
|
|
|
hecl::blender::PoolSkinIndex& poolSkinIndex);
|
|
|
|
|
|
|
|
struct MaterialPool {
|
|
|
|
std::vector<const Material*> materials;
|
|
|
|
size_t addMaterial(const Material& mat, bool& newMat) {
|
|
|
|
size_t ret = 0;
|
|
|
|
newMat = false;
|
|
|
|
for (const Material* testMat : materials) {
|
|
|
|
if (mat == *testMat)
|
|
|
|
return ret;
|
|
|
|
++ret;
|
2016-03-02 07:29:19 +00:00
|
|
|
}
|
2018-12-08 05:30:43 +00:00
|
|
|
materials.push_back(&mat);
|
|
|
|
newMat = true;
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
};
|
2016-03-02 07:29:19 +00:00
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
template <class MaterialSet, class SurfaceHeader, class MeshHeader>
|
|
|
|
bool WriteMREASecs(std::vector<std::vector<uint8_t>>& secsOut, const hecl::ProjectPath& inPath,
|
|
|
|
const std::vector<Mesh>& meshes, zeus::CAABox& fullAABB, std::vector<zeus::CAABox>& meshAABBs) {
|
|
|
|
/* Build material set */
|
|
|
|
std::vector<size_t> surfToGlobalMats;
|
|
|
|
MaterialSet matSet;
|
|
|
|
{
|
|
|
|
MaterialPool matPool;
|
|
|
|
|
|
|
|
size_t surfCount = 0;
|
|
|
|
for (const Mesh& mesh : meshes)
|
|
|
|
surfCount += mesh.surfaces.size();
|
|
|
|
surfToGlobalMats.reserve(surfCount);
|
2016-03-02 07:29:19 +00:00
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
size_t endOff = 0;
|
|
|
|
std::vector<hecl::ProjectPath> texPaths;
|
|
|
|
for (const Mesh& mesh : meshes) {
|
|
|
|
if (mesh.materialSets.size()) {
|
|
|
|
std::vector<size_t> meshToGlobalMats;
|
|
|
|
meshToGlobalMats.reserve(mesh.materialSets[0].size());
|
|
|
|
|
|
|
|
for (const Material& mat : mesh.materialSets[0]) {
|
|
|
|
bool newMat;
|
|
|
|
size_t idx = matPool.addMaterial(mat, newMat);
|
|
|
|
meshToGlobalMats.push_back(idx);
|
|
|
|
if (!newMat)
|
|
|
|
continue;
|
|
|
|
|
2019-05-08 03:50:21 +00:00
|
|
|
for (const auto& chunk : mat.chunks) {
|
|
|
|
if (auto pass = chunk.get_if<Material::PASS>()) {
|
|
|
|
bool found = false;
|
|
|
|
for (const hecl::ProjectPath& ePath : texPaths) {
|
|
|
|
if (pass->tex == ePath) {
|
|
|
|
found = true;
|
|
|
|
break;
|
|
|
|
}
|
2018-12-08 05:30:43 +00:00
|
|
|
}
|
2019-05-08 03:50:21 +00:00
|
|
|
if (!found)
|
|
|
|
texPaths.push_back(pass->tex);
|
2016-03-02 07:29:19 +00:00
|
|
|
}
|
2018-12-08 05:30:43 +00:00
|
|
|
}
|
2016-03-02 07:29:19 +00:00
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
auto lightmapped = mat.iprops.find("retro_lightmapped");
|
|
|
|
bool lm = lightmapped != mat.iprops.cend() && lightmapped->second != 0;
|
2016-03-02 07:29:19 +00:00
|
|
|
|
2019-05-08 03:50:21 +00:00
|
|
|
matSet.materials.emplace_back(mat, texPaths, mesh.colorLayerCount, lm, false);
|
2018-12-08 05:30:43 +00:00
|
|
|
|
|
|
|
matSet.materials.back().binarySize(endOff);
|
|
|
|
matSet.head.addMaterialEndOff(endOff);
|
2016-03-02 07:29:19 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
for (const Mesh::Surface& surf : mesh.surfaces)
|
|
|
|
surfToGlobalMats.push_back(meshToGlobalMats[surf.materialIdx]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (const hecl::ProjectPath& path : texPaths)
|
|
|
|
matSet.head.addTexture(path);
|
2016-03-02 07:29:19 +00:00
|
|
|
|
2018-02-22 07:24:51 +00:00
|
|
|
size_t secSz = 0;
|
2018-12-08 05:30:43 +00:00
|
|
|
matSet.binarySize(secSz);
|
|
|
|
secsOut.emplace_back(secSz, 0);
|
|
|
|
athena::io::MemoryWriter w(secsOut.back().data(), secsOut.back().size());
|
|
|
|
matSet.write(w);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Iterate meshes */
|
|
|
|
auto matIt = surfToGlobalMats.cbegin();
|
|
|
|
for (const Mesh& mesh : meshes) {
|
|
|
|
zeus::CTransform meshXf(mesh.sceneXf.val);
|
|
|
|
meshXf.basis.transpose();
|
|
|
|
|
|
|
|
/* Header */
|
|
|
|
{
|
|
|
|
MeshHeader meshHeader = {};
|
|
|
|
meshHeader.visorFlags.setFromBlenderProps(mesh.customProps);
|
|
|
|
memmove(meshHeader.xfMtx, &mesh.sceneXf, 48);
|
|
|
|
|
|
|
|
zeus::CAABox aabb(zeus::CVector3f(mesh.aabbMin), zeus::CVector3f(mesh.aabbMax));
|
|
|
|
aabb = aabb.getTransformedAABox(meshXf);
|
|
|
|
meshAABBs.push_back(aabb);
|
|
|
|
fullAABB.accumulateBounds(aabb);
|
|
|
|
meshHeader.aabb[0] = aabb.min;
|
|
|
|
meshHeader.aabb[1] = aabb.max;
|
|
|
|
|
|
|
|
size_t secSz = 0;
|
|
|
|
meshHeader.binarySize(secSz);
|
|
|
|
secsOut.emplace_back(secSz, 0);
|
|
|
|
athena::io::MemoryWriter w(secsOut.back().data(), secsOut.back().size());
|
|
|
|
meshHeader.write(w);
|
|
|
|
}
|
2016-03-02 07:29:19 +00:00
|
|
|
|
|
|
|
std::vector<size_t> surfEndOffs;
|
2018-12-08 05:30:43 +00:00
|
|
|
surfEndOffs.reserve(mesh.surfaces.size());
|
2016-03-02 07:29:19 +00:00
|
|
|
size_t endOff = 0;
|
2018-12-08 05:30:43 +00:00
|
|
|
auto smatIt = matIt;
|
|
|
|
for (const Mesh::Surface& surf : mesh.surfaces) {
|
|
|
|
const typename MaterialSet::Material::VAFlags& vaFlags = matSet.materials.at(*smatIt++).getVAFlags();
|
|
|
|
size_t vertSz = vaFlags.vertDLSize();
|
|
|
|
|
|
|
|
endOff += 96;
|
|
|
|
for (auto it = surf.verts.cbegin(); it != surf.verts.cend();) {
|
|
|
|
atUint16 vertCount = 0;
|
|
|
|
auto itEnd = surf.verts.cend();
|
|
|
|
for (auto it2 = it; it2 != surf.verts.cend(); ++it2, ++vertCount)
|
|
|
|
if (it2->iPos == 0xffffffff) {
|
|
|
|
if (vertCount == 3) {
|
|
|
|
/* All primitives here on out are triangles */
|
|
|
|
vertCount = atUint16((surf.verts.cend() - it + 1) * 3 / 4);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
itEnd = it2;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
endOff += 3 + vertSz * vertCount;
|
|
|
|
if (itEnd == surf.verts.cend())
|
|
|
|
break;
|
|
|
|
it = itEnd + 1;
|
|
|
|
}
|
|
|
|
endOff = ROUND_UP_32(endOff);
|
|
|
|
surfEndOffs.push_back(endOff);
|
2016-03-02 07:29:19 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
/* Positions */
|
2016-03-02 07:29:19 +00:00
|
|
|
{
|
2018-12-08 05:30:43 +00:00
|
|
|
size_t secSz = ROUND_UP_32(mesh.pos.size() * 12);
|
|
|
|
if (secSz == 0)
|
|
|
|
secSz = 32;
|
|
|
|
secsOut.emplace_back(secSz, 0);
|
|
|
|
athena::io::MemoryWriter w(secsOut.back().data(), secsOut.back().size());
|
|
|
|
for (const hecl::blender::Vector3f& v : mesh.pos) {
|
|
|
|
zeus::CVector3f preXfPos = meshXf * zeus::CVector3f(v);
|
|
|
|
w.writeVec3fBig(preXfPos);
|
|
|
|
}
|
2016-03-02 07:29:19 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
/* Normals */
|
2016-03-02 07:29:19 +00:00
|
|
|
{
|
2018-12-08 05:30:43 +00:00
|
|
|
size_t secSz = ROUND_UP_32(mesh.norm.size() * 6);
|
|
|
|
if (secSz == 0)
|
|
|
|
secSz = 32;
|
|
|
|
secsOut.emplace_back(secSz, 0);
|
|
|
|
athena::io::MemoryWriter w(secsOut.back().data(), secsOut.back().size());
|
|
|
|
for (const hecl::blender::Vector3f& v : mesh.norm) {
|
|
|
|
zeus::CVector3f preXfNorm = (meshXf.basis * zeus::CVector3f(v)).normalized();
|
|
|
|
for (int i = 0; i < 3; ++i) {
|
|
|
|
int tmpV = int(preXfNorm[i] * 16384.f);
|
|
|
|
tmpV = zeus::clamp(-32768, tmpV, 32767);
|
|
|
|
w.writeInt16Big(atInt16(tmpV));
|
|
|
|
}
|
|
|
|
}
|
2016-03-02 07:29:19 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
/* Colors */
|
2018-03-28 08:09:41 +00:00
|
|
|
{
|
2018-12-08 05:30:43 +00:00
|
|
|
size_t secSz = ROUND_UP_32(mesh.color.size() * 4);
|
|
|
|
if (secSz == 0)
|
|
|
|
secSz = 32;
|
|
|
|
secsOut.emplace_back(secSz, 0);
|
|
|
|
athena::io::MemoryWriter w(secsOut.back().data(), secsOut.back().size());
|
|
|
|
for (const hecl::blender::Vector3f& v : mesh.color) {
|
|
|
|
zeus::CColor col((zeus::CVector4f(zeus::CVector3f(v))));
|
|
|
|
col.writeRGBA8(w);
|
|
|
|
}
|
2018-03-28 08:09:41 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
/* UVs */
|
2018-03-28 08:09:41 +00:00
|
|
|
{
|
2018-12-08 05:30:43 +00:00
|
|
|
size_t secSz = ROUND_UP_32(mesh.uv.size() * 8);
|
|
|
|
if (secSz == 0)
|
|
|
|
secSz = 32;
|
|
|
|
secsOut.emplace_back(secSz, 0);
|
|
|
|
athena::io::MemoryWriter w(secsOut.back().data(), secsOut.back().size());
|
|
|
|
for (const hecl::blender::Vector2f& v : mesh.uv)
|
|
|
|
w.writeVec2fBig(v.val);
|
2018-03-28 08:09:41 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
/* LUVs */
|
2018-03-28 08:09:41 +00:00
|
|
|
{
|
2018-12-08 05:30:43 +00:00
|
|
|
size_t secSz = ROUND_UP_32(mesh.luv.size() * 4);
|
|
|
|
if (secSz == 0)
|
|
|
|
secSz = 32;
|
|
|
|
secsOut.emplace_back(secSz, 0);
|
|
|
|
athena::io::MemoryWriter w(secsOut.back().data(), secsOut.back().size());
|
|
|
|
for (const hecl::blender::Vector2f& v : mesh.luv) {
|
|
|
|
for (int i = 0; i < 2; ++i) {
|
|
|
|
int tmpV = int(v.val.simd[i] * 32768.f);
|
|
|
|
tmpV = zeus::clamp(-32768, tmpV, 32767);
|
|
|
|
w.writeInt16Big(atInt16(tmpV));
|
2018-03-28 08:09:41 +00:00
|
|
|
}
|
2018-12-08 05:30:43 +00:00
|
|
|
}
|
|
|
|
}
|
2018-03-28 08:09:41 +00:00
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
/* Surface index */
|
|
|
|
{
|
|
|
|
secsOut.emplace_back((surfEndOffs.size() + 1) * 4, 0);
|
|
|
|
athena::io::MemoryWriter w(secsOut.back().data(), secsOut.back().size());
|
|
|
|
w.writeUint32Big(surfEndOffs.size());
|
|
|
|
for (size_t off : surfEndOffs)
|
|
|
|
w.writeUint32Big(off);
|
|
|
|
}
|
2018-03-28 08:09:41 +00:00
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
/* Surfaces */
|
|
|
|
GX::Primitive prim = GX::TRIANGLES;
|
|
|
|
if (mesh.topology == hecl::HMDLTopology::Triangles)
|
|
|
|
prim = GX::TRIANGLES;
|
|
|
|
else if (mesh.topology == hecl::HMDLTopology::TriStrips)
|
|
|
|
prim = GX::TRIANGLESTRIP;
|
|
|
|
else
|
2019-07-20 04:27:21 +00:00
|
|
|
LogDNACommon.report(logvisor::Fatal, fmt("unrecognized mesh output mode"));
|
2018-12-08 05:30:43 +00:00
|
|
|
auto surfEndOffIt = surfEndOffs.begin();
|
|
|
|
size_t lastEndOff = 0;
|
|
|
|
for (const Mesh::Surface& surf : mesh.surfaces) {
|
|
|
|
size_t matIdx = *matIt++;
|
|
|
|
const typename MaterialSet::Material& mat = matSet.materials.at(matIdx);
|
|
|
|
const typename MaterialSet::Material::VAFlags& vaFlags = mat.getVAFlags();
|
|
|
|
|
|
|
|
SurfaceHeader header;
|
|
|
|
header.centroid = meshXf * zeus::CVector3f(surf.centroid);
|
|
|
|
header.matIdx = matIdx;
|
|
|
|
uint32_t dlSize = uint32_t(*surfEndOffIt - lastEndOff - 96);
|
|
|
|
header.dlSize = dlSize | 0x80000000;
|
|
|
|
lastEndOff = *surfEndOffIt++;
|
|
|
|
header.reflectionNormal = (meshXf.basis * zeus::CVector3f(surf.reflectionNormal)).normalized();
|
|
|
|
header.aabbSz = 24;
|
|
|
|
zeus::CAABox aabb(zeus::CVector3f(surf.aabbMin), zeus::CVector3f(surf.aabbMax));
|
|
|
|
aabb = aabb.getTransformedAABox(meshXf);
|
|
|
|
header.aabb[0] = aabb.min;
|
|
|
|
header.aabb[1] = aabb.max;
|
|
|
|
|
|
|
|
size_t secSz = 0;
|
|
|
|
header.binarySize(secSz);
|
|
|
|
secSz += dlSize;
|
|
|
|
secSz = ROUND_UP_32(secSz);
|
|
|
|
secsOut.emplace_back(secSz, 0);
|
|
|
|
athena::io::MemoryWriter w(secsOut.back().data(), secsOut.back().size());
|
|
|
|
header.write(w);
|
|
|
|
|
|
|
|
GX::Primitive usePrim = prim;
|
|
|
|
for (auto it = surf.verts.cbegin(); it != surf.verts.cend();) {
|
|
|
|
atUint16 vertCount = 0;
|
|
|
|
auto itEnd = surf.verts.cend();
|
|
|
|
for (auto it2 = it; it2 != surf.verts.cend(); ++it2, ++vertCount)
|
|
|
|
if (it2->iPos == 0xffffffff) {
|
|
|
|
if (vertCount == 3) {
|
|
|
|
/* All primitives here on out are triangles */
|
|
|
|
usePrim = GX::TRIANGLES;
|
|
|
|
vertCount = atUint16((surf.verts.cend() - it + 1) * 3 / 4);
|
|
|
|
break;
|
2018-03-28 08:09:41 +00:00
|
|
|
}
|
2018-12-08 05:30:43 +00:00
|
|
|
itEnd = it2;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* VAT1 = short normals, float UVs
|
|
|
|
* VAT2 = short normals, short UVs */
|
|
|
|
w.writeUByte(usePrim | (mat.flags.lightmapUVArray() ? 0x2 : 0x1));
|
|
|
|
w.writeUint16Big(vertCount);
|
|
|
|
|
|
|
|
for (auto it2 = it; it2 != itEnd; ++it2) {
|
|
|
|
const Mesh::Surface::Vert& vert = *it2;
|
|
|
|
if (vert.iPos == 0xffffffff)
|
|
|
|
continue;
|
|
|
|
atUint32 skinIdx = vert.iBankSkin * 3;
|
|
|
|
WriteDLVal(w, vaFlags.pnMatIdx(), skinIdx);
|
|
|
|
WriteDLVal(w, vaFlags.tex0MatIdx(), skinIdx);
|
|
|
|
WriteDLVal(w, vaFlags.tex1MatIdx(), skinIdx);
|
|
|
|
WriteDLVal(w, vaFlags.tex2MatIdx(), skinIdx);
|
|
|
|
WriteDLVal(w, vaFlags.tex3MatIdx(), skinIdx);
|
|
|
|
WriteDLVal(w, vaFlags.tex4MatIdx(), skinIdx);
|
|
|
|
WriteDLVal(w, vaFlags.tex5MatIdx(), skinIdx);
|
|
|
|
WriteDLVal(w, vaFlags.tex6MatIdx(), skinIdx);
|
|
|
|
WriteDLVal(w, vaFlags.position(), vert.iPos);
|
|
|
|
WriteDLVal(w, vaFlags.normal(), vert.iNorm);
|
|
|
|
WriteDLVal(w, vaFlags.color0(), vert.iColor[0]);
|
|
|
|
WriteDLVal(w, vaFlags.color1(), vert.iColor[1]);
|
|
|
|
WriteDLVal(w, vaFlags.tex0(), vert.iUv[0]);
|
|
|
|
WriteDLVal(w, vaFlags.tex1(), vert.iUv[1]);
|
|
|
|
WriteDLVal(w, vaFlags.tex2(), vert.iUv[2]);
|
|
|
|
WriteDLVal(w, vaFlags.tex3(), vert.iUv[3]);
|
|
|
|
WriteDLVal(w, vaFlags.tex4(), vert.iUv[4]);
|
|
|
|
WriteDLVal(w, vaFlags.tex5(), vert.iUv[5]);
|
|
|
|
WriteDLVal(w, vaFlags.tex6(), vert.iUv[6]);
|
2018-03-28 08:09:41 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
if (itEnd == surf.verts.cend())
|
|
|
|
break;
|
|
|
|
it = itEnd + 1;
|
|
|
|
}
|
2018-03-28 08:09:41 +00:00
|
|
|
}
|
2018-12-08 05:30:43 +00:00
|
|
|
}
|
2018-03-28 08:09:41 +00:00
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
return true;
|
2016-08-10 02:52:00 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
template bool WriteMREASecs<DNAMP1::MaterialSet, DNACMDL::SurfaceHeader_1, DNAMP1::MREA::MeshHeader>(
|
|
|
|
std::vector<std::vector<uint8_t>>& secsOut, const hecl::ProjectPath& inPath, const std::vector<Mesh>& meshes,
|
|
|
|
zeus::CAABox& fullAABB, std::vector<zeus::CAABox>& meshAABBs);
|
2018-03-28 08:09:41 +00:00
|
|
|
|
2016-08-10 02:52:00 +00:00
|
|
|
template <class MaterialSet, class SurfaceHeader, class MeshHeader>
|
|
|
|
bool WriteHMDLMREASecs(std::vector<std::vector<uint8_t>>& secsOut, const hecl::ProjectPath& inPath,
|
2018-12-08 05:30:43 +00:00
|
|
|
const std::vector<Mesh>& meshes, zeus::CAABox& fullAABB, std::vector<zeus::CAABox>& meshAABBs) {
|
|
|
|
/* Build material set */
|
|
|
|
std::vector<size_t> surfToGlobalMats;
|
|
|
|
{
|
|
|
|
MaterialPool matPool;
|
2016-08-12 19:42:18 +00:00
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
size_t surfCount = 0;
|
|
|
|
for (const Mesh& mesh : meshes)
|
|
|
|
surfCount += mesh.surfaces.size();
|
|
|
|
surfToGlobalMats.reserve(surfCount);
|
2016-08-12 19:42:18 +00:00
|
|
|
|
2019-05-08 03:50:21 +00:00
|
|
|
MaterialSet matSet = {};
|
2018-12-08 05:30:43 +00:00
|
|
|
size_t endOff = 0;
|
|
|
|
for (const Mesh& mesh : meshes) {
|
|
|
|
if (mesh.materialSets.size()) {
|
|
|
|
std::vector<size_t> meshToGlobalMats;
|
|
|
|
meshToGlobalMats.reserve(mesh.materialSets[0].size());
|
|
|
|
|
|
|
|
for (const Material& mat : mesh.materialSets[0]) {
|
|
|
|
bool newMat;
|
|
|
|
size_t idx = matPool.addMaterial(mat, newMat);
|
|
|
|
meshToGlobalMats.push_back(idx);
|
|
|
|
if (!newMat)
|
|
|
|
continue;
|
|
|
|
|
2019-05-08 03:50:21 +00:00
|
|
|
++matSet.materialCount;
|
|
|
|
matSet.materials.emplace_back(mat);
|
2018-12-08 05:30:43 +00:00
|
|
|
matSet.materials.back().binarySize(endOff);
|
2019-05-08 03:50:21 +00:00
|
|
|
matSet.materialEndOffs.push_back(endOff);
|
2016-08-10 02:52:00 +00:00
|
|
|
}
|
2018-12-08 05:30:43 +00:00
|
|
|
|
|
|
|
for (const Mesh::Surface& surf : mesh.surfaces)
|
|
|
|
surfToGlobalMats.push_back(meshToGlobalMats[surf.materialIdx]);
|
|
|
|
}
|
2016-08-10 02:52:00 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
size_t secSz = 0;
|
|
|
|
matSet.binarySize(secSz);
|
|
|
|
secsOut.emplace_back(secSz, 0);
|
|
|
|
athena::io::MemoryWriter w(secsOut.back().data(), secsOut.back().size());
|
|
|
|
matSet.write(w);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Iterate meshes */
|
|
|
|
auto matIt = surfToGlobalMats.cbegin();
|
|
|
|
for (const Mesh& mesh : meshes) {
|
|
|
|
zeus::CTransform meshXf(mesh.sceneXf.val);
|
|
|
|
meshXf.basis.transpose();
|
|
|
|
|
|
|
|
/* Header */
|
2016-08-10 02:52:00 +00:00
|
|
|
{
|
2018-12-08 05:30:43 +00:00
|
|
|
MeshHeader meshHeader = {};
|
|
|
|
meshHeader.visorFlags.setFromBlenderProps(mesh.customProps);
|
|
|
|
memmove(meshHeader.xfMtx, &mesh.sceneXf, 48);
|
|
|
|
|
|
|
|
zeus::CAABox aabb(zeus::CVector3f(mesh.aabbMin), zeus::CVector3f(mesh.aabbMax));
|
|
|
|
aabb = aabb.getTransformedAABox(meshXf);
|
|
|
|
meshAABBs.push_back(aabb);
|
|
|
|
fullAABB.accumulateBounds(aabb);
|
|
|
|
meshHeader.aabb[0] = aabb.min;
|
|
|
|
meshHeader.aabb[1] = aabb.max;
|
|
|
|
|
|
|
|
size_t secSz = 0;
|
|
|
|
meshHeader.binarySize(secSz);
|
|
|
|
secsOut.emplace_back(secSz, 0);
|
|
|
|
athena::io::MemoryWriter w(secsOut.back().data(), secsOut.back().size());
|
|
|
|
meshHeader.write(w);
|
|
|
|
}
|
2016-08-10 21:54:53 +00:00
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
hecl::blender::PoolSkinIndex poolSkinIndex;
|
|
|
|
hecl::blender::HMDLBuffers bufs = mesh.getHMDLBuffers(true, poolSkinIndex);
|
2016-08-10 21:54:53 +00:00
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
std::vector<size_t> surfEndOffs;
|
|
|
|
surfEndOffs.reserve(bufs.m_surfaces.size());
|
|
|
|
size_t endOff = 0;
|
|
|
|
for (const hecl::blender::HMDLBuffers::Surface& surf : bufs.m_surfaces) {
|
|
|
|
(void)surf;
|
|
|
|
endOff += 96;
|
|
|
|
surfEndOffs.push_back(endOff);
|
|
|
|
}
|
2016-08-10 21:54:53 +00:00
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
/* Metadata */
|
|
|
|
{
|
|
|
|
size_t secSz = 0;
|
|
|
|
bufs.m_meta.binarySize(secSz);
|
|
|
|
secsOut.emplace_back(secSz, 0);
|
|
|
|
athena::io::MemoryWriter w(secsOut.back().data(), secsOut.back().size());
|
|
|
|
bufs.m_meta.write(w);
|
|
|
|
}
|
2016-08-10 21:54:53 +00:00
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
/* VBO */
|
|
|
|
{
|
|
|
|
secsOut.emplace_back(bufs.m_vboSz, 0);
|
|
|
|
athena::io::MemoryWriter w(secsOut.back().data(), secsOut.back().size());
|
|
|
|
w.writeUBytes(bufs.m_vboData.get(), bufs.m_vboSz);
|
|
|
|
}
|
2016-08-10 21:54:53 +00:00
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
/* IBO */
|
|
|
|
{
|
|
|
|
secsOut.emplace_back(bufs.m_iboSz, 0);
|
|
|
|
athena::io::MemoryWriter w(secsOut.back().data(), secsOut.back().size());
|
|
|
|
w.writeUBytes(bufs.m_iboData.get(), bufs.m_iboSz);
|
|
|
|
}
|
2016-08-10 21:54:53 +00:00
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
/* Surface index */
|
|
|
|
{
|
|
|
|
secsOut.emplace_back((surfEndOffs.size() + 1) * 4, 0);
|
|
|
|
athena::io::MemoryWriter w(secsOut.back().data(), secsOut.back().size());
|
|
|
|
w.writeUint32Big(surfEndOffs.size());
|
|
|
|
for (size_t off : surfEndOffs)
|
|
|
|
w.writeUint32Big(off);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Surfaces */
|
|
|
|
for (const hecl::blender::HMDLBuffers::Surface& surf : bufs.m_surfaces) {
|
|
|
|
const Mesh::Surface& osurf = surf.m_origSurf;
|
|
|
|
|
|
|
|
SurfaceHeader header;
|
|
|
|
header.centroid = meshXf * zeus::CVector3f(osurf.centroid);
|
|
|
|
header.matIdx = *matIt++;
|
|
|
|
header.reflectionNormal = (meshXf.basis * zeus::CVector3f(osurf.reflectionNormal)).normalized();
|
|
|
|
header.idxStart = surf.m_start;
|
|
|
|
header.idxCount = surf.m_count;
|
|
|
|
header.skinMtxBankIdx = osurf.skinBankIdx;
|
|
|
|
|
|
|
|
header.aabbSz = 24;
|
|
|
|
zeus::CAABox aabb(zeus::CVector3f(surf.m_origSurf.aabbMin), zeus::CVector3f(surf.m_origSurf.aabbMax));
|
|
|
|
aabb = aabb.getTransformedAABox(meshXf);
|
|
|
|
header.aabb[0] = aabb.min;
|
|
|
|
header.aabb[1] = aabb.max;
|
|
|
|
|
|
|
|
size_t secSz = 0;
|
|
|
|
header.binarySize(secSz);
|
|
|
|
secsOut.emplace_back(secSz, 0);
|
|
|
|
athena::io::MemoryWriter w(secsOut.back().data(), secsOut.back().size());
|
|
|
|
header.write(w);
|
2016-08-10 02:52:00 +00:00
|
|
|
}
|
2018-12-08 05:30:43 +00:00
|
|
|
}
|
2016-08-10 02:52:00 +00:00
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
return true;
|
2016-08-10 02:52:00 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
template bool WriteHMDLMREASecs<DNAMP1::HMDLMaterialSet, DNACMDL::SurfaceHeader_2, DNAMP1::MREA::MeshHeader>(
|
|
|
|
std::vector<std::vector<uint8_t>>& secsOut, const hecl::ProjectPath& inPath, const std::vector<Mesh>& meshes,
|
|
|
|
zeus::CAABox& fullAABB, std::vector<zeus::CAABox>& meshAABBs);
|
2016-08-10 02:52:00 +00:00
|
|
|
|
2018-02-22 07:24:51 +00:00
|
|
|
template <>
|
2018-12-08 05:30:43 +00:00
|
|
|
void SurfaceHeader_1::Enumerate<BigDNA::Read>(typename Read::StreamT& reader) {
|
|
|
|
/* centroid */
|
|
|
|
centroid = reader.readVec3fBig();
|
|
|
|
/* matIdx */
|
|
|
|
matIdx = reader.readUint32Big();
|
|
|
|
/* dlSize */
|
|
|
|
dlSize = reader.readUint32Big();
|
|
|
|
/* idxStart */
|
|
|
|
idxStart = reader.readUint32Big();
|
|
|
|
/* idxCount */
|
|
|
|
idxCount = reader.readUint32Big();
|
|
|
|
/* aabbSz */
|
|
|
|
aabbSz = reader.readUint32Big();
|
|
|
|
/* reflectionNormal */
|
|
|
|
reflectionNormal = reader.readVec3fBig();
|
|
|
|
/* aabb */
|
|
|
|
size_t remAABB = aabbSz;
|
|
|
|
if (remAABB >= 24) {
|
|
|
|
aabb[0] = reader.readVec3fBig();
|
|
|
|
aabb[1] = reader.readVec3fBig();
|
|
|
|
remAABB -= 24;
|
|
|
|
}
|
|
|
|
reader.seek(remAABB, athena::Current);
|
|
|
|
/* align */
|
|
|
|
reader.seekAlign32();
|
2016-07-26 22:05:59 +00:00
|
|
|
}
|
|
|
|
|
2018-02-22 07:24:51 +00:00
|
|
|
template <>
|
2018-12-08 05:30:43 +00:00
|
|
|
void SurfaceHeader_1::Enumerate<BigDNA::Write>(typename Write::StreamT& writer) {
|
|
|
|
/* centroid */
|
|
|
|
writer.writeVec3fBig(centroid);
|
|
|
|
/* matIdx */
|
|
|
|
writer.writeUint32Big(matIdx);
|
|
|
|
/* dlSize */
|
|
|
|
writer.writeUint32Big(dlSize);
|
|
|
|
/* idxStart */
|
|
|
|
writer.writeUint32Big(idxStart);
|
|
|
|
/* idxCount */
|
|
|
|
writer.writeUint32Big(idxCount);
|
|
|
|
/* aabbSz */
|
|
|
|
writer.writeUint32Big(aabbSz ? 24 : 0);
|
|
|
|
/* reflectionNormal */
|
|
|
|
writer.writeVec3fBig(reflectionNormal);
|
|
|
|
/* aabb */
|
|
|
|
if (aabbSz) {
|
|
|
|
writer.writeVec3fBig(aabb[0]);
|
|
|
|
writer.writeVec3fBig(aabb[1]);
|
|
|
|
}
|
|
|
|
/* align */
|
|
|
|
writer.seekAlign32();
|
2016-07-26 22:05:59 +00:00
|
|
|
}
|
|
|
|
|
2018-02-22 07:24:51 +00:00
|
|
|
template <>
|
2018-12-08 05:30:43 +00:00
|
|
|
void SurfaceHeader_1::Enumerate<BigDNA::BinarySize>(typename BinarySize::StreamT& s) {
|
|
|
|
s += (aabbSz ? 24 : 0);
|
|
|
|
s += 44;
|
|
|
|
s = (s + 31) & ~31;
|
2016-07-26 22:05:59 +00:00
|
|
|
}
|
|
|
|
|
2018-02-22 07:24:51 +00:00
|
|
|
template <>
|
2018-12-08 05:30:43 +00:00
|
|
|
void SurfaceHeader_2::Enumerate<BigDNA::Read>(typename Read::StreamT& reader) {
|
|
|
|
/* centroid */
|
|
|
|
centroid = reader.readVec3fBig();
|
|
|
|
/* matIdx */
|
|
|
|
matIdx = reader.readUint32Big();
|
|
|
|
/* dlSize */
|
|
|
|
dlSize = reader.readUint32Big();
|
|
|
|
/* idxStart */
|
|
|
|
idxStart = reader.readUint32Big();
|
|
|
|
/* idxCount */
|
|
|
|
idxCount = reader.readUint32Big();
|
|
|
|
/* aabbSz */
|
|
|
|
aabbSz = reader.readUint32Big();
|
|
|
|
/* reflectionNormal */
|
|
|
|
reflectionNormal = reader.readVec3fBig();
|
|
|
|
/* skinMtxBankIdx */
|
|
|
|
skinMtxBankIdx = reader.readInt16Big();
|
|
|
|
/* surfaceGroup */
|
|
|
|
surfaceGroup = reader.readUint16Big();
|
|
|
|
/* aabb */
|
|
|
|
size_t remAABB = aabbSz;
|
|
|
|
if (remAABB >= 24) {
|
|
|
|
aabb[0] = reader.readVec3fBig();
|
|
|
|
aabb[1] = reader.readVec3fBig();
|
|
|
|
remAABB -= 24;
|
|
|
|
}
|
|
|
|
reader.seek(remAABB, athena::Current);
|
|
|
|
/* align */
|
|
|
|
reader.seekAlign32();
|
2016-07-26 22:05:59 +00:00
|
|
|
}
|
|
|
|
|
2018-02-22 07:24:51 +00:00
|
|
|
template <>
|
2018-12-08 05:30:43 +00:00
|
|
|
void SurfaceHeader_2::Enumerate<BigDNA::Write>(typename Write::StreamT& writer) {
|
|
|
|
/* centroid */
|
|
|
|
writer.writeVec3fBig(centroid);
|
|
|
|
/* matIdx */
|
|
|
|
writer.writeUint32Big(matIdx);
|
|
|
|
/* dlSize */
|
|
|
|
writer.writeUint32Big(dlSize);
|
|
|
|
/* idxStart */
|
|
|
|
writer.writeUint32Big(idxStart);
|
|
|
|
/* idxCount */
|
|
|
|
writer.writeUint32Big(idxCount);
|
|
|
|
/* aabbSz */
|
|
|
|
writer.writeUint32Big(aabbSz ? 24 : 0);
|
|
|
|
/* reflectionNormal */
|
|
|
|
writer.writeVec3fBig(reflectionNormal);
|
|
|
|
/* skinMtxBankIdx */
|
|
|
|
writer.writeInt16Big(skinMtxBankIdx);
|
|
|
|
/* surfaceGroup */
|
|
|
|
writer.writeUint16Big(surfaceGroup);
|
|
|
|
/* aabb */
|
|
|
|
if (aabbSz) {
|
|
|
|
writer.writeVec3fBig(aabb[0]);
|
|
|
|
writer.writeVec3fBig(aabb[1]);
|
|
|
|
}
|
|
|
|
/* align */
|
|
|
|
writer.seekAlign32();
|
2016-07-26 22:05:59 +00:00
|
|
|
}
|
|
|
|
|
2018-02-22 07:24:51 +00:00
|
|
|
template <>
|
2018-12-08 05:30:43 +00:00
|
|
|
void SurfaceHeader_2::Enumerate<BigDNA::BinarySize>(typename BinarySize::StreamT& s) {
|
|
|
|
s += (aabbSz ? 24 : 0);
|
|
|
|
s += 48;
|
|
|
|
s = (s + 31) & ~31;
|
2016-07-26 22:05:59 +00:00
|
|
|
}
|
|
|
|
|
2018-02-22 07:24:51 +00:00
|
|
|
template <>
|
2018-12-08 05:30:43 +00:00
|
|
|
void SurfaceHeader_3::Enumerate<BigDNA::Read>(typename Read::StreamT& reader) {
|
|
|
|
/* centroid */
|
|
|
|
centroid = reader.readVec3fBig();
|
|
|
|
/* matIdx */
|
|
|
|
matIdx = reader.readUint32Big();
|
|
|
|
/* dlSize */
|
|
|
|
dlSize = reader.readUint32Big();
|
|
|
|
/* idxStart */
|
|
|
|
idxStart = reader.readUint32Big();
|
|
|
|
/* idxCount */
|
|
|
|
idxCount = reader.readUint32Big();
|
|
|
|
/* aabbSz */
|
|
|
|
aabbSz = reader.readUint32Big();
|
|
|
|
/* reflectionNormal */
|
|
|
|
reflectionNormal = reader.readVec3fBig();
|
|
|
|
/* skinMtxBankIdx */
|
|
|
|
skinMtxBankIdx = reader.readInt16Big();
|
|
|
|
/* surfaceGroup */
|
|
|
|
surfaceGroup = reader.readUint16Big();
|
|
|
|
/* aabb */
|
|
|
|
size_t remAABB = aabbSz;
|
|
|
|
if (remAABB >= 24) {
|
|
|
|
aabb[0] = reader.readVec3fBig();
|
|
|
|
aabb[1] = reader.readVec3fBig();
|
|
|
|
remAABB -= 24;
|
|
|
|
}
|
|
|
|
reader.seek(remAABB, athena::Current);
|
|
|
|
/* unk3 */
|
|
|
|
unk3 = reader.readUByte();
|
|
|
|
/* align */
|
|
|
|
reader.seekAlign32();
|
2016-07-26 22:05:59 +00:00
|
|
|
}
|
|
|
|
|
2018-02-22 07:24:51 +00:00
|
|
|
template <>
|
2018-12-08 05:30:43 +00:00
|
|
|
void SurfaceHeader_3::Enumerate<BigDNA::Write>(typename Write::StreamT& writer) {
|
|
|
|
/* centroid */
|
|
|
|
writer.writeVec3fBig(centroid);
|
|
|
|
/* matIdx */
|
|
|
|
writer.writeUint32Big(matIdx);
|
|
|
|
/* dlSize */
|
|
|
|
writer.writeUint32Big(dlSize);
|
|
|
|
/* idxStart */
|
|
|
|
writer.writeUint32Big(idxStart);
|
|
|
|
/* idxCount */
|
|
|
|
writer.writeUint32Big(idxCount);
|
|
|
|
/* aabbSz */
|
|
|
|
writer.writeUint32Big(aabbSz ? 24 : 0);
|
|
|
|
/* reflectionNormal */
|
|
|
|
writer.writeVec3fBig(reflectionNormal);
|
|
|
|
/* skinMtxBankIdx */
|
|
|
|
writer.writeInt16Big(skinMtxBankIdx);
|
|
|
|
/* surfaceGroup */
|
|
|
|
writer.writeUint16Big(surfaceGroup);
|
|
|
|
/* aabb */
|
|
|
|
if (aabbSz) {
|
|
|
|
writer.writeVec3fBig(aabb[0]);
|
|
|
|
writer.writeVec3fBig(aabb[1]);
|
|
|
|
}
|
|
|
|
/* unk3 */
|
|
|
|
writer.writeUByte(unk3);
|
|
|
|
/* align */
|
|
|
|
writer.seekAlign32();
|
2016-07-26 22:05:59 +00:00
|
|
|
}
|
|
|
|
|
2018-02-22 07:24:51 +00:00
|
|
|
template <>
|
2018-12-08 05:30:43 +00:00
|
|
|
void SurfaceHeader_3::Enumerate<BigDNA::BinarySize>(typename BinarySize::StreamT& s) {
|
|
|
|
s += (aabbSz ? 24 : 0);
|
|
|
|
s += 49;
|
|
|
|
s = (s + 31) & ~31;
|
2016-07-26 22:05:59 +00:00
|
|
|
}
|
|
|
|
|
2018-12-08 05:30:43 +00:00
|
|
|
} // namespace DataSpec::DNACMDL
|