diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index cef3fd4c7..510ae8e7f 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -13,8 +13,7 @@ jobs: strategy: fail-fast: false matrix: - #name: [ubuntu-latest-g++, macos-latest-clang++, windows-latest-cl.exe, ubuntu-latest-clang++, ubuntu-gcc-hunter, macos-clang-hunter, windows-msvc-hunter] - name: [ubuntu-latest-g++, macos-latest-clang++, windows-latest-cl.exe, ubuntu-latest-clang++, ubuntu-gcc-hunter, macos-clang-hunter] + name: [ubuntu-latest-g++, macos-latest-clang++, windows-latest-cl.exe, ubuntu-latest-clang++, ubuntu-gcc-hunter, macos-clang-hunter, windows-msvc-hunter] # For Windows msvc, for Linux and macOS let's use the clang compiler, use gcc for Linux. include: - name: windows-latest-cl.exe @@ -39,9 +38,9 @@ jobs: - name: macos-clang-hunter os: macos-latest toolchain: ninja-clang-cxx17-fpic - #- name: windows-msvc-hunter - # os: windows-latest - # toolchain: ninja-vs-win64-cxx17 + - name: windows-msvc-hunter + os: windows-latest + toolchain: ninja-vs-win64-cxx17 steps: - uses: actions/checkout@v2 diff --git a/CMakeLists.txt b/CMakeLists.txt index 695f90c31..5cbbfef2c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -49,8 +49,8 @@ option(ASSIMP_HUNTER_ENABLED "Enable Hunter package manager support" OFF) IF(ASSIMP_HUNTER_ENABLED) include("cmake-modules/HunterGate.cmake") HunterGate( - URL "https://github.com/cpp-pm/hunter/archive/v0.23.311.tar.gz" - SHA1 "1a82b9b73055879181cb1466b2ab5d48ee8ae410" + URL "https://github.com/cpp-pm/hunter/archive/v0.24.0.tar.gz" + SHA1 "a3d7f4372b1dcd52faa6ff4a3bd5358e1d0e5efd" ) add_definitions(-DASSIMP_USE_HUNTER) diff --git a/code/AssetLib/LWO/LWOFileData.h b/code/AssetLib/LWO/LWOFileData.h index f477f582f..59d10eca1 100644 --- a/code/AssetLib/LWO/LWOFileData.h +++ b/code/AssetLib/LWO/LWOFileData.h @@ -64,6 +64,7 @@ namespace LWO { #define AI_LWO_FOURCC_LWOB AI_IFF_FOURCC('L', 'W', 'O', 'B') #define AI_LWO_FOURCC_LWO2 AI_IFF_FOURCC('L', 'W', 'O', '2') +#define AI_LWO_FOURCC_LWO3 AI_IFF_FOURCC('L', 'W', 'O', '3') #define AI_LWO_FOURCC_LXOB AI_IFF_FOURCC('L', 'X', 'O', 'B') // chunks specific to the LWOB format @@ -248,6 +249,57 @@ namespace LWO { #define AI_LWO_SPOT AI_IFF_FOURCC('S', 'P', 'O', 'T') #define AI_LWO_PICK AI_IFF_FOURCC('P', 'I', 'C', 'K') +// Surface Part +#define AI_LWO_NODS AI_IFF_FOURCC('N', 'O', 'D', 'S') +#define AI_LWO_NNDS AI_IFF_FOURCC('N', 'N', 'D', 'S') +#define AI_LWO_NTAG AI_IFF_FOURCC('N', 'T', 'A', 'G') +#define AI_LWO_NRNM AI_IFF_FOURCC('N', 'R', 'N', 'M') +#define AI_LWO_NRME AI_IFF_FOURCC('N', 'R', 'M', 'E') +#define AI_LWO_NDTA AI_IFF_FOURCC('N', 'D', 'T', 'A') +#define AI_LWO_ATTR AI_IFF_FOURCC('A', 'T', 'T', 'R') +#define AI_LWO_VERS AI_IFF_FOURCC('V', 'E', 'R', 'S') +#define AI_LWO_ENUM AI_IFF_FOURCC('E', 'N', 'U', 'M') +#define AI_LWO_ENTR AI_IFF_FOURCC('E', 'N', 'T', 'R') +#define AI_LWO_NAME AI_IFF_FOURCC('N', 'A', 'M', 'E') +#define AI_LWO_FLAG AI_IFF_FOURCC('F', 'L', 'A', 'G') +#define AI_LWO_TAG AI_IFF_FOURCC('T', 'A', 'G', ' ') +#define AI_LWO_VALU AI_IFF_FOURCC('V', 'A', 'L', 'U') +#define AI_LWO_IBGC AI_IFF_FOURCC('I', 'B', 'G', 'C') +#define AI_LWO_IOPC AI_IFF_FOURCC('I', 'O', 'P', 'C') +#define AI_LWO_IIMG AI_IFF_FOURCC('I', 'I', 'M', 'G') +#define AI_LWO_TXTR AI_IFF_FOURCC('T', 'X', 'T', 'R') + +#define AI_LWO_IFAL AI_IFF_FOURCC('I', 'F', 'A', 'L') +#define AI_LWO_ISCL AI_IFF_FOURCC('I', 'S', 'C', 'L') +#define AI_LWO_IPOS AI_IFF_FOURCC('I', 'P', 'O', 'S') +#define AI_LWO_IROT AI_IFF_FOURCC('I', 'R', 'O', 'T') +#define AI_LWO_IBMP AI_IFF_FOURCC('I', 'B', 'M', 'P') +#define AI_LWO_IUTD AI_IFF_FOURCC('I', 'U', 'T', 'D') +#define AI_LWO_IVTD AI_IFF_FOURCC('I', 'V', 'T', 'D') + +#define AI_LWO_IPIX AI_IFF_FOURCC('I', 'P', 'I', 'X') +#define AI_LWO_IMIP AI_IFF_FOURCC('I', 'M', 'I', 'P') +#define AI_LWO_IMOD AI_IFF_FOURCC('I', 'M', 'O', 'D') +#define AI_LWO_AMOD AI_IFF_FOURCC('A', 'M', 'O', 'D') +#define AI_LWO_IINV AI_IFF_FOURCC('I', 'I', 'N', 'V') +#define AI_LWO_INCR AI_IFF_FOURCC('I', 'N', 'C', 'R') +#define AI_LWO_IAXS AI_IFF_FOURCC('I', 'A', 'X', 'S') +#define AI_LWO_IFOT AI_IFF_FOURCC('I', 'F', 'O', 'T') +#define AI_LWO_ITIM AI_IFF_FOURCC('I', 'T', 'I', 'M') +#define AI_LWO_IWRL AI_IFF_FOURCC('I', 'W', 'R', 'L') +#define AI_LWO_IUTI AI_IFF_FOURCC('I', 'U', 'T', 'I') +#define AI_LWO_IINX AI_IFF_FOURCC('I', 'I', 'N', 'X') +#define AI_LWO_IINY AI_IFF_FOURCC('I', 'I', 'N', 'Y') +#define AI_LWO_IINZ AI_IFF_FOURCC('I', 'I', 'N', 'Z') +#define AI_LWO_IREF AI_IFF_FOURCC('I', 'R', 'E', 'F') +#define AI_LWO_IMST AI_IFF_FOURCC('I', 'M', 'S', 'T') +#define AI_LWO_VPVL AI_IFF_FOURCC('V', 'P', 'V', 'L') +#define AI_LWO_VPRM AI_IFF_FOURCC('V', 'P', 'R', 'M') +#define AI_LWO_IMAP AI_IFF_FOURCC('I', 'M', 'A', 'P') +#define AI_LWO_IUVI AI_IFF_FOURCC('I', 'U', 'V', 'I') +#define AI_LWO_IUTL AI_IFF_FOURCC('I', 'U', 'T', 'L') +#define AI_LWO_IVTL AI_IFF_FOURCC('I', 'V', 'T', 'L') + // MODO extension - per-vertex normal vectors #define AI_LWO_MODO_NORM AI_IFF_FOURCC('N', 'O', 'R', 'M') @@ -555,6 +607,31 @@ struct Surface { float mAdditiveTransparency; }; +// --------------------------------------------------------------------------- +/** \brief Data structure for a LWO node + */ +struct Node { + // Name of node + std::string mName; + + // RefName of node + std::string mRefName; + + // Ref FileName + std::string fileName; +}; + +struct NodeAttribute { + //! Color of the surface + aiColor3D mColor; + + //! true for two-sided materials + bool bDoubleSided; + + //! Various material parameters + float mDiffuseValue, mSpecularValue, mTransparency, mGlossiness, mLuminosity, mColorHighlights; +}; + // --------------------------------------------------------------------------- #define AI_LWO_VALIDATE_CHUNK_LENGTH(length, name, size) \ if (length < size) { \ diff --git a/code/AssetLib/LWO/LWOLoader.cpp b/code/AssetLib/LWO/LWOLoader.cpp index 7410fb6cf..c4e85bc50 100644 --- a/code/AssetLib/LWO/LWOLoader.cpp +++ b/code/AssetLib/LWO/LWOLoader.cpp @@ -83,6 +83,7 @@ static const aiImporterDesc desc = { LWOImporter::LWOImporter() : mIsLWO2(), mIsLXOB(), + mIsLWO3(), mLayers(), mCurLayer(), mTags(), @@ -182,16 +183,19 @@ void LWOImporter::InternReadFile(const std::string &pFile, mCurLayer->mIndex = (uint16_t) -1; // old lightwave file format (prior to v6) + mIsLWO2 = false; + mIsLWO3 = false; + mIsLXOB = false; + if (AI_LWO_FOURCC_LWOB == fileType) { ASSIMP_LOG_INFO("LWO file format: LWOB (<= LightWave 5.5)"); - mIsLWO2 = false; - mIsLXOB = false; LoadLWOBFile(); } else if (AI_LWO_FOURCC_LWO2 == fileType) { // New lightwave format - mIsLXOB = false; ASSIMP_LOG_INFO("LWO file format: LWO2 (>= LightWave 6)"); + } else if ( AI_LWO_FOURCC_LWO3 == fileType ) { + ASSIMP_LOG_INFO("LWO file format: LWO3 (>= LightWave 2018)"); } else if (AI_LWO_FOURCC_LXOB == fileType) { // MODO file format mIsLXOB = true; @@ -207,8 +211,13 @@ void LWOImporter::InternReadFile(const std::string &pFile, throw DeadlyImportError("Unknown LWO sub format: ", szBuff); } - if (AI_LWO_FOURCC_LWOB != fileType) { - mIsLWO2 = true; + if (AI_LWO_FOURCC_LWOB != fileType) { // + if( AI_LWO_FOURCC_LWO3 == fileType ) { + mIsLWO3 = true; + } else { + mIsLWO2 = true; + } + LoadLWO2File(); // The newer lightwave format allows the user to configure the @@ -442,6 +451,7 @@ void LWOImporter::InternReadFile(const std::string &pFile, // The RemoveRedundantMaterials step will clean this up later 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; @@ -687,7 +697,7 @@ void LWOImporter::ResolveClips() { // ------------------------------------------------------------------------------------------------ void LWOImporter::AdjustTexturePath(std::string &out) { // --- this function is used for both LWO2 and LWOB - if (!mIsLWO2 && ::strstr(out.c_str(), "(sequence)")) { + if (!mIsLWO2 && !mIsLWO3 && ::strstr(out.c_str(), "(sequence)")) { // remove the (sequence) and append 000 ASSIMP_LOG_INFO("LWOB: Sequence of animated texture found. It will be ignored"); @@ -730,7 +740,7 @@ void LWOImporter::LoadLWOPoints(unsigned int length) { throw DeadlyImportError("LWO2: Points chunk length is not multiple of vertexLen (12)"); } unsigned int regularSize = (unsigned int)mCurLayer->mTempPoints.size() + length / 12; - if (mIsLWO2) { + if (mIsLWO2 || mIsLWO3) { mCurLayer->mTempPoints.reserve(regularSize + (regularSize >> 2u)); mCurLayer->mTempPoints.resize(regularSize); @@ -1155,6 +1165,76 @@ void LWOImporter::LoadLWO2Clip(unsigned int length) { } } +void LWOImporter::LoadLWO3Clip(unsigned int length) { + AI_LWO_VALIDATE_CHUNK_LENGTH(length, CLIP, 12); + + mClips.push_back(LWO::Clip()); + LWO::Clip &clip = mClips.back(); + + // first - get the index of the clip + clip.idx = GetU4(); + + IFF::ChunkHeader head = IFF::LoadChunk(mFileBuffer); + switch (head.type) { + case AI_LWO_STIL: + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, STIL, 1); + + // "Normal" texture + GetS0(clip.path, head.length); + clip.type = Clip::STILL; + break; + + case AI_LWO_ISEQ: + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, ISEQ, 16); + // Image sequence. We'll later take the first. + { + 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: + ASSIMP_LOG_WARN("LWO3: Color shifted images are not supported"); + break; + + case AI_LWO_ANIM: + ASSIMP_LOG_WARN("LWO3: Animated textures are not supported"); + break; + + case AI_LWO_XREF: + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, XREF, 4); + + // Just a cross-reference to another CLIp + 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: + ASSIMP_LOG_WARN("LWO3: Encountered unknown CLIP sub-chunk"); + } +} + // ------------------------------------------------------------------------------------------------ // Load envelope description void LWOImporter::LoadLWO2Envelope(unsigned int length) { @@ -1265,6 +1345,104 @@ void LWOImporter::LoadLWO2Envelope(unsigned int length) { } } +void LWOImporter::LoadLWO3Envelope(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(); + + // Get the index of the envelope + envelope.index = ReadVSizedIntLWO2(mFileBuffer); + + // ... and read all blocks + while (true) { + if (mFileBuffer + 8 >= end) break; + LE_NCONST IFF::ChunkHeader head = IFF::LoadChunk(mFileBuffer); + + if (mFileBuffer + head.length > end) + throw DeadlyImportError("LWO3: Invalid envelope chunk length"); + + uint8_t *const next = mFileBuffer + head.length; + switch (head.type) { + // Type & representation of the envelope + case AI_LWO_TYPE: + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, TYPE, 4); + mFileBuffer++; // skip user format + + // Determine type of envelope + envelope.type = (LWO::EnvelopeType)*mFileBuffer; + ++mFileBuffer; + break; + + // precondition + case AI_LWO_PRE: + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, PRE, 4); + envelope.pre = (LWO::PrePostBehaviour)GetU2(); + break; + + // postcondition + case AI_LWO_POST: + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, POST, 4); + envelope.post = (LWO::PrePostBehaviour)GetU2(); + break; + + // keyframe + case AI_LWO_KEY: { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, KEY, 10); + + envelope.keys.push_back(LWO::Key()); + LWO::Key &key = envelope.keys.back(); + + key.time = GetF4(); + key.value = GetF4(); + break; + } + + // interval interpolation + case AI_LWO_SPAN: { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, SPAN, 6); + if (envelope.keys.size() < 2) + ASSIMP_LOG_WARN("LWO3: 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: + ASSIMP_LOG_WARN("LWO3: Unknown interval interpolation mode"); + }; + + // todo ... read params + } + break; + } + + default: + ASSIMP_LOG_WARN("LWO3: Encountered unknown ENVL subchunk"); + break; + } + // regardless how much we did actually read, go to the next chunk + mFileBuffer = next; + } +} + // ------------------------------------------------------------------------------------------------ // Load file - master function void LWOImporter::LoadLWO2File() { @@ -1272,16 +1450,25 @@ void LWOImporter::LoadLWO2File() { LE_NCONST uint8_t *const end = mFileBuffer + fileSize; unsigned int iUnnamed = 0; + while (true) { if (mFileBuffer + sizeof(IFF::ChunkHeader) > end) break; - const IFF::ChunkHeader head = IFF::LoadChunk(mFileBuffer); + + IFF::ChunkHeader head = IFF::LoadChunk(mFileBuffer); + + int bufOffset = 0; + if( head.type == AI_IFF_FOURCC_FORM ) { // not chunk, it's a form + mFileBuffer -= 8; + head = IFF::LoadForm(mFileBuffer); + bufOffset = 4; + } if (mFileBuffer + head.length > end) { throw DeadlyImportError("LWO2: Chunk length points behind the file"); break; } uint8_t *const next = mFileBuffer + head.length; - + mFileBuffer += bufOffset; if (!head.length) { mFileBuffer = next; continue; @@ -1337,7 +1524,6 @@ void LWOImporter::LoadLWO2File() { break; } - // vertex list case AI_LWO_PNTS: { if (skip) @@ -1399,19 +1585,29 @@ void LWOImporter::LoadLWO2File() { // surface chunk case AI_LWO_SURF: { - LoadLWO2Surface(head.length); + if( mIsLWO3 ) + LoadLWO3Surface(head.length); + else + LoadLWO2Surface(head.length); + break; } // clip chunk case AI_LWO_CLIP: { - LoadLWO2Clip(head.length); + if( mIsLWO3 ) + LoadLWO3Clip(head.length); + else + LoadLWO2Clip(head.length); break; } // envelope chunk case AI_LWO_ENVL: { - LoadLWO2Envelope(head.length); + if( mIsLWO3 ) + LoadLWO3Envelope(head.length); + else + LoadLWO2Envelope(head.length); break; } } diff --git a/code/AssetLib/LWO/LWOLoader.h b/code/AssetLib/LWO/LWOLoader.h index f3add5323..9e116a3cc 100644 --- a/code/AssetLib/LWO/LWOLoader.h +++ b/code/AssetLib/LWO/LWOLoader.h @@ -116,6 +116,8 @@ private: */ inline void GetS0(std::string &out, unsigned int max); inline float GetF4(); + inline float GetF8(); + inline uint64_t GetU8(); inline uint32_t GetU4(); inline uint16_t GetU2(); inline uint8_t GetU1(); @@ -131,6 +133,7 @@ private: * @param size Maximum size to be read, in bytes. */ void LoadLWO2Surface(unsigned int size); + void LoadLWO3Surface(unsigned int size); // ------------------------------------------------------------------- /** Loads a texture block from a LWO2 file. @@ -197,12 +200,23 @@ private: * @param length Size of the chunk */ void LoadLWO2Clip(unsigned int length); + void LoadLWO3Clip(unsigned int length); // ------------------------------------------------------------------- /** Load an envelope from an EVL chunk * @param length Size of the chunk */ void LoadLWO2Envelope(unsigned int length); + void LoadLWO3Envelope(unsigned int length); + + // ------------------------------------------------------------------- + /** Load an nodal blocks from surface form + * @param length Size of the chunk + */ + void LoadNodalBlocks(unsigned int length); + void LoadNodes(unsigned int length); + void LoadNodeTag(unsigned int length); + void LoadNodeData(unsigned int length); // ------------------------------------------------------------------- /** Count vertices and faces in a LWOB/LWO2 file @@ -347,6 +361,8 @@ protected: /** true if the file is a LXOB file*/ bool mIsLXOB; + bool mIsLWO3; + /** Temporary list of layers from the file */ LayerList *mLayers; @@ -400,6 +416,22 @@ inline float LWOImporter::GetF4() { return f; } +inline float LWOImporter::GetF8() { + double f; + ::memcpy(&f, mFileBuffer, 8); + mFileBuffer += 8; + AI_LSWAP8(f); + return (float)f; +} + +inline uint64_t LWOImporter::GetU8() { + uint64_t f; + ::memcpy(&f, mFileBuffer, 8); + mFileBuffer += 8; + AI_LSWAP8(f); + return f; +} + // ------------------------------------------------------------------------------------------------ inline uint32_t LWOImporter::GetU4() { uint32_t f; diff --git a/code/AssetLib/LWO/LWOMaterial.cpp b/code/AssetLib/LWO/LWOMaterial.cpp index b6f0bcce9..4728706db 100644 --- a/code/AssetLib/LWO/LWOMaterial.cpp +++ b/code/AssetLib/LWO/LWOMaterial.cpp @@ -159,7 +159,7 @@ bool LWOImporter::HandleTextures(aiMaterial *pcMat, const TextureList &in, aiTex // The older LWOB format does not use indirect references to clips. // The file name of a texture is directly specified in the tex chunk. - if (mIsLWO2) { + if (mIsLWO2 || mIsLWO3) { // find the corresponding clip (take the last one if multiple // share the same index) ClipList::iterator end = mClips.end(), candidate = end; @@ -270,7 +270,7 @@ void LWOImporter::ConvertMaterial(const LWO::Surface &surf, aiMaterial *pcMat) { aiShadingMode m; if (surf.mSpecularValue && surf.mGlossiness) { float fGloss; - if (mIsLWO2) { + if (mIsLWO2 || mIsLWO3) { fGloss = std::pow(surf.mGlossiness * ai_real(10.0) + ai_real(2.0), ai_real(2.0)); } else { if (16.0 >= surf.mGlossiness) @@ -688,6 +688,252 @@ void LWOImporter::LoadLWO2ShaderBlock(LE_NCONST IFF::SubChunkHeader * /*head*/, surf.mShaders.push_back(shader); } +// ------------------------------------------------------------------------------------------------ +void LWOImporter::LoadNodalBlocks(unsigned int size) { + LE_NCONST uint8_t *const end = mFileBuffer + size; + + while (true) { + if (mFileBuffer + 8 >= end) + break; + + IFF::ChunkHeader head = IFF::LoadChunk(mFileBuffer); + int bufOffset = 0; + if (head.type == AI_IFF_FOURCC_FORM) { // not chunk, it's a form + mFileBuffer -= 8; + head = IFF::LoadForm(mFileBuffer); + bufOffset = 4; + } + + if (mFileBuffer + head.length > end) { + throw DeadlyImportError("LWO3: cannot read length; LoadNodalBlocks"); + } + int node_idx = 0; + uint8_t *const next = mFileBuffer + head.length; + mFileBuffer += bufOffset; + switch (head.type) { + case AI_LWO_NNDS: + node_idx++; + LoadNodes(head.length); + break; + } + + mFileBuffer = next; + } +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::LoadNodes(unsigned int size) { + LE_NCONST uint8_t *const end = mFileBuffer + size; + + while (true) { + if (mFileBuffer + 8 >= end) + break; + + IFF::ChunkHeader head = IFF::LoadChunk(mFileBuffer); + int bufOffset = 0; + if (head.type == AI_IFF_FOURCC_FORM) { // not chunk, it's a form + mFileBuffer -= 8; + head = IFF::LoadForm(mFileBuffer); + bufOffset = 4; + } + + if (mFileBuffer + head.length > end) { + throw DeadlyImportError("LWO3: cannot read length; LoadNodes"); + } + + uint8_t *const next = mFileBuffer + head.length; + mFileBuffer += bufOffset; + switch (head.type) { + case AI_LWO_NTAG: + LoadNodeTag(head.length); + break; + } + + mFileBuffer = next; + } +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::LoadNodeTag(unsigned int size) { + LE_NCONST uint8_t *const end = mFileBuffer + size; + + while (true) { + if (mFileBuffer + 8 >= end) + break; + + IFF::ChunkHeader head = IFF::LoadChunk(mFileBuffer); + int bufOffset = 0; + if (head.type == AI_IFF_FOURCC_FORM) { // not chunk, it's a form + mFileBuffer -= 8; + head = IFF::LoadForm(mFileBuffer); + bufOffset = 4; + } + + if (mFileBuffer + head.length > end) { + throw DeadlyImportError("LWO3: cannot read length; LoadNodeTag"); + } + + uint8_t *const next = mFileBuffer + head.length; + mFileBuffer += bufOffset; + + switch (head.type) { + case AI_LWO_NDTA: + LoadNodeData(head.length); + break; + } + + mFileBuffer = next; + } +} + +// ------------------------------------------------------------------------------------------------ +void LWOImporter::LoadNodeData(unsigned int size) { + LE_NCONST uint8_t *const end = mFileBuffer + size; + + LWO::Surface &surf = mSurfaces->back(); + + while (true) { + if (mFileBuffer + 8 >= end) + break; + + IFF::ChunkHeader head = IFF::LoadChunk(mFileBuffer); + int bufOffset = 0; + if (head.type == AI_IFF_FOURCC_FORM) { // not chunk, it's a form + mFileBuffer -= 8; + head = IFF::LoadForm(mFileBuffer); + bufOffset = 4; + } + + if (mFileBuffer + head.length > end) { + throw DeadlyImportError("LWO3: INVALID LENGTH; LoadNodeData"); + } + + uint8_t *const next = mFileBuffer + head.length; + mFileBuffer += bufOffset; + switch (head.type) { + case AI_LWO_VERS: + case AI_LWO_ENUM: + case AI_LWO_IBGC: + case AI_LWO_IOPC: + case AI_LWO_IIMG: + case AI_LWO_TXTR: + case AI_LWO_IFAL: + case AI_LWO_ISCL: + case AI_LWO_IPOS: + case AI_LWO_IROT: + case AI_LWO_IBMP: + case AI_LWO_IUTD: + case AI_LWO_IVTD: + + case AI_LWO_IPIX: + case AI_LWO_IMIP: + case AI_LWO_IMOD: + case AI_LWO_AMOD: + case AI_LWO_IINV: + case AI_LWO_INCR: + case AI_LWO_IAXS: + case AI_LWO_IFOT: + case AI_LWO_ITIM: + case AI_LWO_IWRL: + case AI_LWO_IUTI: + case AI_LWO_IUVI: + case AI_LWO_IINX: + case AI_LWO_IINY: + case AI_LWO_IINZ: + case AI_LWO_IREF: + case AI_LWO_IMST: + case AI_LWO_IMAP: + case AI_LWO_IUTL: + case AI_LWO_IVTL: + case AI_LWO_VPVL: + case AI_LWO_VPRM: + mFileBuffer = next; + break; + case AI_LWO_ENTR: + std::string attrName; + + while (true) { + if (mFileBuffer + 8 >= next) + break; + + IFF::ChunkHeader head1 = IFF::LoadChunk(mFileBuffer); + int bufOffset1 = 0; + if (head1.type == AI_IFF_FOURCC_FORM) { // not chunk, it's a form + mFileBuffer -= 8; + head1 = IFF::LoadForm(mFileBuffer); + bufOffset1 = 4; + } + + if (mFileBuffer + head1.length > end) { + throw DeadlyImportError("LWO3: cannot read length;"); + } + uint8_t *const next1 = mFileBuffer + head1.length; + mFileBuffer += bufOffset1; + + switch (head1.type) { + case AI_LWO_FLAG: + case AI_LWO_TAG: + mFileBuffer = next1; + break; + case AI_LWO_NAME: + GetS0(attrName, head1.length); + break; + case AI_LWO_VALU: + mFileBuffer += 8; + + std::string valueType; + GetS0(valueType, 8); + + if (valueType == "int") { + static_cast(GetU4()); + } else if (valueType == "double") { + static_cast(GetU8()); + } else if (valueType == "vparam") { + mFileBuffer += 24; + + float value = GetF8(); + if (attrName == "Diffuse") { + surf.mDiffuseValue = value; + } else if (attrName == "Specular") { + surf.mSpecularValue = value; + } else if (attrName == "Transparency") { + surf.mTransparency = value; + } else if (attrName == "Glossiness") { + surf.mGlossiness = value; + } else if (attrName == "Luminosity") { + surf.mLuminosity = value; + } else if (attrName == "Color Highlight") { + surf.mColorHighlights = value; + } else if (attrName == "Refraction Index") { + surf.mIOR = value; + } else if (attrName == "Bump Height") { + surf.mBumpIntensity = value; + } + } else if (valueType == "vparam3") { + mFileBuffer += 24; + + float value1, value2, value3; + value1 = GetF8(); + value2 = GetF8(); + value3 = GetF8(); + + if (attrName == "Color") { + surf.mColor.r = value1; + surf.mColor.g = value2; + surf.mColor.b = value3; + } + } + + mFileBuffer = next1; + break; + } + } + + break; + } + } +} + // ------------------------------------------------------------------------------------------------ void LWOImporter::LoadLWO2Surface(unsigned int size) { LE_NCONST uint8_t *const end = mFileBuffer + size; @@ -841,4 +1087,69 @@ void LWOImporter::LoadLWO2Surface(unsigned int size) { } } +void LWOImporter::LoadLWO3Surface(unsigned int size) { + mFileBuffer += 8; + LE_NCONST uint8_t *const end = mFileBuffer + size - 12; + + mSurfaces->push_back(LWO::Surface()); + LWO::Surface &surf = mSurfaces->back(); + + GetS0(surf.mName, size); + + // check whether this surface was derived from any other surface + std::string derived; + GetS0(derived, (unsigned int)(end - mFileBuffer)); + if (derived.length()) { + // yes, find this surface + for (SurfaceList::iterator it = mSurfaces->begin(), itEnd = mSurfaces->end() - 1; it != itEnd; ++it) { + if ((*it).mName == derived) { + // we have it ... + surf = *it; + derived.clear(); + break; + } + } + if (derived.size()) { + ASSIMP_LOG_WARN("LWO3: Unable to find source surface: ", derived); + } + } + while (true) { + if (mFileBuffer + 8 >= end) + break; + + IFF::ChunkHeader head = IFF::LoadChunk(mFileBuffer); + int bufOffset = 0; + if( head.type == AI_IFF_FOURCC_FORM ) { // not chunk, it's a form + mFileBuffer -= 8; + head = IFF::LoadForm(mFileBuffer); + bufOffset = 4; + } + + if (mFileBuffer + head.length > end) { + throw DeadlyImportError("LWO3: cannot read length; LoadLWO3Surface"); + } + + uint8_t *const next = mFileBuffer + head.length; + mFileBuffer += bufOffset; + switch (head.type) { + case AI_LWO_NODS: + LoadNodalBlocks(head.length); + break; + // polygon sidedness + case AI_LWO_SIDE: { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, SIDE, 2); + surf.bDoubleSided = (3 == GetU2()); + break; + } + // maximum smoothing angle + case AI_LWO_SMAN: { + AI_LWO_VALIDATE_CHUNK_LENGTH(head.length, SMAN, 4); + surf.mMaximumSmoothAngle = std::fabs(GetF4()); + break; + } + } + mFileBuffer = next; + } +} + #endif // !! ASSIMP_BUILD_NO_X_IMPORTER diff --git a/code/Common/IFF.h b/code/Common/IFF.h index 91d7d4828..cf07f9833 100644 --- a/code/Common/IFF.h +++ b/code/Common/IFF.h @@ -35,6 +35,17 @@ struct SubChunkHeader uint16_t length; }; +///////////////////////////////////////////////////////////////////////////////// +//! Describes an IFF form header +///////////////////////////////////////////////////////////////////////////////// +struct FormHeader +{ + //! Length of the chunk data, in bytes + uint32_t length; + + //! Type of the chunk header - FourCC + uint32_t type; +}; #define AI_IFF_FOURCC(a,b,c,d) ((uint32_t) (((uint8_t)a << 24u) | \ ((uint8_t)b << 16u) | ((uint8_t)c << 8u) | ((uint8_t)d))) @@ -77,6 +88,24 @@ inline SubChunkHeader LoadSubChunk(uint8_t*& outFile) return head; } +///////////////////////////////////////////////////////////////////////////////// +//! Load a chunk header +//! @param outFile Pointer to the file data - points to the chunk data afterwards +//! @return Copy of the chunk header +///////////////////////////////////////////////////////////////////////////////// +inline ChunkHeader LoadForm(uint8_t*& outFile) +{ + ChunkHeader head; + outFile += 4; + ::memcpy(&head.length, outFile, 4); + outFile += 4; + ::memcpy(&head.type, outFile, 4); + + AI_LSWAP4(head.length); + AI_LSWAP4(head.type); + return head; +} + ///////////////////////////////////////////////////////////////////////////////// //! Read the file header and return the type of the file and its size //! @param outFile Pointer to the file data. The buffer must at diff --git a/include/assimp/material.inl b/include/assimp/material.inl index a10ba4de3..0b30a6839 100644 --- a/include/assimp/material.inl +++ b/include/assimp/material.inl @@ -130,7 +130,54 @@ AI_FORCE_INLINE aiReturn aiMaterial::Get(const char* pKey,unsigned int type, } // --------------------------------------------------------------------------- -AI_FORCE_INLINE aiReturn aiMaterial::Get(const char* pKey,unsigned int type, +// Specialisation for a single bool. +// Casts floating point and integer to bool +template <> +AI_FORCE_INLINE + aiReturn + aiMaterial::Get(const char *pKey, unsigned int type, + unsigned int idx, bool &pOut) const { + const aiMaterialProperty *prop; + const aiReturn ret = ::aiGetMaterialProperty(this, pKey, type, idx, + (const aiMaterialProperty **)&prop); + if (AI_SUCCESS == ret) { + + switch (prop->mType) { + // Type cannot be converted + default: return AI_FAILURE; + + case aiPTI_Buffer: { + // Native bool value storage + if (prop->mDataLength < sizeof(bool)) { + return AI_FAILURE; + } + ::memcpy(&pOut, prop->mData, sizeof(bool)); + } break; + + case aiPTI_Float: + case aiPTI_Double: { + // Read as float and cast to bool + float value = 0.0f; + if (AI_SUCCESS == ::aiGetMaterialFloat(this, pKey, type, idx, &value)) { + pOut = static_cast(value); + return AI_SUCCESS; + } + return AI_FAILURE; + } + case aiPTI_Integer: { + // Cast to bool + const int value = static_cast(*prop->mData); + pOut = static_cast(value); + return AI_SUCCESS; + } + } + } + return ret; +} + +// --------------------------------------------------------------------------- +AI_FORCE_INLINE +aiReturn aiMaterial::Get(const char* pKey,unsigned int type, unsigned int idx,ai_real* pOut, unsigned int* pMax) const { return ::aiGetMaterialFloatArray(this,pKey,type,idx,pOut,pMax); diff --git a/include/assimp/pbrmaterial.h b/include/assimp/pbrmaterial.h index 06932ced2..3bdf151a3 100644 --- a/include/assimp/pbrmaterial.h +++ b/include/assimp/pbrmaterial.h @@ -61,7 +61,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR AI_MATKEY_METALLIC_FACTOR #define AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR AI_MATKEY_ROUGHNESS_FACTOR -//#define AI_MATKEY_GLTF_PBRSPECULARGLOSSINESS "$mat.gltf.pbrSpecularGlossiness", 0, 0 +#define AI_MATKEY_GLTF_PBRSPECULARGLOSSINESS AI_MATKEY_GLOSSINESS_FACTOR #define AI_MATKEY_GLTF_PBRSPECULARGLOSSINESS_GLOSSINESS_FACTOR AI_MATKEY_GLOSSINESS_FACTOR // Use AI_MATKEY_SHADING_MODEL == aiShadingMode_Unlit instead diff --git a/test/unit/utMaterialSystem.cpp b/test/unit/utMaterialSystem.cpp index 7b4560443..39ef60679 100644 --- a/test/unit/utMaterialSystem.cpp +++ b/test/unit/utMaterialSystem.cpp @@ -125,13 +125,94 @@ TEST_F(MaterialSystemTest, testStringProperty) { } // ------------------------------------------------------------------------------------------------ -TEST_F(MaterialSystemTest, testMaterialNameAccess) { - aiMaterial *mat = new aiMaterial(); - EXPECT_NE(nullptr, mat); - - aiString name = mat->GetName(); +TEST_F(MaterialSystemTest, testDefaultMaterialName) { + aiString name = pcMat->GetName(); const int retValue(strncmp(name.C_Str(), AI_DEFAULT_MATERIAL_NAME, name.length)); EXPECT_EQ(0, retValue); - - delete mat; +} + +// ------------------------------------------------------------------------------------------------ +TEST_F(MaterialSystemTest, testBoolProperty) { + const bool valTrue = true; + const bool valFalse = false; + EXPECT_EQ(AI_SUCCESS, pcMat->AddProperty(&valTrue, 1, "bool_true")); + EXPECT_EQ(AI_SUCCESS, pcMat->AddProperty(&valFalse, 1, "bool_false")); + + bool read = false; + EXPECT_EQ(AI_SUCCESS, pcMat->Get("bool_true", 0, 0, read)); + EXPECT_TRUE(read) << "read true bool"; + EXPECT_EQ(AI_SUCCESS, pcMat->Get("bool_false", 0, 0, read)); + EXPECT_FALSE(read) << "read false bool"; +} + +// ------------------------------------------------------------------------------------------------ +TEST_F(MaterialSystemTest, testCastIntProperty) { + int value = 10; + EXPECT_EQ(AI_SUCCESS, pcMat->AddProperty(&value, 1, "integer")); + value = 0; + EXPECT_EQ(AI_SUCCESS, pcMat->AddProperty(&value, 1, "zero")); + value = -1; + EXPECT_EQ(AI_SUCCESS, pcMat->AddProperty(&value, 1, "negative")); + + // To float + float valFloat = 0.0f; + EXPECT_EQ(AI_SUCCESS, pcMat->Get("integer", 0, 0, valFloat)); + EXPECT_EQ(10.0f, valFloat); + EXPECT_EQ(AI_SUCCESS, pcMat->Get("zero", 0, 0, valFloat)); + EXPECT_EQ(0.0f, valFloat); + EXPECT_EQ(AI_SUCCESS, pcMat->Get("negative", 0, 0, valFloat)); + EXPECT_EQ(-1.0f, valFloat); + + // To bool + bool valBool = false; + EXPECT_EQ(AI_SUCCESS, pcMat->Get("integer", 0, 0, valBool)); + EXPECT_EQ(true, valBool); + EXPECT_EQ(AI_SUCCESS, pcMat->Get("zero", 0, 0, valBool)); + EXPECT_EQ(false, valBool); + EXPECT_EQ(AI_SUCCESS, pcMat->Get("negative", 0, 0, valBool)); + EXPECT_EQ(true, valBool); +} + +// ------------------------------------------------------------------------------------------------ +TEST_F(MaterialSystemTest, testCastFloatProperty) { + float value = 150392.63f; + EXPECT_EQ(AI_SUCCESS, pcMat->AddProperty(&value, 1, "float")); + value = 0; + EXPECT_EQ(AI_SUCCESS, pcMat->AddProperty(&value, 1, "zero")); + + // To int + int valInt = 0.0f; + EXPECT_EQ(AI_SUCCESS, pcMat->Get("float", 0, 0, valInt)); + EXPECT_EQ(150392, valInt); + EXPECT_EQ(AI_SUCCESS, pcMat->Get("zero", 0, 0, valInt)); + EXPECT_EQ(0, valInt); + + // To bool + bool valBool = false; + EXPECT_EQ(AI_SUCCESS, pcMat->Get("float", 0, 0, valBool)); + EXPECT_EQ(true, valBool); + EXPECT_EQ(AI_SUCCESS, pcMat->Get("zero", 0, 0, valBool)); + EXPECT_EQ(false, valBool); +} + +// ------------------------------------------------------------------------------------------------ +TEST_F(MaterialSystemTest, testCastSmallFloatProperty) { + float value = 0.0078125f; + EXPECT_EQ(AI_SUCCESS, pcMat->AddProperty(&value, 1, "float")); + value = 0; + EXPECT_EQ(AI_SUCCESS, pcMat->AddProperty(&value, 1, "zero")); + + // To int + int valInt = 0.0f; + EXPECT_EQ(AI_SUCCESS, pcMat->Get("float", 0, 0, valInt)); + EXPECT_EQ(0, valInt); + EXPECT_EQ(AI_SUCCESS, pcMat->Get("zero", 0, 0, valInt)); + EXPECT_EQ(0, valInt); + + // To bool + bool valBool = false; + EXPECT_EQ(AI_SUCCESS, pcMat->Get("float", 0, 0, valBool)); + EXPECT_EQ(true, valBool); + EXPECT_EQ(AI_SUCCESS, pcMat->Get("zero", 0, 0, valBool)); + EXPECT_EQ(false, valBool); }