#ifndef ASSIMP_BUILD_NO_LWO_IMPORTER
#include "LWOLoader.h"
#include "StringComparison.h"
#include "SGSpatialSort.h"
#include "ByteSwapper.h"
#include "ProcessHelper.h"
#include "ConvertToLHProcess.h"
#include <assimp/IOSystem.hpp>
#include <assimp/importerdesc.h>
#include <memory>
#include <sstream>
#include <iomanip>
#include <map>
using namespace Assimp;
static const aiImporterDesc desc = {
"LightWave/Modo Object Importer",
"",
"",
"https://www.lightwave3d.com/lightwave_sdk/",
aiImporterFlags_SupportTextFlavour,
0,
0,
0,
0,
"lwo lxo"
};
LWOImporter::LWOImporter()
: mIsLWO2(),
mIsLXOB(),
mLayers(),
mCurLayer(),
mTags(),
mMapping(),
mSurfaces(),
mFileBuffer(),
fileSize(),
pScene(),
configSpeedFlag(),
configLayerIndex(),
hasNamedLayer()
{}
LWOImporter::~LWOImporter()
{}
bool LWOImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
{
const std::string extension = GetExtension(pFile);
if (extension == "lwo" || extension == "lxo") {
return true;
}
if (!extension.length() || checkSig) {
uint32_t tokens[3];
tokens[0] = AI_LWO_FOURCC_LWOB;
tokens[1] = AI_LWO_FOURCC_LWO2;
tokens[2] = AI_LWO_FOURCC_LXOB;
return CheckMagicToken(pIOHandler,pFile,tokens,3,8);
}
return false;
}
void LWOImporter::SetupProperties(const Importer* pImp)
{
configSpeedFlag = ( 0 != pImp->GetPropertyInteger(AI_CONFIG_FAVOUR_SPEED,0) ? true : false);
configLayerIndex = pImp->GetPropertyInteger (AI_CONFIG_IMPORT_LWO_ONE_LAYER_ONLY,UINT_MAX);
configLayerName = pImp->GetPropertyString (AI_CONFIG_IMPORT_LWO_ONE_LAYER_ONLY,"");
}
const aiImporterDesc* LWOImporter::GetInfo () const
{
return &desc;
}
void LWOImporter::InternReadFile( const std::string& pFile,
aiScene* pScene,
IOSystem* pIOHandler)
{
std::unique_ptr<IOStream> file( pIOHandler->Open( pFile, "rb"));
if( file.get() == NULL)
throw DeadlyImportError( "Failed to open LWO file " + pFile + ".");
if((this->fileSize = (unsigned int)file->FileSize()) < 12)
throw DeadlyImportError("LWO: The file is too small to contain the IFF header");
std::vector< uint8_t > mBuffer(fileSize);
file->Read( &mBuffer[0], 1, fileSize);
this->pScene = pScene;
uint32_t fileType;
const char* sz = IFF::ReadHeader(&mBuffer[0],fileType);
if (sz)throw DeadlyImportError(sz);
mFileBuffer = &mBuffer[0] + 12;
fileSize -= 12;
hasNamedLayer = false;
LayerList _mLayers;
SurfaceList _mSurfaces;
TagList _mTags;
TagMappingTable _mMapping;
mLayers = &_mLayers;
mTags = &_mTags;
mMapping = &_mMapping;
mSurfaces = &_mSurfaces;
mLayers->push_back(Layer());
mCurLayer = &mLayers->back();
mCurLayer->mName = "<LWODefault>";
mCurLayer->mIndex = -1;
if (AI_LWO_FOURCC_LWOB == fileType) {
DefaultLogger::get()->info("LWO file format: LWOB (<= LightWave 5.5)");
mIsLWO2 = false;
mIsLXOB = false;
LoadLWOBFile();
}
else if (AI_LWO_FOURCC_LWO2 == fileType) {
mIsLXOB = false;
DefaultLogger::get()->info("LWO file format: LWO2 (>= LightWave 6)");
}
else if (AI_LWO_FOURCC_LXOB == fileType) {
mIsLXOB = true;
DefaultLogger::get()->info("LWO file format: LXOB (Modo)");
}
else
{
char szBuff[5];
szBuff[0] = (char)(fileType >> 24u);
szBuff[1] = (char)(fileType >> 16u);
szBuff[2] = (char)(fileType >> 8u);
szBuff[3] = (char)(fileType);
szBuff[4] = '\0';
throw DeadlyImportError(std::string("Unknown LWO sub format: ") + szBuff);
}
if (AI_LWO_FOURCC_LWOB != fileType) {
mIsLWO2 = true;
LoadLWO2File();
if (UINT_MAX != configLayerIndex) {
unsigned int layerCount = 0;
for(std::list<LWO::Layer>::iterator itLayers=mLayers->begin(); itLayers!=mLayers->end(); ++itLayers)
if (!itLayers->skip)
layerCount++;
if (layerCount!=2)
throw DeadlyImportError("LWO2: The requested layer was not found");
}
if (configLayerName.length() && !hasNamedLayer) {
throw DeadlyImportError("LWO2: Unable to find the requested layer: "
+ configLayerName);
}
}
ResolveTags();
ResolveClips();
std::vector<aiMesh*> apcMeshes;
std::map<uint16_t, aiNode*> apcNodes;
apcMeshes.reserve(mLayers->size()*std::min(((unsigned int)mSurfaces->size()/2u), 1u));
unsigned int iDefaultSurface = UINT_MAX; for (LWO::Layer &layer : *mLayers) {
if (layer.skip)
continue;
const unsigned int meshStart = (unsigned int)apcMeshes.size();
if (!layer.mFaces.empty() && !layer.mTempPoints.empty()) {
std::vector<SortedRep> pSorted(mSurfaces->size()+1);
unsigned int i = 0;
for (FaceList::iterator it = layer.mFaces.begin(), end = layer.mFaces.end();it != end;++it,++i) {
if ((*it).type != AI_LWO_FACE && (*it).type != AI_LWO_PTCH &&
(*it).type != AI_LWO_BONE && (*it).type != AI_LWO_SUBD) {
continue;
}
unsigned int idx = (*it).surfaceIndex;
if (idx >= mTags->size())
{
DefaultLogger::get()->warn("LWO: Invalid face surface index");
idx = UINT_MAX;
}
if(UINT_MAX == idx || UINT_MAX == (idx = _mMapping[idx])) {
if (UINT_MAX == iDefaultSurface) {
iDefaultSurface = (unsigned int)mSurfaces->size();
mSurfaces->push_back(LWO::Surface());
LWO::Surface& surf = mSurfaces->back();
surf.mColor.r = surf.mColor.g = surf.mColor.b = 0.6f;
surf.mName = "LWODefaultSurface";
}
idx = iDefaultSurface;
}
pSorted[idx].push_back(i);
}
if (UINT_MAX == iDefaultSurface) {
pSorted.erase(pSorted.end()-1);
}
for (unsigned int p = 0,i = 0;i < mSurfaces->size();++i) {
SortedRep& sorted = pSorted[i];
if (sorted.empty())
continue;
aiMesh* mesh = new aiMesh();
apcMeshes.push_back(mesh);
mesh->mNumFaces = (unsigned int)sorted.size();
SortedRep::const_iterator it = sorted.begin(), end = sorted.end();
for (;it != end;++it) {
mesh->mNumVertices += layer.mFaces[*it].mNumIndices;
}
aiVector3D *nrm = NULL, * pv = mesh->mVertices = new aiVector3D[mesh->mNumVertices];
aiFace* pf = mesh->mFaces = new aiFace[mesh->mNumFaces];
mesh->mMaterialIndex = i;
unsigned int vUVChannelIndices[AI_MAX_NUMBER_OF_TEXTURECOORDS];
unsigned int vVColorIndices[AI_MAX_NUMBER_OF_COLOR_SETS];
#ifdef ASSIMP_BUILD_DEBUG
for (unsigned int mui = 0; mui < AI_MAX_NUMBER_OF_TEXTURECOORDS;++mui ) {
vUVChannelIndices[mui] = UINT_MAX;
}
for (unsigned int mui = 0; mui < AI_MAX_NUMBER_OF_COLOR_SETS;++mui ) {
vVColorIndices[mui] = UINT_MAX;
}
#endif
FindUVChannels(_mSurfaces[i],sorted,layer,vUVChannelIndices);
FindVCChannels(_mSurfaces[i],sorted,layer,vVColorIndices);
aiVector3D* pvUV[AI_MAX_NUMBER_OF_TEXTURECOORDS];
for (unsigned int mui = 0; mui < AI_MAX_NUMBER_OF_TEXTURECOORDS;++mui ) {
if (UINT_MAX == vUVChannelIndices[mui]) {
break;
}
pvUV[mui] = mesh->mTextureCoords[mui] = new aiVector3D[mesh->mNumVertices];
mesh->mNumUVComponents[0] = 2;
}
if (layer.mNormals.name.length())
nrm = mesh->mNormals = new aiVector3D[mesh->mNumVertices];
aiColor4D* pvVC[AI_MAX_NUMBER_OF_COLOR_SETS];
for (unsigned int mui = 0; mui < AI_MAX_NUMBER_OF_COLOR_SETS;++mui) {
if (UINT_MAX == vVColorIndices[mui]) {
break;
}
pvVC[mui] = mesh->mColors[mui] = new aiColor4D[mesh->mNumVertices];
}
std::vector<unsigned int>& smoothingGroups = layer.mPointReferrers;
smoothingGroups.erase (smoothingGroups.begin(),smoothingGroups.end());
smoothingGroups.resize(mesh->mNumFaces,0);
unsigned int vert = 0;
std::vector<unsigned int>::iterator outIt = smoothingGroups.begin();
for (it = sorted.begin(); it != end;++it,++outIt) {
const LWO::Face& face = layer.mFaces[*it];
*outIt = face.smoothGroup;
for (unsigned int q = 0; q < face.mNumIndices;++q,++vert) {
unsigned int idx = face.mIndices[q];
*pv++ = layer.mTempPoints[idx] ;
for (unsigned int w = 0; w < AI_MAX_NUMBER_OF_TEXTURECOORDS;++w) {
if (UINT_MAX == vUVChannelIndices[w]) {
break;
}
aiVector3D*& pp = pvUV[w];
const aiVector2D& src = ((aiVector2D*)&layer.mUVChannels[vUVChannelIndices[w]].rawData[0])[idx];
pp->x = src.x;
pp->y = src.y;
pp++;
}
if (nrm) {
*nrm = ((aiVector3D*)&layer.mNormals.rawData[0])[idx];
nrm->z *= -1.f;
++nrm;
}
for (unsigned int w = 0; w < AI_MAX_NUMBER_OF_COLOR_SETS;++w) {
if (UINT_MAX == vVColorIndices[w]) {
break;
}
*pvVC[w] = ((aiColor4D*)&layer.mVColorChannels[vVColorIndices[w]].rawData[0])[idx];
if(_mSurfaces[i].mVCMapType == AI_LWO_RGB)
pvVC[w]->a = 1.f;
pvVC[w]++;
}
#if 0#endif
face.mIndices[q] = vert;
}
pf->mIndices = face.mIndices;
pf->mNumIndices = face.mNumIndices;
unsigned int** p = (unsigned int**)&face.mIndices;*p = NULL; pf++;
}
if (!mesh->mNormals) {
ComputeNormals(mesh,smoothingGroups,_mSurfaces[i]);
}
else DefaultLogger::get()->debug("LWO2: No need to compute normals, they're already there");
++p;
}
}
unsigned int num = static_cast<unsigned int>(apcMeshes.size() - meshStart);
if (layer.mName != "<LWODefault>" || num > 0) {
aiNode* pcNode = new aiNode();
apcNodes[layer.mIndex] = pcNode;
pcNode->mName.Set(layer.mName);
pcNode->mParent = (aiNode*)&layer;
pcNode->mNumMeshes = num;
if (pcNode->mNumMeshes) {
pcNode->mMeshes = new unsigned int[pcNode->mNumMeshes];
for (unsigned int p = 0; p < pcNode->mNumMeshes;++p)
pcNode->mMeshes[p] = p + meshStart;
}
}
}
if (apcNodes.empty() || apcMeshes.empty())
throw DeadlyImportError("LWO: No meshes loaded");
pScene->mMaterials = new aiMaterial*[pScene->mNumMaterials = (unsigned int)mSurfaces->size()];
for (unsigned int mat = 0; mat < pScene->mNumMaterials;++mat) {
aiMaterial* pcMat = new aiMaterial();
pScene->mMaterials[mat] = pcMat;
ConvertMaterial((*mSurfaces)[mat],pcMat);
}
pScene->mMeshes = new aiMesh*[ pScene->mNumMeshes = (unsigned int)apcMeshes.size() ];
::memcpy(pScene->mMeshes,&apcMeshes[0],pScene->mNumMeshes*sizeof(void*));
GenerateNodeGraph(apcNodes);
}
void LWOImporter::ComputeNormals(aiMesh* mesh, const std::vector<unsigned int>& smoothingGroups,
const LWO::Surface& surface)
{
mesh->mNormals = new aiVector3D[mesh->mNumVertices];
aiVector3D* out;
std::vector<aiVector3D> faceNormals;
if (!surface.mMaximumSmoothAngle)
out = mesh->mNormals;
else {
faceNormals.resize(mesh->mNumVertices);
out = &faceNormals[0];
}
aiFace* begin = mesh->mFaces, *const end = mesh->mFaces+mesh->mNumFaces;
for (; begin != end; ++begin) {
aiFace& face = *begin;
if(face.mNumIndices < 3) {
continue;
}
aiVector3D* pV1 = mesh->mVertices + face.mIndices[0];
aiVector3D* pV2 = mesh->mVertices + face.mIndices[1];
aiVector3D* pV3 = mesh->mVertices + face.mIndices[face.mNumIndices-1];
aiVector3D vNor = ((*pV2 - *pV1) ^(*pV3 - *pV1)).Normalize();
for (unsigned int i = 0; i < face.mNumIndices;++i)
out[face.mIndices[i]] = vNor;
}
if (!surface.mMaximumSmoothAngle)return;
const float posEpsilon = ComputePositionEpsilon(mesh);
SGSpatialSort sSort;
std::vector<unsigned int>::const_iterator it = smoothingGroups.begin();
for( begin = mesh->mFaces; begin != end; ++begin, ++it)
{
aiFace& face = *begin;
for (unsigned int i = 0; i < face.mNumIndices;++i)
{
unsigned int tt = face.mIndices[i];
sSort.Add(mesh->mVertices[tt],tt,*it);
}
}
sSort.Prepare();
std::vector<unsigned int> poResult;
poResult.reserve(20);
if (surface.mMaximumSmoothAngle < 3.f && !configSpeedFlag) {
const float fLimit = std::cos(surface.mMaximumSmoothAngle);
for( begin = mesh->mFaces, it = smoothingGroups.begin(); begin != end; ++begin, ++it) {
const aiFace& face = *begin;
unsigned int* beginIdx = face.mIndices, *const endIdx = face.mIndices+face.mNumIndices;
for (; beginIdx != endIdx; ++beginIdx)
{
unsigned int idx = *beginIdx;
sSort.FindPositions(mesh->mVertices[idx],*it,posEpsilon,poResult,true);
std::vector<unsigned int>::const_iterator a, end = poResult.end();
aiVector3D vNormals;
for (a = poResult.begin();a != end;++a) {
const aiVector3D& v = faceNormals[*a];
if (v * faceNormals[idx] < fLimit)
continue;
vNormals += v;
}
mesh->mNormals[idx] = vNormals.Normalize();
}
}
}
else {
std::vector<bool> vertexDone(mesh->mNumVertices,false);
for( begin = mesh->mFaces, it = smoothingGroups.begin(); begin != end; ++begin, ++it) {
const aiFace& face = *begin;
unsigned int* beginIdx = face.mIndices, *const endIdx = face.mIndices+face.mNumIndices;
for (; beginIdx != endIdx; ++beginIdx)
{
unsigned int idx = *beginIdx;
if (vertexDone[idx])
continue;
sSort.FindPositions(mesh->mVertices[idx],*it,posEpsilon,poResult,true);
std::vector<unsigned int>::const_iterator a, end = poResult.end();
aiVector3D vNormals;
for (a = poResult.begin();a != end;++a) {
const aiVector3D& v = faceNormals[*a];
vNormals += v;
}
vNormals.Normalize();
for (a = poResult.begin();a != end;++a) {
mesh->mNormals[*a] = vNormals;
vertexDone[*a] = true;
}
}
}
}
}
void LWOImporter::GenerateNodeGraph(std::map<uint16_t,aiNode*>& apcNodes)
{
aiNode* root = pScene->mRootNode = new aiNode();
root->mName.Set("<LWORoot>");
std::map<uint16_t, aiNode*> mapPivot;
for (std::map<uint16_t,aiNode*>::iterator itapcNodes = apcNodes.begin(); itapcNodes != apcNodes.end(); ++itapcNodes) {
LWO::Layer* nodeLayer = (LWO::Layer*)(itapcNodes->second->mParent);
uint16_t parentIndex = nodeLayer->mParent;
aiNode* pivotNode = new aiNode();
pivotNode->mName.Set("Pivot-"+std::string(itapcNodes->second->mName.data));
mapPivot[-(itapcNodes->first+2)] = pivotNode;
itapcNodes->second->mParent = pivotNode;
if (apcNodes.find(parentIndex) != apcNodes.end()) {
pivotNode->mParent = apcNodes[parentIndex];
} else {
pivotNode->mParent = root;
}
itapcNodes->second->mTransformation.a4 = -nodeLayer->mPivot.x;
itapcNodes->second->mTransformation.b4 = -nodeLayer->mPivot.y;
itapcNodes->second->mTransformation.c4 = -nodeLayer->mPivot.z;
pivotNode->mTransformation.a4 = nodeLayer->mPivot.x;
pivotNode->mTransformation.b4 = nodeLayer->mPivot.y;
pivotNode->mTransformation.c4 = nodeLayer->mPivot.z;
}
for (std::map<uint16_t, aiNode*>::iterator itMapPivot = mapPivot.begin(); itMapPivot != mapPivot.end(); ++itMapPivot) {
apcNodes[itMapPivot->first] = itMapPivot->second;
}
apcNodes[-1] = root;
for (std::map<uint16_t,aiNode*>::iterator itMapParentNodes = apcNodes.begin(); itMapParentNodes != apcNodes.end(); ++itMapParentNodes) {
for (std::map<uint16_t,aiNode*>::iterator itMapChildNodes = apcNodes.begin(); itMapChildNodes != apcNodes.end(); ++itMapChildNodes) {
if ((itMapParentNodes->first != itMapChildNodes->first) && (itMapParentNodes->second == itMapChildNodes->second->mParent)) {
++(itMapParentNodes->second->mNumChildren);
}
}
if (itMapParentNodes->second->mNumChildren) {
itMapParentNodes->second->mChildren = new aiNode* [ itMapParentNodes->second->mNumChildren ];
uint16_t p = 0;
for (std::map<uint16_t,aiNode*>::iterator itMapChildNodes = apcNodes.begin(); itMapChildNodes != apcNodes.end(); ++itMapChildNodes) {
if ((itMapParentNodes->first != itMapChildNodes->first) && (itMapParentNodes->second == itMapChildNodes->second->mParent)) {
itMapParentNodes->second->mChildren[p++] = itMapChildNodes->second;
}
}
}
}
if (!pScene->mRootNode->mNumChildren)
throw DeadlyImportError("LWO: Unable to build a valid node graph");
if (1 == pScene->mRootNode->mNumChildren) {
aiNode* pc = pScene->mRootNode->mChildren[0];
pc->mParent = pScene->mRootNode->mChildren[0] = NULL;
delete pScene->mRootNode;
pScene->mRootNode = pc;
}
MakeLeftHandedProcess maker;
maker.Execute(pScene);
FlipWindingOrderProcess flipper;
flipper.Execute(pScene);
}
void LWOImporter::ResolveTags()
{
mMapping->resize(mTags->size(), UINT_MAX);
for (unsigned int a = 0; a < mTags->size();++a) {
const std::string& c = (*mTags)[a];
for (unsigned int i = 0; i < mSurfaces->size();++i) {
const std::string& d = (*mSurfaces)[i].mName;
if (!ASSIMP_stricmp(c,d)) {
(*mMapping)[a] = i;
break;
}
}
}
}
void LWOImporter::ResolveClips()
{
for( unsigned int i = 0; i < mClips.size();++i) {
Clip& clip = mClips[i];
if (Clip::REF == clip.type) {
if (clip.clipRef >= mClips.size()) {
DefaultLogger::get()->error("LWO2: Clip referrer index is out of range");
clip.clipRef = 0;
}
Clip& dest = mClips[clip.clipRef];
if (Clip::REF == dest.type) {
DefaultLogger::get()->error("LWO2: Clip references another clip reference");
clip.type = Clip::UNSUPPORTED;
}
else {
clip.path = dest.path;
clip.type = dest.type;
}
}
}
}
void LWOImporter::AdjustTexturePath(std::string& out)
{
if (!mIsLWO2 && ::strstr(out.c_str(), "(sequence)")) {
DefaultLogger::get()->info("LWOB: Sequence of animated texture found. It will be ignored");
out = out.substr(0,out.length()-10) + "000";
}
std::string::size_type n = out.find_first_of(':');
if (std::string::npos != n) {
out.insert(n+1,"/");
}
}
void LWOImporter::LoadLWOTags(unsigned int size)
{
const char* szCur = (const char*)mFileBuffer, *szLast = szCur;
const char* const szEnd = szLast+size;
while (szCur < szEnd)
{
if (!(*szCur))
{
const size_t len = (size_t)(szCur-szLast);
if (len)
mTags->push_back(std::string(szLast,len));
szCur += (len&0x1 ? 1 : 2);
szLast = szCur;
}
szCur++;
}
}
void LWOImporter::LoadLWOPoints(unsigned int length)
{
const size_t vertexLen = 12;
if ((length % vertexLen) != 0)
{
throw DeadlyImportError( "LWO2: Points chunk length is not multiple of vertexLen (12)");
}
unsigned int regularSize = (unsigned int)mCurLayer->mTempPoints.size() + length / 12;
if (mIsLWO2)
{
mCurLayer->mTempPoints.reserve ( regularSize + (regularSize>>2u) );
mCurLayer->mTempPoints.resize ( regularSize );
mCurLayer->mPointReferrers.reserve ( regularSize + (regularSize>>2u) );
mCurLayer->mPointReferrers.resize ( regularSize, UINT_MAX );
}
else mCurLayer->mTempPoints.resize( regularSize );
#ifndef AI_BUILD_BIG_ENDIAN
for (unsigned int i = 0; i < length>>2;++i)
ByteSwap::Swap4( mFileBuffer + (i << 2));
#endif
::memcpy(&mCurLayer->mTempPoints[0],mFileBuffer,length);
}
void LWOImporter::LoadLWO2Polygons(unsigned int length)
{
LE_NCONST uint16_t* const end = (LE_NCONST uint16_t*)(mFileBuffer+length);
const uint32_t type = GetU4();
switch (type)
{
case AI_LWO_MBAL:
DefaultLogger::get()->warn("LWO2: Encountered unsupported primitive chunk (METABALL)");
break;
case AI_LWO_CURV:
DefaultLogger::get()->warn("LWO2: Encountered unsupported primitive chunk (SPLINE)");;
break;
case AI_LWO_PTCH:
case AI_LWO_FACE:
case AI_LWO_BONE:
case AI_LWO_SUBD:
break;
default:
DefaultLogger::get()->error("LWO2: Ignoring unknown polygon type.");
break;
}
uint16_t* cursor= (uint16_t*)mFileBuffer;
unsigned int iNumFaces = 0,iNumVertices = 0;
CountVertsAndFacesLWO2(iNumVertices,iNumFaces,cursor,end);
if (iNumFaces)
{
cursor = (uint16_t*)mFileBuffer;
mCurLayer->mFaces.resize(iNumFaces,LWO::Face(type));
FaceList::iterator it = mCurLayer->mFaces.begin();
CopyFaceIndicesLWO2(it,cursor,end);
}
}
void LWOImporter::CountVertsAndFacesLWO2(unsigned int& verts, unsigned int& faces,
uint16_t*& cursor, const uint16_t* const end, unsigned int max)
{
while (cursor < end && max--)
{
uint16_t numIndices;
::memcpy(&numIndices, cursor++, 2);
AI_LSWAP2(numIndices);
numIndices &= 0x03FF;
verts += numIndices;
++faces;
for(uint16_t i = 0; i < numIndices; i++)
{
ReadVSizedIntLWO2((uint8_t*&)cursor);
}
}
}
void LWOImporter::CopyFaceIndicesLWO2(FaceList::iterator& it,
uint16_t*& cursor,
const uint16_t* const end)
{
while (cursor < end)
{
LWO::Face& face = *it++;
uint16_t numIndices;
::memcpy(&numIndices, cursor++, 2);
AI_LSWAP2(numIndices);
face.mNumIndices = numIndices & 0x03FF;
if(face.mNumIndices)
{
face.mIndices = new unsigned int[face.mNumIndices];
for(unsigned int i = 0; i < face.mNumIndices; i++)
{
face.mIndices[i] = ReadVSizedIntLWO2((uint8_t*&)cursor) + mCurLayer->mPointIDXOfs;
if(face.mIndices[i] > mCurLayer->mTempPoints.size())
{
DefaultLogger::get()->warn("LWO2: Failure evaluating face record, index is out of range");
face.mIndices[i] = (unsigned int)mCurLayer->mTempPoints.size()-1;
}
}
}
else throw DeadlyImportError("LWO2: Encountered invalid face record with zero indices");
}
}
void LWOImporter::LoadLWO2PolygonTags(unsigned int length)
{
LE_NCONST uint8_t* const end = mFileBuffer+length;
AI_LWO_VALIDATE_CHUNK_LENGTH(length,PTAG,4);
uint32_t type = GetU4();
if (type != AI_LWO_SURF && type != AI_LWO_SMGP)
return;
while (mFileBuffer < end)
{
unsigned int i = ReadVSizedIntLWO2(mFileBuffer) + mCurLayer->mFaceIDXOfs;
unsigned int j = GetU2();
if (i >= mCurLayer->mFaces.size()) {
DefaultLogger::get()->warn("LWO2: face index in PTAG is out of range");
continue;
}
switch (type) {
case AI_LWO_SURF:
mCurLayer->mFaces[i].surfaceIndex = j;
break;
case AI_LWO_SMGP:
mCurLayer->mFaces[i].smoothGroup = j;
break;
};
}
}
template <class T>
VMapEntry* FindEntry(std::vector< T >& list,const std::string& name, bool perPoly)
{
for (auto & elem : list) {
if (elem.name == name) {
if (!perPoly) {
DefaultLogger::get()->warn("LWO2: Found two VMAP sections with equal names");
}
return &elem;
}
}
list.push_back( T() );
VMapEntry* p = &list.back();
p->name = name;
return p;
}
template <class T>
inline void CreateNewEntry(T& chan, unsigned int srcIdx)
{
if (!chan.name.length())
return;
chan.abAssigned[srcIdx] = true;
chan.abAssigned.resize(chan.abAssigned.size()+1,false);
for (unsigned int a = 0; a < chan.dims;++a)
chan.rawData.push_back(chan.rawData[srcIdx*chan.dims+a]);
}
template <class T>
inline void CreateNewEntry(std::vector< T >& list, unsigned int srcIdx)
{
for (auto &elem : list) {
CreateNewEntry( elem, srcIdx );
}
}
inline void LWOImporter::DoRecursiveVMAPAssignment(VMapEntry* base, unsigned int numRead,
unsigned int idx, float* data)
{
ai_assert(NULL != data);
LWO::ReferrerList& refList = mCurLayer->mPointReferrers;
unsigned int i;
if (idx >= base->abAssigned.size()) {
throw DeadlyImportError("Bad index");
}
base->abAssigned[idx] = true;
for (i = 0; i < numRead;++i) {
base->rawData[idx*base->dims+i]= data[i];
}
if (UINT_MAX != (i = refList[idx])) {
DoRecursiveVMAPAssignment(base,numRead,i,data);
}
}
inline void AddToSingleLinkedList(ReferrerList& refList, unsigned int srcIdx, unsigned int destIdx)
{
if(UINT_MAX == refList[srcIdx]) {
refList[srcIdx] = destIdx;
return;
}
AddToSingleLinkedList(refList,refList[srcIdx],destIdx);
}
void LWOImporter::LoadLWO2VertexMap(unsigned int length, bool perPoly)
{
LE_NCONST uint8_t* const end = mFileBuffer+length;
AI_LWO_VALIDATE_CHUNK_LENGTH(length,VMAP,6);
unsigned int type = GetU4();
unsigned int dims = GetU2();
VMapEntry* base;
std::string name;
GetS0(name,length);
switch (type)
{
case AI_LWO_TXUV:
if (dims != 2) {
DefaultLogger::get()->warn("LWO2: Skipping UV channel \'"
+ name + "\' with !2 components");
return;
}
base = FindEntry(mCurLayer->mUVChannels,name,perPoly);
break;
case AI_LWO_WGHT:
case AI_LWO_MNVW:
if (dims != 1) {
DefaultLogger::get()->warn("LWO2: Skipping Weight Channel \'"
+ name + "\' with !1 components");
return;
}
base = FindEntry((type == AI_LWO_WGHT ? mCurLayer->mWeightChannels
: mCurLayer->mSWeightChannels),name,perPoly);
break;
case AI_LWO_RGB:
case AI_LWO_RGBA:
if (dims != 3 && dims != 4) {
DefaultLogger::get()->warn("LWO2: Skipping Color Map \'"
+ name + "\' with a dimension > 4 or < 3");
return;
}
base = FindEntry(mCurLayer->mVColorChannels,name,perPoly);
break;
case AI_LWO_MODO_NORM:
if (name != "vert_normals" || dims != 3 || mCurLayer->mNormals.name.length())
return;
DefaultLogger::get()->info("Processing non-standard extension: MODO VMAP.NORM.vert_normals");
mCurLayer->mNormals.name = name;
base = & mCurLayer->mNormals;
break;
case AI_LWO_PICK:
case AI_LWO_MORF:
case AI_LWO_SPOT:
return;
default:
if (name == "APS.Level") {
}
DefaultLogger::get()->warn("LWO2: Skipping unknown VMAP/VMAD channel \'" + name + "\'");
return;
};
base->Allocate((unsigned int)mCurLayer->mTempPoints.size());
type = std::min(dims,base->dims);
const unsigned int diff = (dims - type)<<2u;
LWO::FaceList& list = mCurLayer->mFaces;
LWO::PointList& pointList = mCurLayer->mTempPoints;
LWO::ReferrerList& refList = mCurLayer->mPointReferrers;
const unsigned int numPoints = (unsigned int)pointList.size();
const unsigned int numFaces = (unsigned int)list.size();
while (mFileBuffer < end) {
unsigned int idx = ReadVSizedIntLWO2(mFileBuffer) + mCurLayer->mPointIDXOfs;
if (idx >= numPoints) {
DefaultLogger::get()->warn("LWO2: Failure evaluating VMAP/VMAD entry \'" + name + "\', vertex index is out of range");
mFileBuffer += base->dims<<2u;
continue;
}
if (perPoly) {
unsigned int polyIdx = ReadVSizedIntLWO2(mFileBuffer) + mCurLayer->mFaceIDXOfs;
if (base->abAssigned[idx]) {
if (polyIdx >= numFaces) {
DefaultLogger::get()->warn("LWO2: Failure evaluating VMAD entry \'" + name + "\', polygon index is out of range");
mFileBuffer += base->dims<<2u;
continue;
}
LWO::Face& src = list[polyIdx];
bool had = false;
for (unsigned int i = 0; i < src.mNumIndices;++i) {
unsigned int srcIdx = src.mIndices[i], tmp = idx;
do {
if (tmp == srcIdx)
break;
}
while ((tmp = refList[tmp]) != UINT_MAX);
if (tmp == UINT_MAX) {
continue;
}
had = true;
refList.resize(refList.size()+1, UINT_MAX);
idx = (unsigned int)pointList.size();
src.mIndices[i] = (unsigned int)pointList.size();
AddToSingleLinkedList(refList,srcIdx,src.mIndices[i]);
pointList.push_back(pointList[srcIdx]);
CreateNewEntry(mCurLayer->mVColorChannels, srcIdx );
CreateNewEntry(mCurLayer->mUVChannels, srcIdx );
CreateNewEntry(mCurLayer->mWeightChannels, srcIdx );
CreateNewEntry(mCurLayer->mSWeightChannels, srcIdx );
CreateNewEntry(mCurLayer->mNormals, srcIdx );
}
if (!had) {
DefaultLogger::get()->warn("LWO2: Failure evaluating VMAD entry \'" + name + "\', vertex index wasn't found in that polygon");
ai_assert(had);
}
}
}
std::unique_ptr<float[]> temp(new float[type]);
for (unsigned int l = 0; l < type;++l)
temp[l] = GetF4();
DoRecursiveVMAPAssignment(base,type,idx, temp.get());
mFileBuffer += diff;
}
}
void LWOImporter::LoadLWO2Clip(unsigned int length)
{
AI_LWO_VALIDATE_CHUNK_LENGTH(length,CLIP,10);
mClips.push_back(LWO::Clip());
LWO::Clip& clip = mClips.back();
clip.idx = GetU4();
IFF::SubChunkHeader head = IFF::LoadSubChunk(mFileBuffer);
switch (head.type)
{
case AI_LWO_STIL:
AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,STIL,1);
GetS0(clip.path,head.length);
clip.type = Clip::STILL;
break;
case AI_LWO_ISEQ:
AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,ISEQ,16);
{
uint8_t digits = GetU1(); mFileBuffer++;
int16_t offset = GetU2(); mFileBuffer+=4;
int16_t start = GetU2(); mFileBuffer+=4;
std::string s;
std::ostringstream ss;
GetS0(s,head.length);
head.length -= (uint16_t)s.length()+1;
ss << s;
ss << std::setw(digits) << offset + start;
GetS0(s,head.length);
ss << s;
clip.path = ss.str();
clip.type = Clip::SEQ;
}
break;
case AI_LWO_STCC:
DefaultLogger::get()->warn("LWO2: Color shifted images are not supported");
break;
case AI_LWO_ANIM:
DefaultLogger::get()->warn("LWO2: Animated textures are not supported");
break;
case AI_LWO_XREF:
AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,XREF,4);
clip.type = Clip::REF;
clip.clipRef = GetU4();
break;
case AI_LWO_NEGA:
AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,NEGA,2);
clip.negate = (0 != GetU2());
break;
default:
DefaultLogger::get()->warn("LWO2: Encountered unknown CLIP subchunk");
}
}
void LWOImporter::LoadLWO2Envelope(unsigned int length)
{
LE_NCONST uint8_t* const end = mFileBuffer + length;
AI_LWO_VALIDATE_CHUNK_LENGTH(length,ENVL,4);
mEnvelopes.push_back(LWO::Envelope());
LWO::Envelope& envelope = mEnvelopes.back();
envelope.index = ReadVSizedIntLWO2(mFileBuffer);
if (mIsLXOB)
{
uint32_t extra = GetU4();
if (extra)
{
mFileBuffer -= 4;
}
}
while (true)
{
if (mFileBuffer + 6 >= end)break;
LE_NCONST IFF::SubChunkHeader head = IFF::LoadSubChunk(mFileBuffer);
if (mFileBuffer + head.length > end)
throw DeadlyImportError("LWO2: Invalid envelope chunk length");
uint8_t* const next = mFileBuffer+head.length;
switch (head.type)
{
case AI_LWO_TYPE:
AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,TYPE,2);
mFileBuffer++;
envelope.type = (LWO::EnvelopeType)*mFileBuffer;
++mFileBuffer;
break;
case AI_LWO_PRE:
AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,PRE,2);
envelope.pre = (LWO::PrePostBehaviour)GetU2();
break;
case AI_LWO_POST:
AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,POST,2);
envelope.post = (LWO::PrePostBehaviour)GetU2();
break;
case AI_LWO_KEY:
{
AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,KEY,8);
envelope.keys.push_back(LWO::Key());
LWO::Key& key = envelope.keys.back();
key.time = GetF4();
key.value = GetF4();
break;
}
case AI_LWO_SPAN:
{
AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,SPAN,4);
if (envelope.keys.size()<2)
DefaultLogger::get()->warn("LWO2: Unexpected SPAN chunk");
else {
LWO::Key& key = envelope.keys.back();
switch (GetU4())
{
case AI_LWO_STEP:
key.inter = LWO::IT_STEP;break;
case AI_LWO_LINE:
key.inter = LWO::IT_LINE;break;
case AI_LWO_TCB:
key.inter = LWO::IT_TCB;break;
case AI_LWO_HERM:
key.inter = LWO::IT_HERM;break;
case AI_LWO_BEZI:
key.inter = LWO::IT_BEZI;break;
case AI_LWO_BEZ2:
key.inter = LWO::IT_BEZ2;break;
default:
DefaultLogger::get()->warn("LWO2: Unknown interval interpolation mode");
};
}
break;
}
default:
DefaultLogger::get()->warn("LWO2: Encountered unknown ENVL subchunk");
}
mFileBuffer = next;
}
}
void LWOImporter::LoadLWO2File()
{
bool skip = false;
LE_NCONST uint8_t* const end = mFileBuffer + fileSize;
while (true)
{
if (mFileBuffer + sizeof(IFF::ChunkHeader) > end)break;
const IFF::ChunkHeader head = IFF::LoadChunk(mFileBuffer);
if (mFileBuffer + head.length > end)
{
throw DeadlyImportError("LWO2: Chunk length points behind the file");
break;
}
uint8_t* const next = mFileBuffer+head.length;
unsigned int iUnnamed = 0;
if(!head.length) {
mFileBuffer = next;
continue;
}
switch (head.type)
{
case AI_LWO_LAYR:
{
mLayers->push_back ( LWO::Layer() );
LWO::Layer& layer = mLayers->back();
mCurLayer = &layer;
AI_LWO_VALIDATE_CHUNK_LENGTH(head.length,LAYR,16);
layer.mIndex = GetU2();
if (UINT_MAX != configLayerIndex && (configLayerIndex-1) != layer.mIndex) {
skip = true;
}
else skip = false;
mFileBuffer += 2;
mCurLayer->mPivot.x = GetF4();
mCurLayer->mPivot.y = GetF4();
mCurLayer->mPivot.z = GetF4();
GetS0(layer.mName,head.length-16);
if (layer.mName.empty()) {
char buffer[128]; ::ai_snprintf(buffer, 128, "Layer_%i", iUnnamed++);
layer.mName = buffer;
}
if (configLayerName.length() && configLayerName != layer.mName) {
skip = true;
}
else hasNamedLayer = true;
if (mFileBuffer + 2 <= next)
layer.mParent = GetU2();
else layer.mParent = -1;
layer.skip = skip;
break;
}
case AI_LWO_PNTS:
{
if (skip)
break;
unsigned int old = (unsigned int)mCurLayer->mTempPoints.size();
LoadLWOPoints(head.length);
mCurLayer->mPointIDXOfs = old;
break;
}
case AI_LWO_VMAD:
if (mCurLayer->mFaces.empty())
{
DefaultLogger::get()->warn("LWO2: Unexpected VMAD chunk");
break;
}
case AI_LWO_VMAP:
{
if (skip)
break;
if (mCurLayer->mTempPoints.empty())
DefaultLogger::get()->warn("LWO2: Unexpected VMAP chunk");
else LoadLWO2VertexMap(head.length,head.type == AI_LWO_VMAD);
break;
}
case AI_LWO_POLS:
{
if (skip)
break;
unsigned int old = (unsigned int)mCurLayer->mFaces.size();
LoadLWO2Polygons(head.length);
mCurLayer->mFaceIDXOfs = old;
break;
}
case AI_LWO_PTAG:
{
if (skip)
break;
if (mCurLayer->mFaces.empty())
DefaultLogger::get()->warn("LWO2: Unexpected PTAG");
else LoadLWO2PolygonTags(head.length);
break;
}
case AI_LWO_TAGS:
{
if (!mTags->empty())
DefaultLogger::get()->warn("LWO2: SRFS chunk encountered twice");
else LoadLWOTags(head.length);
break;
}
case AI_LWO_SURF:
{
LoadLWO2Surface(head.length);
break;
}
case AI_LWO_CLIP:
{
LoadLWO2Clip(head.length);
break;
}
case AI_LWO_ENVL:
{
LoadLWO2Envelope(head.length);
break;
}
}
mFileBuffer = next;
}
}
#endif