diff --git a/.clang-format b/.clang-format new file mode 100644 index 000000000..eba6d586f --- /dev/null +++ b/.clang-format @@ -0,0 +1,127 @@ +# Commented out parameters are those with the same value as base LLVM style +# We can uncomment them if we want to change their value, or enforce the +# chosen value in case the base style changes (last sync: Clang 6.0.1). +--- +### General config, applies to all languages ### +BasedOnStyle: LLVM +AccessModifierOffset: -4 +AlignAfterOpenBracket: DontAlign +# AlignConsecutiveAssignments: false +# AlignConsecutiveDeclarations: false +# AlignEscapedNewlines: Right +# AlignOperands: true +AlignTrailingComments: false +AllowAllParametersOfDeclarationOnNextLine: false +# AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: true +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: true +# AllowShortLoopsOnASingleLine: false +# AlwaysBreakAfterDefinitionReturnType: None +# AlwaysBreakAfterReturnType: None +# AlwaysBreakBeforeMultilineStrings: false +# AlwaysBreakTemplateDeclarations: false +# BinPackArguments: true +# BinPackParameters: true +# BraceWrapping: +# AfterClass: false +# AfterControlStatement: false +# AfterEnum: false +# AfterFunction: false +# AfterNamespace: false +# AfterObjCDeclaration: false +# AfterStruct: false +# AfterUnion: false +# AfterExternBlock: false +# BeforeCatch: false +# BeforeElse: false +# IndentBraces: false +# SplitEmptyFunction: true +# SplitEmptyRecord: true +# SplitEmptyNamespace: true +# BreakBeforeBinaryOperators: None +# BreakBeforeBraces: Attach +# BreakBeforeInheritanceComma: false +BreakBeforeTernaryOperators: false +# BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: AfterColon +# BreakStringLiterals: true +ColumnLimit: 0 +# CommentPragmas: '^ IWYU pragma:' +# CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 8 +ContinuationIndentWidth: 8 +Cpp11BracedListStyle: false +# DerivePointerAlignment: false +# DisableFormat: false +# ExperimentalAutoDetectBinPacking: false +# FixNamespaceComments: true +# ForEachMacros: +# - foreach +# - Q_FOREACH +# - BOOST_FOREACH +# IncludeBlocks: Preserve +IncludeCategories: + - Regex: '".*"' + Priority: 1 + - Regex: '^<.*\.h>' + Priority: 2 + - Regex: '^<.*' + Priority: 3 +# IncludeIsMainRegex: '(Test)?$' +IndentCaseLabels: true +# IndentPPDirectives: None +IndentWidth: 4 +# IndentWrappedFunctionNames: false +# JavaScriptQuotes: Leave +# JavaScriptWrapImports: true +# KeepEmptyLinesAtTheStartOfBlocks: true +# MacroBlockBegin: '' +# MacroBlockEnd: '' +# MaxEmptyLinesToKeep: 1 +# NamespaceIndentation: None +# PenaltyBreakAssignment: 2 +# PenaltyBreakBeforeFirstCallParameter: 19 +# PenaltyBreakComment: 300 +# PenaltyBreakFirstLessLess: 120 +# PenaltyBreakString: 1000 +# PenaltyExcessCharacter: 1000000 +# PenaltyReturnTypeOnItsOwnLine: 60 +# PointerAlignment: Right +# RawStringFormats: +# - Delimiter: pb +# Language: TextProto +# BasedOnStyle: google +# ReflowComments: true +# SortIncludes: true +# SortUsingDeclarations: true +# SpaceAfterCStyleCast: false +# SpaceAfterTemplateKeyword: true +# SpaceBeforeAssignmentOperators: true +# SpaceBeforeParens: ControlStatements +# SpaceInEmptyParentheses: false +# SpacesBeforeTrailingComments: 1 +# SpacesInAngles: false +# SpacesInContainerLiterals: true +# SpacesInCStyleCastParentheses: false +# SpacesInParentheses: false +# SpacesInSquareBrackets: false +TabWidth: 4 +UseTab: Always +--- +### C++ specific config ### +Language: Cpp +Standard: Cpp11 +--- +### ObjC specific config ### +Language: ObjC +Standard: Cpp11 +ObjCBlockIndentWidth: 4 +# ObjCSpaceAfterProperty: false +# ObjCSpaceBeforeProtocolList: true +--- +### Java specific config ### +Language: Java +# BreakAfterJavaFieldAnnotations: false +... diff --git a/.gitignore b/.gitignore index 9dcb6623d..e975976bf 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,11 @@ build .project *.kdev4* +.DS_Store + +# build artefacts +*.o +*.a # Visual Studio *.sln diff --git a/CMakeLists.txt b/CMakeLists.txt index dcafb649f..693d6f16a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -253,7 +253,7 @@ ELSEIF(MSVC) IF(MSVC12) ADD_COMPILE_OPTIONS(/wd4351) ENDIF() - SET(CMAKE_CXX_FLAGS_DEBUG "/D_DEBUG /MDd /Ob2 /DEBUG:FULL /Zi") + SET(CMAKE_CXX_FLAGS_DEBUG "/D_DEBUG /MDd /Ob2 /Zi") ELSEIF ( "${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang" ) IF(NOT HUNTER_ENABLED) SET(CMAKE_CXX_FLAGS "-fPIC -std=c++11 ${CMAKE_CXX_FLAGS}") @@ -391,6 +391,11 @@ IF(HUNTER_ENABLED) ) ELSE(HUNTER_ENABLED) # cmake configuration files + if(${BUILD_SHARED_LIBS}) + set(BUILD_LIB_TYPE SHARED) + else() + set(BUILD_LIB_TYPE STATIC) + endif() CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/assimp-config.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/assimp-config.cmake" @ONLY IMMEDIATE) CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/assimpTargets.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/assimpTargets.cmake" @ONLY IMMEDIATE) IF (is_multi_config) diff --git a/Readme.md b/Readme.md index f749993fd..863e64f08 100644 --- a/Readme.md +++ b/Readme.md @@ -60,13 +60,19 @@ __Importers__: - ENFF - [FBX](https://en.wikipedia.org/wiki/FBX) - [glTF 1.0](https://en.wikipedia.org/wiki/GlTF#glTF_1.0) + GLB -- [glTF 2.0](https://en.wikipedia.org/wiki/GlTF#glTF_2.0) +- [glTF 2.0](https://en.wikipedia.org/wiki/GlTF#glTF_2.0): + At the moment for glTF2.0 the following extensions are supported: + + KHR_lights_punctual ( 5.0 ) + + KHR_materials_pbrSpecularGlossiness ( 5.0 ) + + KHR_materials_unlit ( 5.0 ) + + KHR_texture_transform ( 5.1 under test ) - HMB - IFC-STEP - IRR / IRRMESH - [LWO](https://en.wikipedia.org/wiki/LightWave_3D) - LWS - LXO +- [M3D](https://bztsrc.gitlab.io/model3d) - MD2 - MD3 - MD5 diff --git a/appveyor.yml b/appveyor.yml index df16431bd..fa0a125f0 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -14,6 +14,7 @@ matrix: fast_finish: true image: + - Visual Studio 2013 - Visual Studio 2015 - Visual Studio 2017 - Visual Studio 2019 @@ -29,11 +30,13 @@ install: - set PATH=C:\Ruby24-x64\bin;%PATH% - set CMAKE_DEFINES -DASSIMP_WERROR=ON - if [%COMPILER%]==[MinGW] set PATH=C:\MinGW\bin;%PATH% + - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2013" set CMAKE_GENERATOR_NAME=Visual Studio 12 2013 - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2015" set CMAKE_GENERATOR_NAME=Visual Studio 14 2015 - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2017" set CMAKE_GENERATOR_NAME=Visual Studio 15 2017 - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2019" set CMAKE_GENERATOR_NAME=Visual Studio 16 2019 - cmake %CMAKE_DEFINES% -G "%CMAKE_GENERATOR_NAME%" -A %platform% . - # Rename sh.exe as sh.exe in PATH interferes with MinGW + # Rename sh.exe as sh.exe in PATH interferes with MinGW - if "%APPVEYOR_BUILD_WORKER_IMAGE%"=="Visual Studio 2015" set CMAKE_GENERATOR_NAME=Visual Studio 14 2015 + - rename "C:\Program Files\Git\usr\bin\sh.exe" "sh2.exe" - set PATH=%PATH%;"C:\\Program Files (x86)\\Inno Setup 5" - ps: Invoke-WebRequest -Uri https://download.microsoft.com/download/5/7/b/57b2947c-7221-4f33-b35e-2fc78cb10df4/vc_redist.x64.exe -OutFile .\packaging\windows-innosetup\vc_redist.x64.exe diff --git a/assimpTargets-debug.cmake.in b/assimpTargets-debug.cmake.in index ed98e777b..e4ccbfba9 100644 --- a/assimpTargets-debug.cmake.in +++ b/assimpTargets-debug.cmake.in @@ -35,6 +35,8 @@ if(MSVC) endif() set(ASSIMP_LIBRARY_SUFFIX "@ASSIMP_LIBRARY_SUFFIX@-${MSVC_PREFIX}-mt" CACHE STRING "the suffix for the assimp windows library" ) + file(TO_NATIVE_PATH ${_IMPORT_PREFIX} _IMPORT_PREFIX) + if(ASSIMP_BUILD_SHARED_LIBS) set(sharedLibraryName "assimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@@CMAKE_SHARED_LIBRARY_SUFFIX@") set(importLibraryName "assimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@@CMAKE_IMPORT_LIBRARY_SUFFIX@") @@ -63,10 +65,13 @@ if(MSVC) else() set(ASSIMP_LIBRARY_SUFFIX "@ASSIMP_LIBRARY_SUFFIX@" CACHE STRING "the suffix for the assimp libraries" ) if(ASSIMP_BUILD_SHARED_LIBS) - if(APPLE) - set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@.@ASSIMP_VERSION_MAJOR@@CMAKE_SHARED_LIBRARY_SUFFIX@") - else(APPLE) - set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@@CMAKE_SHARED_LIBRARY_SUFFIX@.@ASSIMP_VERSION_MAJOR@") + if(WIN32) + # Handle MinGW compiler. + set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@@CMAKE_SHARED_LIBRARY_SUFFIX@@CMAKE_STATIC_LIBRARY_SUFFIX@") + elseif(APPLE) + set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@.@ASSIMP_VERSION_MAJOR@@CMAKE_SHARED_LIBRARY_SUFFIX@") + else() + set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@@CMAKE_SHARED_LIBRARY_SUFFIX@.@ASSIMP_VERSION_MAJOR@") endif() set_target_properties(assimp::assimp PROPERTIES IMPORTED_SONAME_DEBUG "${sharedLibraryName}" diff --git a/assimpTargets-release.cmake.in b/assimpTargets-release.cmake.in index a5fe74933..79b643a9a 100644 --- a/assimpTargets-release.cmake.in +++ b/assimpTargets-release.cmake.in @@ -34,6 +34,8 @@ if(MSVC) endif() endif() set(ASSIMP_LIBRARY_SUFFIX "@ASSIMP_LIBRARY_SUFFIX@-${MSVC_PREFIX}-mt" CACHE STRING "the suffix for the assimp windows library" ) + + file(TO_NATIVE_PATH ${_IMPORT_PREFIX} _IMPORT_PREFIX) if(ASSIMP_BUILD_SHARED_LIBS) set(sharedLibraryName "assimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_SHARED_LIBRARY_SUFFIX@") @@ -63,13 +65,17 @@ if(MSVC) else() set(ASSIMP_LIBRARY_SUFFIX "@ASSIMP_LIBRARY_SUFFIX@" CACHE STRING "the suffix for the assimp libraries" ) if(ASSIMP_BUILD_SHARED_LIBS) - if(APPLE) - set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}.@ASSIMP_VERSION_MAJOR@@CMAKE_SHARED_LIBRARY_SUFFIX@") - else(APPLE) - set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_SHARED_LIBRARY_SUFFIX@.@ASSIMP_VERSION_MAJOR@") + if(WIN32) + # Handle MinGW compiler. + set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_DEBUG_POSTFIX@@CMAKE_SHARED_LIBRARY_SUFFIX@@CMAKE_STATIC_LIBRARY_SUFFIX@") + elseif(APPLE) + set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}.@ASSIMP_VERSION_MAJOR@@CMAKE_SHARED_LIBRARY_SUFFIX@") + else() + set(sharedLibraryName "libassimp${ASSIMP_LIBRARY_SUFFIX}@CMAKE_SHARED_LIBRARY_SUFFIX@.@ASSIMP_VERSION_MAJOR@") endif() set_target_properties(assimp::assimp PROPERTIES IMPORTED_SONAME_RELEASE "${sharedLibraryName}" + IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/${sharedLibraryName}" ) list(APPEND _IMPORT_CHECK_TARGETS assimp::assimp ) diff --git a/assimpTargets.cmake.in b/assimpTargets.cmake.in index afef9cbd1..b1c618c3a 100644 --- a/assimpTargets.cmake.in +++ b/assimpTargets.cmake.in @@ -54,11 +54,7 @@ if(_IMPORT_PREFIX STREQUAL "/") endif() # Create imported target assimp::assimp -if(@BUILD_SHARED_LIBS@) - add_library(assimp::assimp SHARED IMPORTED) -else() - add_library(assimp::assimp STATIC IMPORTED) -endif() +add_library(assimp::assimp @BUILD_LIB_TYPE@ IMPORTED) set_target_properties(assimp::assimp PROPERTIES COMPATIBLE_INTERFACE_STRING "assimp_MAJOR_VERSION" diff --git a/code/3DS/3DSConverter.cpp b/code/3DS/3DSConverter.cpp index 2176b75fc..3c3da36a3 100644 --- a/code/3DS/3DSConverter.cpp +++ b/code/3DS/3DSConverter.cpp @@ -72,7 +72,7 @@ void Discreet3DSImporter::ReplaceDefaultMaterial() unsigned int idx( NotSet ); for (unsigned int i = 0; i < mScene->mMaterials.size();++i) { - std::string &s = mScene->mMaterials[i].mName; + std::string s = mScene->mMaterials[i].mName; for ( std::string::iterator it = s.begin(); it != s.end(); ++it ) { *it = static_cast< char >( ::tolower( *it ) ); } diff --git a/code/CMakeLists.txt b/code/CMakeLists.txt index eec805b54..827f43333 100644 --- a/code/CMakeLists.txt +++ b/code/CMakeLists.txt @@ -407,6 +407,18 @@ ADD_ASSIMP_IMPORTER( LWS LWS/LWSLoader.h ) +ADD_ASSIMP_IMPORTER( M3D + M3D/M3DMaterials.h + M3D/M3DImporter.h + M3D/M3DImporter.cpp + M3D/m3d.h +) + +ADD_ASSIMP_EXPORTER( M3D + M3D/M3DExporter.h + M3D/M3DExporter.cpp +) + ADD_ASSIMP_IMPORTER( MD2 MD2/MD2FileData.h MD2/MD2Loader.cpp @@ -670,6 +682,8 @@ SET( PostProcessing_SRCS PostProcessing/MakeVerboseFormat.h PostProcessing/ScaleProcess.cpp PostProcessing/ScaleProcess.h + PostProcessing/ArmaturePopulate.cpp + PostProcessing/ArmaturePopulate.h PostProcessing/GenBoundingBoxesProcess.cpp PostProcessing/GenBoundingBoxesProcess.h ) diff --git a/code/Collada/ColladaHelper.h b/code/Collada/ColladaHelper.h index 66cff2d20..b7d47da15 100644 --- a/code/Collada/ColladaHelper.h +++ b/code/Collada/ColladaHelper.h @@ -583,7 +583,7 @@ struct Image /** Embedded image data */ std::vector mImageData; - /** File format hint ofembedded image data */ + /** File format hint of embedded image data */ std::string mEmbeddedFormat; }; diff --git a/code/Collada/ColladaLoader.cpp b/code/Collada/ColladaLoader.cpp index 0fbc7d599..37529bc98 100644 --- a/code/Collada/ColladaLoader.cpp +++ b/code/Collada/ColladaLoader.cpp @@ -1735,6 +1735,7 @@ void ColladaLoader::BuildMaterials(ColladaParser& pParser, aiScene* /*pScene*/) // ------------------------------------------------------------------------------------------------ // Resolves the texture name for the given effect texture entry +// and loads the texture data aiString ColladaLoader::FindFilenameForEffectTexture(const ColladaParser& pParser, const Collada::Effect& pEffect, const std::string& pName) { @@ -1762,7 +1763,7 @@ aiString ColladaLoader::FindFilenameForEffectTexture(const ColladaParser& pParse //set default texture file name result.Set(name + ".jpg"); - ConvertPath(result); + ColladaParser::UriDecodePath(result); return result; } @@ -1781,7 +1782,7 @@ aiString ColladaLoader::FindFilenameForEffectTexture(const ColladaParser& pParse // setup format hint - if (imIt->second.mEmbeddedFormat.length() > 3) { + if (imIt->second.mEmbeddedFormat.length() >= HINTMAXTEXTURELEN) { ASSIMP_LOG_WARN("Collada: texture format hint is too long, truncating to 3 characters"); } strncpy(tex->achFormatHint, imIt->second.mEmbeddedFormat.c_str(), 3); @@ -1802,61 +1803,10 @@ aiString ColladaLoader::FindFilenameForEffectTexture(const ColladaParser& pParse } result.Set(imIt->second.mFileName); - ConvertPath(result); } return result; } -// ------------------------------------------------------------------------------------------------ -// Convert a path read from a collada file to the usual representation -void ColladaLoader::ConvertPath(aiString& ss) -{ - // TODO: collada spec, p 22. Handle URI correctly. - // For the moment we're just stripping the file:// away to make it work. - // Windows doesn't seem to be able to find stuff like - // 'file://..\LWO\LWO2\MappingModes\earthSpherical.jpg' - if (0 == strncmp(ss.data, "file://", 7)) - { - ss.length -= 7; - memmove(ss.data, ss.data + 7, ss.length); - ss.data[ss.length] = '\0'; - } - - // Maxon Cinema Collada Export writes "file:///C:\andsoon" with three slashes... - // I need to filter it without destroying linux paths starting with "/somewhere" -#if defined( _MSC_VER ) - if (ss.data[0] == '/' && isalpha((unsigned char)ss.data[1]) && ss.data[2] == ':') { -#else - if (ss.data[0] == '/' && isalpha(ss.data[1]) && ss.data[2] == ':') { -#endif - --ss.length; - ::memmove(ss.data, ss.data + 1, ss.length); - ss.data[ss.length] = 0; - } - - // find and convert all %xy special chars - char* out = ss.data; - for (const char* it = ss.data; it != ss.data + ss.length; /**/) - { - if (*it == '%' && (it + 3) < ss.data + ss.length) - { - // separate the number to avoid dragging in chars from behind into the parsing - char mychar[3] = { it[1], it[2], 0 }; - size_t nbr = strtoul16(mychar); - it += 3; - *out++ = (char)(nbr & 0xFF); - } - else - { - *out++ = *it++; - } - } - - // adjust length and terminator of the shortened string - *out = 0; - ss.length = (ptrdiff_t)(out - ss.data); -} - // ------------------------------------------------------------------------------------------------ // Reads a float value from an accessor and its data array. ai_real ColladaLoader::ReadFloat(const Collada::Accessor& pAccessor, const Collada::Data& pData, size_t pIndex, size_t pOffset) const diff --git a/code/Collada/ColladaLoader.h b/code/Collada/ColladaLoader.h index dce46e06f..d8d3f4f52 100644 --- a/code/Collada/ColladaLoader.h +++ b/code/Collada/ColladaLoader.h @@ -94,7 +94,7 @@ public: public: /** Returns whether the class can handle the format of the given file. * See BaseImporter::CanRead() for details. */ - bool CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const override; + bool CanRead(const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const override; protected: /** Return importer meta information. @@ -184,9 +184,6 @@ protected: aiString FindFilenameForEffectTexture( const ColladaParser& pParser, const Collada::Effect& pEffect, const std::string& pName); - /** Converts a path read from a collada file to the usual representation */ - void ConvertPath( aiString& ss); - /** Reads a float value from an accessor and its data array. * @param pAccessor The accessor to use for reading * @param pData The data array to read from diff --git a/code/Collada/ColladaParser.cpp b/code/Collada/ColladaParser.cpp index 646ec473d..e2e6626c3 100644 --- a/code/Collada/ColladaParser.cpp +++ b/code/Collada/ColladaParser.cpp @@ -183,13 +183,67 @@ std::string ColladaParser::ReadZaeManifest(ZipArchiveIOSystem &zip_archive) { if (filepath == nullptr) return std::string(); - return std::string(filepath); + aiString ai_str(filepath); + UriDecodePath(ai_str); + + return std::string(ai_str.C_Str()); } } } return std::string(); } +// ------------------------------------------------------------------------------------------------ +// Convert a path read from a collada file to the usual representation +void ColladaParser::UriDecodePath(aiString& ss) +{ + // TODO: collada spec, p 22. Handle URI correctly. + // For the moment we're just stripping the file:// away to make it work. + // Windows doesn't seem to be able to find stuff like + // 'file://..\LWO\LWO2\MappingModes\earthSpherical.jpg' + if (0 == strncmp(ss.data, "file://", 7)) + { + ss.length -= 7; + memmove(ss.data, ss.data + 7, ss.length); + ss.data[ss.length] = '\0'; + } + + // Maxon Cinema Collada Export writes "file:///C:\andsoon" with three slashes... + // I need to filter it without destroying linux paths starting with "/somewhere" +#if defined( _MSC_VER ) + if (ss.data[0] == '/' && isalpha((unsigned char)ss.data[1]) && ss.data[2] == ':') { +#else + if (ss.data[0] == '/' && isalpha(ss.data[1]) && ss.data[2] == ':') { +#endif + --ss.length; + ::memmove(ss.data, ss.data + 1, ss.length); + ss.data[ss.length] = 0; + } + + // find and convert all %xy special chars + char* out = ss.data; + for (const char* it = ss.data; it != ss.data + ss.length; /**/) + { + if (*it == '%' && (it + 3) < ss.data + ss.length) + { + // separate the number to avoid dragging in chars from behind into the parsing + char mychar[3] = { it[1], it[2], 0 }; + size_t nbr = strtoul16(mychar); + it += 3; + *out++ = (char)(nbr & 0xFF); + } + else + { + *out++ = *it++; + } + } + + // adjust length and terminator of the shortened string + *out = 0; + ai_assert(out > ss.data); + ss.length = static_cast(out - ss.data); +} + // ------------------------------------------------------------------------------------------------ // Read bool from text contents of current element bool ColladaParser::ReadBoolFromTextContent() @@ -1120,7 +1174,12 @@ void ColladaParser::ReadImage(Collada::Image& pImage) if (!mReader->isEmptyElement()) { // element content is filename - hopefully const char* sz = TestTextContent(); - if (sz)pImage.mFileName = sz; + if (sz) + { + aiString filepath(sz); + UriDecodePath(filepath); + pImage.mFileName = filepath.C_Str(); + } TestClosing("init_from"); } if (!pImage.mFileName.length()) { @@ -1153,7 +1212,12 @@ void ColladaParser::ReadImage(Collada::Image& pImage) { // element content is filename - hopefully const char* sz = TestTextContent(); - if (sz)pImage.mFileName = sz; + if (sz) + { + aiString filepath(sz); + UriDecodePath(filepath); + pImage.mFileName = filepath.C_Str(); + } TestClosing("ref"); } else if (IsElement("hex") && !pImage.mFileName.length()) @@ -3056,7 +3120,7 @@ void ColladaParser::ReadMaterialVertexInputBinding(Collada::SemanticMappingTable } } -void Assimp::ColladaParser::ReadEmbeddedTextures(ZipArchiveIOSystem& zip_archive) +void ColladaParser::ReadEmbeddedTextures(ZipArchiveIOSystem& zip_archive) { // Attempt to load any undefined Collada::Image in ImageLibrary for (ImageLibrary::iterator it = mImageLibrary.begin(); it != mImageLibrary.end(); ++it) { @@ -3170,13 +3234,12 @@ void ColladaParser::ReadScene() // ------------------------------------------------------------------------------------------------ // Aborts the file reading with an exception -AI_WONT_RETURN void ColladaParser::ThrowException(const std::string& pError) const -{ +AI_WONT_RETURN void ColladaParser::ThrowException(const std::string& pError) const { throw DeadlyImportError(format() << "Collada: " << mFileName << " - " << pError); } -void ColladaParser::ReportWarning(const char* msg, ...) -{ - ai_assert(NULL != msg); + +void ColladaParser::ReportWarning(const char* msg, ...) { + ai_assert(nullptr != msg); va_list args; va_start(args, msg); @@ -3191,11 +3254,11 @@ void ColladaParser::ReportWarning(const char* msg, ...) // ------------------------------------------------------------------------------------------------ // Skips all data until the end node of the current element -void ColladaParser::SkipElement() -{ +void ColladaParser::SkipElement() { // nothing to skip if it's an - if (mReader->isEmptyElement()) + if (mReader->isEmptyElement()) { return; + } // reroute SkipElement(mReader->getNodeName()); @@ -3203,63 +3266,75 @@ void ColladaParser::SkipElement() // ------------------------------------------------------------------------------------------------ // Skips all data until the end node of the given element -void ColladaParser::SkipElement(const char* pElement) -{ +void ColladaParser::SkipElement(const char* pElement) { // copy the current node's name because it'a pointer to the reader's internal buffer, // which is going to change with the upcoming parsing std::string element = pElement; - while (mReader->read()) - { - if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) - if (mReader->getNodeName() == element) + while (mReader->read()) { + if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END) { + if (mReader->getNodeName() == element) { break; + } + } } } // ------------------------------------------------------------------------------------------------ // Tests for an opening element of the given name, throws an exception if not found -void ColladaParser::TestOpening(const char* pName) -{ +void ColladaParser::TestOpening(const char* pName) { // read element start - if (!mReader->read()) + if (!mReader->read()) { ThrowException(format() << "Unexpected end of file while beginning of <" << pName << "> element."); + } // whitespace in front is ok, just read again if found - if (mReader->getNodeType() == irr::io::EXN_TEXT) - if (!mReader->read()) + if (mReader->getNodeType() == irr::io::EXN_TEXT) { + if (!mReader->read()) { ThrowException(format() << "Unexpected end of file while reading beginning of <" << pName << "> element."); + } + } - if (mReader->getNodeType() != irr::io::EXN_ELEMENT || strcmp(mReader->getNodeName(), pName) != 0) + if (mReader->getNodeType() != irr::io::EXN_ELEMENT || strcmp(mReader->getNodeName(), pName) != 0) { ThrowException(format() << "Expected start of <" << pName << "> element."); + } } // ------------------------------------------------------------------------------------------------ // Tests for the closing tag of the given element, throws an exception if not found -void ColladaParser::TestClosing(const char* pName) -{ - // check if we're already on the closing tag and return right away - if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END && strcmp(mReader->getNodeName(), pName) == 0) +void ColladaParser::TestClosing(const char* pName) { + // check if we have an empty (self-closing) element + if (mReader->isEmptyElement()) { return; + } + + // check if we're already on the closing tag and return right away + if (mReader->getNodeType() == irr::io::EXN_ELEMENT_END && strcmp(mReader->getNodeName(), pName) == 0) { + return; + } // if not, read some more - if (!mReader->read()) + if (!mReader->read()) { ThrowException(format() << "Unexpected end of file while reading end of <" << pName << "> element."); + } // whitespace in front is ok, just read again if found - if (mReader->getNodeType() == irr::io::EXN_TEXT) - if (!mReader->read()) + if (mReader->getNodeType() == irr::io::EXN_TEXT) { + if (!mReader->read()) { ThrowException(format() << "Unexpected end of file while reading end of <" << pName << "> element."); + } + } // but this has the be the closing tag, or we're lost - if (mReader->getNodeType() != irr::io::EXN_ELEMENT_END || strcmp(mReader->getNodeName(), pName) != 0) + if (mReader->getNodeType() != irr::io::EXN_ELEMENT_END || strcmp(mReader->getNodeName(), pName) != 0) { ThrowException(format() << "Expected end of <" << pName << "> element."); + } } // ------------------------------------------------------------------------------------------------ // Returns the index of the named attribute or -1 if not found. Does not throw, therefore useful for optional attributes -int ColladaParser::GetAttribute(const char* pAttr) const -{ +int ColladaParser::GetAttribute(const char* pAttr) const { int index = TestAttribute(pAttr); - if (index != -1) + if (index != -1) { return index; + } // attribute not found -> throw an exception ThrowException(format() << "Expected attribute \"" << pAttr << "\" for element <" << mReader->getNodeName() << ">."); diff --git a/code/Collada/ColladaParser.h b/code/Collada/ColladaParser.h index a2c9a4ff2..4e8c8fccf 100644 --- a/code/Collada/ColladaParser.h +++ b/code/Collada/ColladaParser.h @@ -66,12 +66,15 @@ namespace Assimp { friend class ColladaLoader; + /** Converts a path read from a collada file to the usual representation */ + static void UriDecodePath(aiString& ss); + protected: /** Map for generic metadata as aiString */ typedef std::map StringMetaData; /** Constructor from XML file */ - ColladaParser( IOSystem* pIOHandler, const std::string& pFile); + ColladaParser(IOSystem* pIOHandler, const std::string& pFile); /** Destructor */ ~ColladaParser(); diff --git a/code/Common/DefaultIOStream.cpp b/code/Common/DefaultIOStream.cpp index 1c100b618..829b44731 100644 --- a/code/Common/DefaultIOStream.cpp +++ b/code/Common/DefaultIOStream.cpp @@ -52,6 +52,35 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using namespace Assimp; +namespace +{ + template + size_t select_ftell(FILE* file) + { + return ::ftell(file); + } + + template + int select_fseek(FILE* file, int64_t offset, int origin) + { + return ::fseek(file, static_cast(offset), origin); + } + +#if defined _WIN32 && (!defined __GNUC__ || __MSVCRT_VERSION__ >= 0x0601) + template<> + size_t select_ftell<8>(FILE* file) + { + return ::_ftelli64(file); + } + + template<> + int select_fseek<8>(FILE* file, int64_t offset, int origin) + { + return ::_fseeki64(file, offset, origin); + } +#endif +} + // ---------------------------------------------------------------------------------- DefaultIOStream::~DefaultIOStream() { @@ -93,7 +122,7 @@ aiReturn DefaultIOStream::Seek(size_t pOffset, aiOrigin_END == SEEK_END && aiOrigin_SET == SEEK_SET"); // do the seek - return (0 == ::fseek(mFile, (long)pOffset,(int)pOrigin) ? AI_SUCCESS : AI_FAILURE); + return (0 == select_fseek(mFile, (int64_t)pOffset,(int)pOrigin) ? AI_SUCCESS : AI_FAILURE); } // ---------------------------------------------------------------------------------- @@ -102,7 +131,7 @@ size_t DefaultIOStream::Tell() const if (!mFile) { return 0; } - return ::ftell(mFile); + return select_ftell(mFile); } // ---------------------------------------------------------------------------------- diff --git a/code/Common/DefaultLogger.cpp b/code/Common/DefaultLogger.cpp index de3528d2b..eee53bd7c 100644 --- a/code/Common/DefaultLogger.cpp +++ b/code/Common/DefaultLogger.cpp @@ -107,7 +107,7 @@ LogStream* LogStream::createDefaultStream(aiDefaultLogStream streams, return nullptr; #endif - // Platform-independent default streams + // Platform-independent default streams case aiDefaultLogStream_STDERR: return new StdOStreamLogStream(std::cerr); case aiDefaultLogStream_STDOUT: @@ -121,7 +121,7 @@ LogStream* LogStream::createDefaultStream(aiDefaultLogStream streams, }; // For compilers without dead code path detection - return NULL; + return nullptr; } // ---------------------------------------------------------------------------------- diff --git a/code/Common/Exporter.cpp b/code/Common/Exporter.cpp index 34d49c472..8a95ceae5 100644 --- a/code/Common/Exporter.cpp +++ b/code/Common/Exporter.cpp @@ -102,94 +102,92 @@ void ExportSceneX3D(const char*, IOSystem*, const aiScene*, const ExportProperti void ExportSceneFBX(const char*, IOSystem*, const aiScene*, const ExportProperties*); void ExportSceneFBXA(const char*, IOSystem*, const aiScene*, const ExportProperties*); void ExportScene3MF( const char*, IOSystem*, const aiScene*, const ExportProperties* ); +void ExportSceneM3D(const char*, IOSystem*, const aiScene*, const ExportProperties*); +void ExportSceneA3D(const char*, IOSystem*, const aiScene*, const ExportProperties*); void ExportAssimp2Json(const char* , IOSystem*, const aiScene* , const Assimp::ExportProperties*); -// ------------------------------------------------------------------------------------------------ -// global array of all export formats which Assimp supports in its current build -Exporter::ExportFormatEntry gExporters[] = -{ + +static void setupExporterArray(std::vector &exporters) { #ifndef ASSIMP_BUILD_NO_COLLADA_EXPORTER - Exporter::ExportFormatEntry( "collada", "COLLADA - Digital Asset Exchange Schema", "dae", &ExportSceneCollada ), + exporters.push_back(Exporter::ExportFormatEntry("collada", "COLLADA - Digital Asset Exchange Schema", "dae", &ExportSceneCollada)); #endif #ifndef ASSIMP_BUILD_NO_X_EXPORTER - Exporter::ExportFormatEntry( "x", "X Files", "x", &ExportSceneXFile, - aiProcess_MakeLeftHanded | aiProcess_FlipWindingOrder | aiProcess_FlipUVs ), + exporters.push_back(Exporter::ExportFormatEntry("x", "X Files", "x", &ExportSceneXFile, + aiProcess_MakeLeftHanded | aiProcess_FlipWindingOrder | aiProcess_FlipUVs)); #endif #ifndef ASSIMP_BUILD_NO_STEP_EXPORTER - Exporter::ExportFormatEntry( "stp", "Step Files", "stp", &ExportSceneStep, 0 ), + exporters.push_back(Exporter::ExportFormatEntry("stp", "Step Files", "stp", &ExportSceneStep, 0)); #endif #ifndef ASSIMP_BUILD_NO_OBJ_EXPORTER - Exporter::ExportFormatEntry( "obj", "Wavefront OBJ format", "obj", &ExportSceneObj, - aiProcess_GenSmoothNormals /*| aiProcess_PreTransformVertices */ ), - Exporter::ExportFormatEntry( "objnomtl", "Wavefront OBJ format without material file", "obj", &ExportSceneObjNoMtl, - aiProcess_GenSmoothNormals /*| aiProcess_PreTransformVertices */ ), + exporters.push_back(Exporter::ExportFormatEntry("obj", "Wavefront OBJ format", "obj", &ExportSceneObj, + aiProcess_GenSmoothNormals /*| aiProcess_PreTransformVertices */)); + exporters.push_back(Exporter::ExportFormatEntry("objnomtl", "Wavefront OBJ format without material file", "obj", &ExportSceneObjNoMtl, + aiProcess_GenSmoothNormals /*| aiProcess_PreTransformVertices */)); #endif #ifndef ASSIMP_BUILD_NO_STL_EXPORTER - Exporter::ExportFormatEntry( "stl", "Stereolithography", "stl" , &ExportSceneSTL, - aiProcess_Triangulate | aiProcess_GenNormals | aiProcess_PreTransformVertices - ), - Exporter::ExportFormatEntry( "stlb", "Stereolithography (binary)", "stl" , &ExportSceneSTLBinary, - aiProcess_Triangulate | aiProcess_GenNormals | aiProcess_PreTransformVertices - ), + exporters.push_back(Exporter::ExportFormatEntry("stl", "Stereolithography", "stl", &ExportSceneSTL, + aiProcess_Triangulate | aiProcess_GenNormals | aiProcess_PreTransformVertices)); + exporters.push_back(Exporter::ExportFormatEntry("stlb", "Stereolithography (binary)", "stl", &ExportSceneSTLBinary, + aiProcess_Triangulate | aiProcess_GenNormals | aiProcess_PreTransformVertices)); #endif #ifndef ASSIMP_BUILD_NO_PLY_EXPORTER - Exporter::ExportFormatEntry( "ply", "Stanford Polygon Library", "ply" , &ExportScenePly, - aiProcess_PreTransformVertices - ), - Exporter::ExportFormatEntry( "plyb", "Stanford Polygon Library (binary)", "ply", &ExportScenePlyBinary, - aiProcess_PreTransformVertices - ), + exporters.push_back(Exporter::ExportFormatEntry("ply", "Stanford Polygon Library", "ply", &ExportScenePly, + aiProcess_PreTransformVertices)); + exporters.push_back(Exporter::ExportFormatEntry("plyb", "Stanford Polygon Library (binary)", "ply", &ExportScenePlyBinary, + aiProcess_PreTransformVertices)); #endif #ifndef ASSIMP_BUILD_NO_3DS_EXPORTER - Exporter::ExportFormatEntry( "3ds", "Autodesk 3DS (legacy)", "3ds" , &ExportScene3DS, - aiProcess_Triangulate | aiProcess_SortByPType | aiProcess_JoinIdenticalVertices ), + exporters.push_back(Exporter::ExportFormatEntry("3ds", "Autodesk 3DS (legacy)", "3ds", &ExportScene3DS, + aiProcess_Triangulate | aiProcess_SortByPType | aiProcess_JoinIdenticalVertices)); #endif #ifndef ASSIMP_BUILD_NO_GLTF_EXPORTER - Exporter::ExportFormatEntry( "gltf2", "GL Transmission Format v. 2", "gltf", &ExportSceneGLTF2, - aiProcess_JoinIdenticalVertices | aiProcess_Triangulate | aiProcess_SortByPType ), - Exporter::ExportFormatEntry( "glb2", "GL Transmission Format v. 2 (binary)", "glb", &ExportSceneGLB2, - aiProcess_JoinIdenticalVertices | aiProcess_Triangulate | aiProcess_SortByPType ), - Exporter::ExportFormatEntry( "gltf", "GL Transmission Format", "gltf", &ExportSceneGLTF, - aiProcess_JoinIdenticalVertices | aiProcess_Triangulate | aiProcess_SortByPType ), - Exporter::ExportFormatEntry( "glb", "GL Transmission Format (binary)", "glb", &ExportSceneGLB, - aiProcess_JoinIdenticalVertices | aiProcess_Triangulate | aiProcess_SortByPType ), + exporters.push_back(Exporter::ExportFormatEntry("gltf2", "GL Transmission Format v. 2", "gltf", &ExportSceneGLTF2, + aiProcess_JoinIdenticalVertices | aiProcess_Triangulate | aiProcess_SortByPType)); + exporters.push_back(Exporter::ExportFormatEntry("glb2", "GL Transmission Format v. 2 (binary)", "glb", &ExportSceneGLB2, + aiProcess_JoinIdenticalVertices | aiProcess_Triangulate | aiProcess_SortByPType)); + exporters.push_back(Exporter::ExportFormatEntry("gltf", "GL Transmission Format", "gltf", &ExportSceneGLTF, + aiProcess_JoinIdenticalVertices | aiProcess_Triangulate | aiProcess_SortByPType)); + exporters.push_back(Exporter::ExportFormatEntry("glb", "GL Transmission Format (binary)", "glb", &ExportSceneGLB, + aiProcess_JoinIdenticalVertices | aiProcess_Triangulate | aiProcess_SortByPType)); #endif #ifndef ASSIMP_BUILD_NO_ASSBIN_EXPORTER - Exporter::ExportFormatEntry( "assbin", "Assimp Binary File", "assbin" , &ExportSceneAssbin, 0 ), + exporters.push_back(Exporter::ExportFormatEntry("assbin", "Assimp Binary File", "assbin", &ExportSceneAssbin, 0)); #endif #ifndef ASSIMP_BUILD_NO_ASSXML_EXPORTER - Exporter::ExportFormatEntry( "assxml", "Assimp XML Document", "assxml" , &ExportSceneAssxml, 0 ), + exporters.push_back(Exporter::ExportFormatEntry("assxml", "Assimp XML Document", "assxml", &ExportSceneAssxml, 0)); #endif #ifndef ASSIMP_BUILD_NO_X3D_EXPORTER - Exporter::ExportFormatEntry( "x3d", "Extensible 3D", "x3d" , &ExportSceneX3D, 0 ), + exporters.push_back(Exporter::ExportFormatEntry("x3d", "Extensible 3D", "x3d", &ExportSceneX3D, 0)); #endif #ifndef ASSIMP_BUILD_NO_FBX_EXPORTER - Exporter::ExportFormatEntry( "fbx", "Autodesk FBX (binary)", "fbx", &ExportSceneFBX, 0 ), - Exporter::ExportFormatEntry( "fbxa", "Autodesk FBX (ascii)", "fbx", &ExportSceneFBXA, 0 ), + exporters.push_back(Exporter::ExportFormatEntry("fbx", "Autodesk FBX (binary)", "fbx", &ExportSceneFBX, 0)); + exporters.push_back(Exporter::ExportFormatEntry("fbxa", "Autodesk FBX (ascii)", "fbx", &ExportSceneFBXA, 0)); +#endif + +#ifndef ASSIMP_BUILD_NO_M3D_EXPORTER + exporters.push_back(Exporter::ExportFormatEntry("m3d", "Model 3D (binary)", "m3d", &ExportSceneM3D, 0)); + exporters.push_back(Exporter::ExportFormatEntry("a3d", "Model 3D (ascii)", "m3d", &ExportSceneA3D, 0)); #endif #ifndef ASSIMP_BUILD_NO_3MF_EXPORTER - Exporter::ExportFormatEntry( "3mf", "The 3MF-File-Format", "3mf", &ExportScene3MF, 0 ), + exporters.push_back(Exporter::ExportFormatEntry("3mf", "The 3MF-File-Format", "3mf", &ExportScene3MF, 0)); #endif #ifndef ASSIMP_BUILD_NO_ASSJSON_EXPORTER - Exporter::ExportFormatEntry( "assjson", "Assimp JSON Document", "json", &ExportAssimp2Json, 0) + exporters.push_back(Exporter::ExportFormatEntry("assjson", "Assimp JSON Document", "json", &ExportAssimp2Json, 0)); #endif -}; - -#define ASSIMP_NUM_EXPORTERS (sizeof(gExporters)/sizeof(gExporters[0])) - +} class ExporterPimpl { public: @@ -205,10 +203,7 @@ public: GetPostProcessingStepInstanceList(mPostProcessingSteps); // grab all built-in exporters - if ( 0 != ( ASSIMP_NUM_EXPORTERS ) ) { - mExporters.resize( ASSIMP_NUM_EXPORTERS ); - std::copy( gExporters, gExporters + ASSIMP_NUM_EXPORTERS, mExporters.begin() ); - } + setupExporterArray(mExporters); } ~ExporterPimpl() { @@ -252,24 +247,28 @@ Exporter :: Exporter() // ------------------------------------------------------------------------------------------------ Exporter::~Exporter() { - FreeBlob(); + ai_assert(nullptr != pimpl); + FreeBlob(); delete pimpl; } // ------------------------------------------------------------------------------------------------ void Exporter::SetIOHandler( IOSystem* pIOHandler) { - pimpl->mIsDefaultIOHandler = !pIOHandler; + ai_assert(nullptr != pimpl); + pimpl->mIsDefaultIOHandler = !pIOHandler; pimpl->mIOSystem.reset(pIOHandler); } // ------------------------------------------------------------------------------------------------ IOSystem* Exporter::GetIOHandler() const { - return pimpl->mIOSystem.get(); + ai_assert(nullptr != pimpl); + return pimpl->mIOSystem.get(); } // ------------------------------------------------------------------------------------------------ bool Exporter::IsDefaultIOHandler() const { - return pimpl->mIsDefaultIOHandler; + ai_assert(nullptr != pimpl); + return pimpl->mIsDefaultIOHandler; } // ------------------------------------------------------------------------------------------------ @@ -295,6 +294,7 @@ void Exporter::SetProgressHandler(ProgressHandler* pHandler) { // ------------------------------------------------------------------------------------------------ const aiExportDataBlob* Exporter::ExportToBlob( const aiScene* pScene, const char* pFormatId, unsigned int pPreprocessing, const ExportProperties* pProperties) { + ai_assert(nullptr != pimpl); if (pimpl->blob) { delete pimpl->blob; pimpl->blob = nullptr; @@ -319,7 +319,7 @@ const aiExportDataBlob* Exporter::ExportToBlob( const aiScene* pScene, const cha aiReturn Exporter::Export( const aiScene* pScene, const char* pFormatId, const char* pPath, unsigned int pPreprocessing, const ExportProperties* pProperties) { ASSIMP_BEGIN_EXCEPTION_REGION(); - + ai_assert(nullptr != pimpl); // when they create scenes from scratch, users will likely create them not in verbose // format. They will likely not be aware that there is a flag in the scene to indicate // this, however. To avoid surprises and bug reports, we check for duplicates in @@ -466,11 +466,13 @@ aiReturn Exporter::Export( const aiScene* pScene, const char* pFormatId, const c // ------------------------------------------------------------------------------------------------ const char* Exporter::GetErrorString() const { + ai_assert(nullptr != pimpl); return pimpl->mError.c_str(); } // ------------------------------------------------------------------------------------------------ void Exporter::FreeBlob() { + ai_assert(nullptr != pimpl); delete pimpl->blob; pimpl->blob = nullptr; @@ -479,30 +481,34 @@ void Exporter::FreeBlob() { // ------------------------------------------------------------------------------------------------ const aiExportDataBlob* Exporter::GetBlob() const { - return pimpl->blob; + ai_assert(nullptr != pimpl); + return pimpl->blob; } // ------------------------------------------------------------------------------------------------ const aiExportDataBlob* Exporter::GetOrphanedBlob() const { - const aiExportDataBlob* tmp = pimpl->blob; + ai_assert(nullptr != pimpl); + const aiExportDataBlob *tmp = pimpl->blob; pimpl->blob = nullptr; return tmp; } // ------------------------------------------------------------------------------------------------ size_t Exporter::GetExportFormatCount() const { + ai_assert(nullptr != pimpl); return pimpl->mExporters.size(); } // ------------------------------------------------------------------------------------------------ const aiExportFormatDesc* Exporter::GetExportFormatDescription( size_t index ) const { - if (index >= GetExportFormatCount()) { + ai_assert(nullptr != pimpl); + if (index >= GetExportFormatCount()) { return nullptr; } // Return from static storage if the requested index is built-in. - if (index < sizeof(gExporters) / sizeof(gExporters[0])) { - return &gExporters[index].mDescription; + if (index < pimpl->mExporters.size()) { + return &pimpl->mExporters[index].mDescription; } return &pimpl->mExporters[index].mDescription; @@ -510,7 +516,8 @@ const aiExportFormatDesc* Exporter::GetExportFormatDescription( size_t index ) c // ------------------------------------------------------------------------------------------------ aiReturn Exporter::RegisterExporter(const ExportFormatEntry& desc) { - for(const ExportFormatEntry& e : pimpl->mExporters) { + ai_assert(nullptr != pimpl); + for (const ExportFormatEntry &e : pimpl->mExporters) { if (!strcmp(e.mDescription.id,desc.mDescription.id)) { return aiReturn_FAILURE; } @@ -522,7 +529,8 @@ aiReturn Exporter::RegisterExporter(const ExportFormatEntry& desc) { // ------------------------------------------------------------------------------------------------ void Exporter::UnregisterExporter(const char* id) { - for(std::vector::iterator it = pimpl->mExporters.begin(); + ai_assert(nullptr != pimpl); + for (std::vector::iterator it = pimpl->mExporters.begin(); it != pimpl->mExporters.end(); ++it) { if (!strcmp((*it).mDescription.id,id)) { pimpl->mExporters.erase(it); diff --git a/code/Common/ImporterRegistry.cpp b/code/Common/ImporterRegistry.cpp index 32ac3b416..b9f28f035 100644 --- a/code/Common/ImporterRegistry.cpp +++ b/code/Common/ImporterRegistry.cpp @@ -197,6 +197,9 @@ corresponding preprocessor flag to selectively disable formats. #ifndef ASSIMP_BUILD_NO_MMD_IMPORTER # include "MMD/MMDImporter.h" #endif +#ifndef ASSIMP_BUILD_NO_M3D_IMPORTER +# include "M3D/M3DImporter.h" +#endif #ifndef ASSIMP_BUILD_NO_STEP_IMPORTER # include "Importer/StepFile/StepFileImporter.h" #endif @@ -223,6 +226,9 @@ void GetImporterInstanceList(std::vector< BaseImporter* >& out) #if (!defined ASSIMP_BUILD_NO_3DS_IMPORTER) out.push_back( new Discreet3DSImporter()); #endif +#if (!defined ASSIMP_BUILD_NO_M3D_IMPORTER) + out.push_back( new M3DImporter()); +#endif #if (!defined ASSIMP_BUILD_NO_MD3_IMPORTER) out.push_back( new MD3Importer()); #endif diff --git a/code/Common/PostStepRegistry.cpp b/code/Common/PostStepRegistry.cpp index ef58f8ddf..8ff4af040 100644 --- a/code/Common/PostStepRegistry.cpp +++ b/code/Common/PostStepRegistry.cpp @@ -131,11 +131,15 @@ corresponding preprocessor flag to selectively disable steps. #if (!defined ASSIMP_BUILD_NO_GLOBALSCALE_PROCESS) # include "PostProcessing/ScaleProcess.h" #endif +#if (!defined ASSIMP_BUILD_NO_ARMATUREPOPULATE_PROCESS) +# include "PostProcessing/ArmaturePopulate.h" +#endif #if (!defined ASSIMP_BUILD_NO_GENBOUNDINGBOXES_PROCESS) # include "PostProcessing/GenBoundingBoxesProcess.h" #endif + namespace Assimp { // ------------------------------------------------------------------------------------------------ @@ -180,6 +184,9 @@ void GetPostProcessingStepInstanceList(std::vector< BaseProcess* >& out) #if (!defined ASSIMP_BUILD_NO_GLOBALSCALE_PROCESS) out.push_back( new ScaleProcess()); #endif +#if (!defined ASSIMP_BUILD_NO_ARMATUREPOPULATE_PROCESS) + out.push_back( new ArmaturePopulate()); +#endif #if (!defined ASSIMP_BUILD_NO_PRETRANSFORMVERTICES_PROCESS) out.push_back( new PretransformVertices()); #endif diff --git a/code/Common/Version.cpp b/code/Common/Version.cpp index 868cfb06a..ea4c996f0 100644 --- a/code/Common/Version.cpp +++ b/code/Common/Version.cpp @@ -46,8 +46,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include "ScenePrivate.h" -static const unsigned int MajorVersion = 5; -static const unsigned int MinorVersion = 0; +#include "revision.h" // -------------------------------------------------------------------------------- // Legal information string - don't remove this. @@ -56,9 +55,9 @@ static const char* LEGAL_INFORMATION = "Open Asset Import Library (Assimp).\n" "A free C/C++ library to import various 3D file formats into applications\n\n" -"(c) 2008-2017, assimp team\n" +"(c) 2006-2019, assimp team\n" "License under the terms and conditions of the 3-clause BSD license\n" -"http://assimp.sourceforge.net\n" +"http://assimp.org\n" ; // ------------------------------------------------------------------------------------------------ @@ -67,16 +66,22 @@ ASSIMP_API const char* aiGetLegalString () { return LEGAL_INFORMATION; } +// ------------------------------------------------------------------------------------------------ +// Get Assimp patch version +ASSIMP_API unsigned int aiGetVersionPatch() { + return VER_PATCH; +} + // ------------------------------------------------------------------------------------------------ // Get Assimp minor version ASSIMP_API unsigned int aiGetVersionMinor () { - return MinorVersion; + return VER_MINOR; } // ------------------------------------------------------------------------------------------------ // Get Assimp major version ASSIMP_API unsigned int aiGetVersionMajor () { - return MajorVersion; + return VER_MAJOR; } // ------------------------------------------------------------------------------------------------ @@ -104,9 +109,6 @@ ASSIMP_API unsigned int aiGetCompileFlags () { return flags; } -// include current build revision, which is even updated from time to time -- :-) -#include "revision.h" - // ------------------------------------------------------------------------------------------------ ASSIMP_API unsigned int aiGetVersionRevision() { return GitVersion; diff --git a/code/Common/scene.cpp b/code/Common/scene.cpp index 2acb348d8..d15619acf 100644 --- a/code/Common/scene.cpp +++ b/code/Common/scene.cpp @@ -44,23 +44,23 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. aiNode::aiNode() : mName("") -, mParent(NULL) +, mParent(nullptr) , mNumChildren(0) -, mChildren(NULL) +, mChildren(nullptr) , mNumMeshes(0) -, mMeshes(NULL) -, mMetaData(NULL) { +, mMeshes(nullptr) +, mMetaData(nullptr) { // empty } aiNode::aiNode(const std::string& name) : mName(name) -, mParent(NULL) +, mParent(nullptr) , mNumChildren(0) -, mChildren(NULL) +, mChildren(nullptr) , mNumMeshes(0) -, mMeshes(NULL) -, mMetaData(NULL) { +, mMeshes(nullptr) +, mMetaData(nullptr) { // empty } @@ -68,7 +68,7 @@ aiNode::aiNode(const std::string& name) aiNode::~aiNode() { // delete all children recursively // to make sure we won't crash if the data is invalid ... - if (mChildren && mNumChildren) + if (mNumChildren && mChildren) { for (unsigned int a = 0; a < mNumChildren; a++) delete mChildren[a]; diff --git a/code/FBX/FBXCommon.h b/code/FBX/FBXCommon.h index e51644913..b28601c74 100644 --- a/code/FBX/FBXCommon.h +++ b/code/FBX/FBXCommon.h @@ -50,9 +50,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. namespace Assimp { namespace FBX { - const std::string NULL_RECORD = { // 13 null bytes - '\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0' - }; // who knows why + const std::string NULL_RECORD = { // 25 null bytes in 64-bit and 13 null bytes in 32-bit + '\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0', + '\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0','\0' + }; // who knows why, it looks like two integers 32/64 bit (compressed and uncompressed sizes?) + 1 byte (might be compression type?) const std::string SEPARATOR = {'\x00', '\x01'}; // for use inside strings const std::string MAGIC_NODE_TAG = "_$AssimpFbx$"; // from import const int64_t SECOND = 46186158000; // FBX's kTime unit diff --git a/code/FBX/FBXCompileConfig.h b/code/FBX/FBXCompileConfig.h index 3a3841fa5..03536a182 100644 --- a/code/FBX/FBXCompileConfig.h +++ b/code/FBX/FBXCompileConfig.h @@ -47,6 +47,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define INCLUDED_AI_FBX_COMPILECONFIG_H #include +#include // #if _MSC_VER > 1500 || (defined __GNUC___) @@ -54,16 +55,23 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # else # define fbx_unordered_map map # define fbx_unordered_multimap multimap +# define fbx_unordered_set set +# define fbx_unordered_multiset multiset #endif #ifdef ASSIMP_FBX_USE_UNORDERED_MULTIMAP # include +# include # if _MSC_VER > 1600 # define fbx_unordered_map unordered_map # define fbx_unordered_multimap unordered_multimap +# define fbx_unordered_set unordered_set +# define fbx_unordered_multiset unordered_multiset # else # define fbx_unordered_map tr1::unordered_map # define fbx_unordered_multimap tr1::unordered_multimap +# define fbx_unordered_set tr1::unordered_set +# define fbx_unordered_multiset tr1::unordered_multiset # endif #endif diff --git a/code/FBX/FBXConverter.cpp b/code/FBX/FBXConverter.cpp index 152be3277..d8a22d9f7 100644 --- a/code/FBX/FBXConverter.cpp +++ b/code/FBX/FBXConverter.cpp @@ -68,7 +68,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include - +#include +#include namespace Assimp { namespace FBX { @@ -77,7 +78,7 @@ namespace Assimp { #define MAGIC_NODE_TAG "_$AssimpFbx$" -#define CONVERT_FBX_TIME(time) static_cast(time) / 46186158000L +#define CONVERT_FBX_TIME(time) static_cast(time) / 46186158000LL FBXConverter::FBXConverter(aiScene* out, const Document& doc, bool removeEmptyBones ) : defaultMaterialIndex() @@ -96,6 +97,14 @@ namespace Assimp { // populate the node_anim_chain_bits map, which is needed // to determine which nodes need to be generated. ConvertAnimations(); + // Embedded textures in FBX could be connected to nothing but to itself, + // for instance Texture -> Video connection only but not to the main graph, + // The idea here is to traverse all objects to find these Textures and convert them, + // so later during material conversion it will find converted texture in the textures_converted array. + if (doc.Settings().readTextures) + { + ConvertOrphantEmbeddedTextures(); + } ConvertRootNode(); if (doc.Settings().readAllMaterials) { @@ -145,7 +154,7 @@ namespace Assimp { out->mRootNode->mName.Set(unique_name); // root has ID 0 - ConvertNodes(0L, *out->mRootNode); + ConvertNodes(0L, out->mRootNode, out->mRootNode); } static std::string getAncestorBaseName(const aiNode* node) @@ -179,8 +188,11 @@ namespace Assimp { GetUniqueName(original_name, unique_name); return unique_name; } - - void FBXConverter::ConvertNodes(uint64_t id, aiNode& parent, const aiMatrix4x4& parent_transform) { + /// todo: pre-build node hierarchy + /// todo: get bone from stack + /// todo: make map of aiBone* to aiNode* + /// then update convert clusters to the new format + void FBXConverter::ConvertNodes(uint64_t id, aiNode *parent, aiNode *root_node) { const std::vector& conns = doc.GetConnectionsByDestinationSequenced(id, "Model"); std::vector nodes; @@ -191,62 +203,69 @@ namespace Assimp { try { for (const Connection* con : conns) { - // ignore object-property links if (con->PropertyName().length()) { - continue; + // really important we document why this is ignored. + FBXImporter::LogInfo("ignoring property link - no docs on why this is ignored"); + continue; //? } + // convert connection source object into Object base class const Object* const object = con->SourceObject(); if (nullptr == object) { - FBXImporter::LogWarn("failed to convert source object for Model link"); + FBXImporter::LogError("failed to convert source object for Model link"); continue; } + // FBX Model::Cube, Model::Bone001, etc elements + // This detects if we can cast the object into this model structure. const Model* const model = dynamic_cast(object); if (nullptr != model) { nodes_chain.clear(); post_nodes_chain.clear(); - aiMatrix4x4 new_abs_transform = parent_transform; - - std::string unique_name = MakeUniqueNodeName(model, parent); - + aiMatrix4x4 new_abs_transform = parent->mTransformation; + std::string node_name = FixNodeName(model->Name()); // even though there is only a single input node, the design of // assimp (or rather: the complicated transformation chain that // is employed by fbx) means that we may need multiple aiNode's // to represent a fbx node's transformation. - const bool need_additional_node = GenerateTransformationNodeChain(*model, unique_name, nodes_chain, post_nodes_chain); + + // generate node transforms - this includes pivot data + // if need_additional_node is true then you t + const bool need_additional_node = GenerateTransformationNodeChain(*model, node_name, nodes_chain, post_nodes_chain); + + // assert that for the current node we must have at least a single transform ai_assert(nodes_chain.size()); if (need_additional_node) { - nodes_chain.push_back(new aiNode(unique_name)); + nodes_chain.push_back(new aiNode(node_name)); } //setup metadata on newest node SetupNodeMetadata(*model, *nodes_chain.back()); // link all nodes in a row - aiNode* last_parent = &parent; - for (aiNode* prenode : nodes_chain) { - ai_assert(prenode); + aiNode* last_parent = parent; + for (aiNode* child : nodes_chain) { + ai_assert(child); - if (last_parent != &parent) { + if (last_parent != parent) { last_parent->mNumChildren = 1; last_parent->mChildren = new aiNode*[1]; - last_parent->mChildren[0] = prenode; + last_parent->mChildren[0] = child; } - prenode->mParent = last_parent; - last_parent = prenode; + child->mParent = last_parent; + last_parent = child; - new_abs_transform *= prenode->mTransformation; + new_abs_transform *= child->mTransformation; } // attach geometry - ConvertModel(*model, *nodes_chain.back(), new_abs_transform); + ConvertModel(*model, nodes_chain.back(), root_node, new_abs_transform); // check if there will be any child nodes const std::vector& child_conns @@ -258,7 +277,7 @@ namespace Assimp { for (aiNode* postnode : post_nodes_chain) { ai_assert(postnode); - if (last_parent != &parent) { + if (last_parent != parent) { last_parent->mNumChildren = 1; last_parent->mChildren = new aiNode*[1]; last_parent->mChildren[0] = postnode; @@ -280,15 +299,15 @@ namespace Assimp { ); } - // attach sub-nodes (if any) - ConvertNodes(model->ID(), *last_parent, new_abs_transform); + // recursion call - child nodes + ConvertNodes(model->ID(), last_parent, root_node); if (doc.Settings().readLights) { - ConvertLights(*model, unique_name); + ConvertLights(*model, node_name); } if (doc.Settings().readCameras) { - ConvertCameras(*model, unique_name); + ConvertCameras(*model, node_name); } nodes.push_back(nodes_chain.front()); @@ -297,11 +316,17 @@ namespace Assimp { } if (nodes.size()) { - parent.mChildren = new aiNode*[nodes.size()](); - parent.mNumChildren = static_cast(nodes.size()); + parent->mChildren = new aiNode*[nodes.size()](); + parent->mNumChildren = static_cast(nodes.size()); - std::swap_ranges(nodes.begin(), nodes.end(), parent.mChildren); + std::swap_ranges(nodes.begin(), nodes.end(), parent->mChildren); } + else + { + parent->mNumChildren = 0; + parent->mChildren = nullptr; + } + } catch (std::exception&) { Util::delete_fun deleter; @@ -803,7 +828,7 @@ namespace Assimp { // is_complex needs to be consistent with NeedsComplexTransformationChain() // or the interplay between this code and the animation converter would // not be guaranteed. - ai_assert(NeedsComplexTransformationChain(model) == ((chainBits & chainMaskComplex) != 0)); + //ai_assert(NeedsComplexTransformationChain(model) == ((chainBits & chainMaskComplex) != 0)); // now, if we have more than just Translation, Scaling and Rotation, // we need to generate a full node chain to accommodate for assimp's @@ -905,7 +930,8 @@ namespace Assimp { } } - void FBXConverter::ConvertModel(const Model& model, aiNode& nd, const aiMatrix4x4& node_global_transform) + void FBXConverter::ConvertModel(const Model &model, aiNode *parent, aiNode *root_node, + const aiMatrix4x4 &absolute_transform) { const std::vector& geos = model.GetGeometry(); @@ -917,11 +943,12 @@ namespace Assimp { const MeshGeometry* const mesh = dynamic_cast(geo); const LineGeometry* const line = dynamic_cast(geo); if (mesh) { - const std::vector& indices = ConvertMesh(*mesh, model, node_global_transform, nd); + const std::vector& indices = ConvertMesh(*mesh, model, parent, root_node, + absolute_transform); std::copy(indices.begin(), indices.end(), std::back_inserter(meshes)); } else if (line) { - const std::vector& indices = ConvertLine(*line, model, node_global_transform, nd); + const std::vector& indices = ConvertLine(*line, model, parent, root_node); std::copy(indices.begin(), indices.end(), std::back_inserter(meshes)); } else { @@ -930,15 +957,16 @@ namespace Assimp { } if (meshes.size()) { - nd.mMeshes = new unsigned int[meshes.size()](); - nd.mNumMeshes = static_cast(meshes.size()); + parent->mMeshes = new unsigned int[meshes.size()](); + parent->mNumMeshes = static_cast(meshes.size()); - std::swap_ranges(meshes.begin(), meshes.end(), nd.mMeshes); + std::swap_ranges(meshes.begin(), meshes.end(), parent->mMeshes); } } - std::vector FBXConverter::ConvertMesh(const MeshGeometry& mesh, const Model& model, - const aiMatrix4x4& node_global_transform, aiNode& nd) + std::vector + FBXConverter::ConvertMesh(const MeshGeometry &mesh, const Model &model, aiNode *parent, aiNode *root_node, + const aiMatrix4x4 &absolute_transform) { std::vector temp; @@ -962,18 +990,18 @@ namespace Assimp { const MatIndexArray::value_type base = mindices[0]; for (MatIndexArray::value_type index : mindices) { if (index != base) { - return ConvertMeshMultiMaterial(mesh, model, node_global_transform, nd); + return ConvertMeshMultiMaterial(mesh, model, parent, root_node, absolute_transform); } } } // faster code-path, just copy the data - temp.push_back(ConvertMeshSingleMaterial(mesh, model, node_global_transform, nd)); + temp.push_back(ConvertMeshSingleMaterial(mesh, model, absolute_transform, parent, root_node)); return temp; } std::vector FBXConverter::ConvertLine(const LineGeometry& line, const Model& model, - const aiMatrix4x4& node_global_transform, aiNode& nd) + aiNode *parent, aiNode *root_node) { std::vector temp; @@ -984,7 +1012,7 @@ namespace Assimp { return temp; } - aiMesh* const out_mesh = SetupEmptyMesh(line, nd); + aiMesh* const out_mesh = SetupEmptyMesh(line, root_node); out_mesh->mPrimitiveTypes |= aiPrimitiveType_LINE; // copy vertices @@ -1019,7 +1047,7 @@ namespace Assimp { return temp; } - aiMesh* FBXConverter::SetupEmptyMesh(const Geometry& mesh, aiNode& nd) + aiMesh* FBXConverter::SetupEmptyMesh(const Geometry& mesh, aiNode *parent) { aiMesh* const out_mesh = new aiMesh(); meshes.push_back(out_mesh); @@ -1036,17 +1064,18 @@ namespace Assimp { } else { - out_mesh->mName = nd.mName; + out_mesh->mName = parent->mName; } return out_mesh; } - unsigned int FBXConverter::ConvertMeshSingleMaterial(const MeshGeometry& mesh, const Model& model, - const aiMatrix4x4& node_global_transform, aiNode& nd) + unsigned int FBXConverter::ConvertMeshSingleMaterial(const MeshGeometry &mesh, const Model &model, + const aiMatrix4x4 &absolute_transform, aiNode *parent, + aiNode *root_node) { const MatIndexArray& mindices = mesh.GetMaterialIndices(); - aiMesh* const out_mesh = SetupEmptyMesh(mesh, nd); + aiMesh* const out_mesh = SetupEmptyMesh(mesh, parent); const std::vector& vertices = mesh.GetVertices(); const std::vector& faces = mesh.GetFaceIndexCounts(); @@ -1113,7 +1142,7 @@ namespace Assimp { binormals = &tempBinormals; } else { - binormals = NULL; + binormals = nullptr; } } @@ -1163,8 +1192,9 @@ namespace Assimp { ConvertMaterialForMesh(out_mesh, model, mesh, mindices[0]); } - if (doc.Settings().readWeights && mesh.DeformerSkin() != NULL) { - ConvertWeights(out_mesh, model, mesh, node_global_transform, NO_MATERIAL_SEPARATION); + if (doc.Settings().readWeights && mesh.DeformerSkin() != nullptr) { + ConvertWeights(out_mesh, model, mesh, absolute_transform, parent, root_node, NO_MATERIAL_SEPARATION, + nullptr); } std::vector animMeshes; @@ -1209,8 +1239,10 @@ namespace Assimp { return static_cast(meshes.size() - 1); } - std::vector FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry& mesh, const Model& model, - const aiMatrix4x4& node_global_transform, aiNode& nd) + std::vector + FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry &mesh, const Model &model, aiNode *parent, + aiNode *root_node, + const aiMatrix4x4 &absolute_transform) { const MatIndexArray& mindices = mesh.GetMaterialIndices(); ai_assert(mindices.size()); @@ -1221,7 +1253,7 @@ namespace Assimp { for (MatIndexArray::value_type index : mindices) { if (had.find(index) == had.end()) { - indices.push_back(ConvertMeshMultiMaterial(mesh, model, index, node_global_transform, nd)); + indices.push_back(ConvertMeshMultiMaterial(mesh, model, index, parent, root_node, absolute_transform)); had.insert(index); } } @@ -1229,18 +1261,18 @@ namespace Assimp { return indices; } - unsigned int FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry& mesh, const Model& model, - MatIndexArray::value_type index, - const aiMatrix4x4& node_global_transform, - aiNode& nd) + unsigned int FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry &mesh, const Model &model, + MatIndexArray::value_type index, + aiNode *parent, aiNode *root_node, + const aiMatrix4x4 &absolute_transform) { - aiMesh* const out_mesh = SetupEmptyMesh(mesh, nd); + aiMesh* const out_mesh = SetupEmptyMesh(mesh, parent); const MatIndexArray& mindices = mesh.GetMaterialIndices(); const std::vector& vertices = mesh.GetVertices(); const std::vector& faces = mesh.GetFaceIndexCounts(); - const bool process_weights = doc.Settings().readWeights && mesh.DeformerSkin() != NULL; + const bool process_weights = doc.Settings().readWeights && mesh.DeformerSkin() != nullptr; unsigned int count_faces = 0; unsigned int count_vertices = 0; @@ -1300,7 +1332,7 @@ namespace Assimp { binormals = &tempBinormals; } else { - binormals = NULL; + binormals = nullptr; } } @@ -1399,7 +1431,7 @@ namespace Assimp { ConvertMaterialForMesh(out_mesh, model, mesh, index); if (process_weights) { - ConvertWeights(out_mesh, model, mesh, node_global_transform, index, &reverseMapping); + ConvertWeights(out_mesh, model, mesh, absolute_transform, parent, root_node, index, &reverseMapping); } std::vector animMeshes; @@ -1449,10 +1481,10 @@ namespace Assimp { return static_cast(meshes.size() - 1); } - void FBXConverter::ConvertWeights(aiMesh* out, const Model& model, const MeshGeometry& geo, - const aiMatrix4x4& node_global_transform, - unsigned int materialIndex, - std::vector* outputVertStartIndices) + void FBXConverter::ConvertWeights(aiMesh *out, const Model &model, const MeshGeometry &geo, + const aiMatrix4x4 &absolute_transform, + aiNode *parent, aiNode *root_node, unsigned int materialIndex, + std::vector *outputVertStartIndices) { ai_assert(geo.DeformerSkin()); @@ -1463,13 +1495,12 @@ namespace Assimp { const Skin& sk = *geo.DeformerSkin(); std::vector bones; - bones.reserve(sk.Clusters().size()); const bool no_mat_check = materialIndex == NO_MATERIAL_SEPARATION; ai_assert(no_mat_check || outputVertStartIndices); try { - + // iterate over the sub deformers for (const Cluster* cluster : sk.Clusters()) { ai_assert(cluster); @@ -1483,15 +1514,16 @@ namespace Assimp { index_out_indices.clear(); out_indices.clear(); + // now check if *any* of these weights is contained in the output mesh, // taking notes so we don't need to do it twice. for (WeightIndexArray::value_type index : indices) { unsigned int count = 0; const unsigned int* const out_idx = geo.ToOutputVertexIndex(index, count); - // ToOutputVertexIndex only returns NULL if index is out of bounds + // ToOutputVertexIndex only returns nullptr if index is out of bounds // which should never happen - ai_assert(out_idx != NULL); + ai_assert(out_idx != nullptr); index_out_indices.push_back(no_index_sentinel); count_out_indices.push_back(0); @@ -1520,68 +1552,107 @@ namespace Assimp { } } } - + // if we found at least one, generate the output bones // XXX this could be heavily simplified by collecting the bone // data in a single step. - ConvertCluster(bones, model, *cluster, out_indices, index_out_indices, - count_out_indices, node_global_transform); + ConvertCluster(bones, cluster, out_indices, index_out_indices, + count_out_indices, absolute_transform, parent, root_node); } + + bone_map.clear(); } - catch (std::exception&) { + catch (std::exception&e) { std::for_each(bones.begin(), bones.end(), Util::delete_fun()); throw; } if (bones.empty()) { + out->mBones = nullptr; + out->mNumBones = 0; return; + } else { + out->mBones = new aiBone *[bones.size()](); + out->mNumBones = static_cast(bones.size()); + + std::swap_ranges(bones.begin(), bones.end(), out->mBones); } - - out->mBones = new aiBone*[bones.size()](); - out->mNumBones = static_cast(bones.size()); - - std::swap_ranges(bones.begin(), bones.end(), out->mBones); } - void FBXConverter::ConvertCluster(std::vector& bones, const Model& /*model*/, const Cluster& cl, - std::vector& out_indices, - std::vector& index_out_indices, - std::vector& count_out_indices, - const aiMatrix4x4& node_global_transform) + const aiNode* FBXConverter::GetNodeByName( const aiString& name, aiNode *current_node ) { + aiNode * iter = current_node; + //printf("Child count: %d", iter->mNumChildren); + return iter; + } - aiBone* const bone = new aiBone(); - bones.push_back(bone); + void FBXConverter::ConvertCluster(std::vector &local_mesh_bones, const Cluster *cl, + std::vector &out_indices, std::vector &index_out_indices, + std::vector &count_out_indices, const aiMatrix4x4 &absolute_transform, + aiNode *parent, aiNode *root_node) { + ai_assert(cl); // make sure cluster valid + std::string deformer_name = cl->TargetNode()->Name(); + aiString bone_name = aiString(FixNodeName(deformer_name)); - bone->mName = FixNodeName(cl.TargetNode()->Name()); + aiBone *bone = nullptr; - bone->mOffsetMatrix = cl.TransformLink(); - bone->mOffsetMatrix.Inverse(); + if (bone_map.count(deformer_name)) { + std::cout << "retrieved bone from lookup " << bone_name.C_Str() << ". Deformer: " << deformer_name + << std::endl; + bone = bone_map[deformer_name]; + } else { + std::cout << "created new bone " << bone_name.C_Str() << ". Deformer: " << deformer_name << std::endl; + bone = new aiBone(); + bone->mName = bone_name; - bone->mOffsetMatrix = bone->mOffsetMatrix * node_global_transform; + // store local transform link for post processing + bone->mOffsetMatrix = cl->TransformLink(); + bone->mOffsetMatrix.Inverse(); - bone->mNumWeights = static_cast(out_indices.size()); - aiVertexWeight* cursor = bone->mWeights = new aiVertexWeight[out_indices.size()]; + aiMatrix4x4 matrix = (aiMatrix4x4)absolute_transform; - const size_t no_index_sentinel = std::numeric_limits::max(); - const WeightArray& weights = cl.GetWeights(); + bone->mOffsetMatrix = bone->mOffsetMatrix * matrix; // * mesh_offset - const size_t c = index_out_indices.size(); - for (size_t i = 0; i < c; ++i) { - const size_t index_index = index_out_indices[i]; - if (index_index == no_index_sentinel) { - continue; + // + // Now calculate the aiVertexWeights + // + + aiVertexWeight *cursor = nullptr; + + bone->mNumWeights = static_cast(out_indices.size()); + cursor = bone->mWeights = new aiVertexWeight[out_indices.size()]; + + const size_t no_index_sentinel = std::numeric_limits::max(); + const WeightArray& weights = cl->GetWeights(); + + const size_t c = index_out_indices.size(); + for (size_t i = 0; i < c; ++i) { + const size_t index_index = index_out_indices[i]; + + if (index_index == no_index_sentinel) { + continue; + } + + const size_t cc = count_out_indices[i]; + for (size_t j = 0; j < cc; ++j) { + // cursor runs from first element relative to the start + // or relative to the start of the next indexes. + aiVertexWeight& out_weight = *cursor++; + + out_weight.mVertexId = static_cast(out_indices[index_index + j]); + out_weight.mWeight = weights[i]; + } } - const size_t cc = count_out_indices[i]; - for (size_t j = 0; j < cc; ++j) { - aiVertexWeight& out_weight = *cursor++; - - out_weight.mVertexId = static_cast(out_indices[index_index + j]); - out_weight.mWeight = weights[i]; - } + bone_map.insert(std::pair(deformer_name, bone)); } + + std::cout << "bone research: Indicies size: " << out_indices.size() << std::endl; + + // lookup must be populated in case something goes wrong + // this also allocates bones to mesh instance outside + local_mesh_bones.push_back(bone); } void FBXConverter::ConvertMaterialForMesh(aiMesh* out, const Model& model, const MeshGeometry& geo, @@ -1711,7 +1782,7 @@ namespace Assimp { bool textureReady = false; //tells if our texture is ready (if it was loaded or if it was found) unsigned int index; - VideoMap::const_iterator it = textures_converted.find(media); + VideoMap::const_iterator it = textures_converted.find(*media); if (it != textures_converted.end()) { index = (*it).second; textureReady = true; @@ -1719,7 +1790,7 @@ namespace Assimp { else { if (media->ContentLength() > 0) { index = ConvertVideo(*media); - textures_converted[media] = index; + textures_converted[*media] = index; textureReady = true; } } @@ -2243,13 +2314,13 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa if (media != nullptr && media->ContentLength() > 0) { unsigned int index; - VideoMap::const_iterator it = textures_converted.find(media); + VideoMap::const_iterator it = textures_converted.find(*media); if (it != textures_converted.end()) { index = (*it).second; } else { index = ConvertVideo(*media); - textures_converted[media] = index; + textures_converted[*media] = index; } // setup texture reference string (copied from ColladaLoader::FindFilenameForEffectTexture) @@ -2677,7 +2748,7 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa // sanity check whether the input is ok static void validateAnimCurveNodes(const std::vector& curves, bool strictMode) { - const Object* target(NULL); + const Object* target(nullptr); for (const AnimationCurveNode* node : curves) { if (!target) { target = node->Target(); @@ -2708,7 +2779,7 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa #ifdef ASSIMP_BUILD_DEBUG validateAnimCurveNodes(curves, doc.Settings().strictMode); #endif - const AnimationCurveNode* curve_node = NULL; + const AnimationCurveNode* curve_node = nullptr; for (const AnimationCurveNode* node : curves) { ai_assert(node); @@ -3556,7 +3627,7 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa ai_assert(!out->mMeshes); ai_assert(!out->mNumMeshes); - // note: the trailing () ensures initialization with NULL - not + // note: the trailing () ensures initialization with nullptr - not // many C++ users seem to know this, so pointing it out to avoid // confusion why this code works. @@ -3603,6 +3674,47 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa } } + void FBXConverter::ConvertOrphantEmbeddedTextures() + { + // in C++14 it could be: + // for (auto&& [id, object] : objects) + for (auto&& id_and_object : doc.Objects()) + { + auto&& id = std::get<0>(id_and_object); + auto&& object = std::get<1>(id_and_object); + // If an object doesn't have parent + if (doc.ConnectionsBySource().count(id) == 0) + { + const Texture* realTexture = nullptr; + try + { + const auto& element = object->GetElement(); + const Token& key = element.KeyToken(); + const char* obtype = key.begin(); + const size_t length = static_cast(key.end() - key.begin()); + if (strncmp(obtype, "Texture", length) == 0) + { + const Texture* texture = static_cast(object->Get()); + if (texture->Media() && texture->Media()->ContentLength() > 0) + { + realTexture = texture; + } + } + } + catch (...) + { + // do nothing + } + if (realTexture) + { + const Video* media = realTexture->Media(); + unsigned int index = ConvertVideo(*media); + textures_converted[*media] = index; + } + } + } + } + // ------------------------------------------------------------------------------------------------ void ConvertToAssimpScene(aiScene* out, const Document& doc, bool removeEmptyBones) { diff --git a/code/FBX/FBXConverter.h b/code/FBX/FBXConverter.h index 77ced1950..46693bdca 100644 --- a/code/FBX/FBXConverter.h +++ b/code/FBX/FBXConverter.h @@ -123,7 +123,7 @@ private: // ------------------------------------------------------------------------------------------------ // collect and assign child nodes - void ConvertNodes(uint64_t id, aiNode& parent, const aiMatrix4x4& parent_transform = aiMatrix4x4()); + void ConvertNodes(uint64_t id, aiNode *parent, aiNode *root_node); // ------------------------------------------------------------------------------------------------ void ConvertLights(const Model& model, const std::string &orig_name ); @@ -179,32 +179,35 @@ private: void SetupNodeMetadata(const Model& model, aiNode& nd); // ------------------------------------------------------------------------------------------------ - void ConvertModel(const Model& model, aiNode& nd, const aiMatrix4x4& node_global_transform); + void ConvertModel(const Model &model, aiNode *parent, aiNode *root_node, + const aiMatrix4x4 &absolute_transform); // ------------------------------------------------------------------------------------------------ // MeshGeometry -> aiMesh, return mesh index + 1 or 0 if the conversion failed - std::vector ConvertMesh(const MeshGeometry& mesh, const Model& model, - const aiMatrix4x4& node_global_transform, aiNode& nd); + std::vector + ConvertMesh(const MeshGeometry &mesh, const Model &model, aiNode *parent, aiNode *root_node, + const aiMatrix4x4 &absolute_transform); // ------------------------------------------------------------------------------------------------ std::vector ConvertLine(const LineGeometry& line, const Model& model, - const aiMatrix4x4& node_global_transform, aiNode& nd); + aiNode *parent, aiNode *root_node); // ------------------------------------------------------------------------------------------------ - aiMesh* SetupEmptyMesh(const Geometry& mesh, aiNode& nd); + aiMesh* SetupEmptyMesh(const Geometry& mesh, aiNode *parent); // ------------------------------------------------------------------------------------------------ - unsigned int ConvertMeshSingleMaterial(const MeshGeometry& mesh, const Model& model, - const aiMatrix4x4& node_global_transform, aiNode& nd); + unsigned int ConvertMeshSingleMaterial(const MeshGeometry &mesh, const Model &model, + const aiMatrix4x4 &absolute_transform, aiNode *parent, + aiNode *root_node); // ------------------------------------------------------------------------------------------------ - std::vector ConvertMeshMultiMaterial(const MeshGeometry& mesh, const Model& model, - const aiMatrix4x4& node_global_transform, aiNode& nd); + std::vector + ConvertMeshMultiMaterial(const MeshGeometry &mesh, const Model &model, aiNode *parent, aiNode *root_node, + const aiMatrix4x4 &absolute_transform); // ------------------------------------------------------------------------------------------------ - unsigned int ConvertMeshMultiMaterial(const MeshGeometry& mesh, const Model& model, - MatIndexArray::value_type index, - const aiMatrix4x4& node_global_transform, aiNode& nd); + unsigned int ConvertMeshMultiMaterial(const MeshGeometry &mesh, const Model &model, MatIndexArray::value_type index, + aiNode *parent, aiNode *root_node, const aiMatrix4x4 &absolute_transform); // ------------------------------------------------------------------------------------------------ static const unsigned int NO_MATERIAL_SEPARATION = /* std::numeric_limits::max() */ @@ -217,17 +220,17 @@ private: * - outputVertStartIndices is only used when a material index is specified, it gives for * each output vertex the DOM index it maps to. */ - void ConvertWeights(aiMesh* out, const Model& model, const MeshGeometry& geo, - const aiMatrix4x4& node_global_transform = aiMatrix4x4(), - unsigned int materialIndex = NO_MATERIAL_SEPARATION, - std::vector* outputVertStartIndices = NULL); - + void ConvertWeights(aiMesh *out, const Model &model, const MeshGeometry &geo, const aiMatrix4x4 &absolute_transform, + aiNode *parent = NULL, aiNode *root_node = NULL, + unsigned int materialIndex = NO_MATERIAL_SEPARATION, + std::vector *outputVertStartIndices = NULL); + // lookup + static const aiNode* GetNodeByName( const aiString& name, aiNode *current_node ); // ------------------------------------------------------------------------------------------------ - void ConvertCluster(std::vector& bones, const Model& /*model*/, const Cluster& cl, - std::vector& out_indices, - std::vector& index_out_indices, - std::vector& count_out_indices, - const aiMatrix4x4& node_global_transform); + void ConvertCluster(std::vector &local_mesh_bones, const Cluster *cl, + std::vector &out_indices, std::vector &index_out_indices, + std::vector &count_out_indices, const aiMatrix4x4 &absolute_transform, + aiNode *parent, aiNode *root_node); // ------------------------------------------------------------------------------------------------ void ConvertMaterialForMesh(aiMesh* out, const Model& model, const MeshGeometry& geo, @@ -424,6 +427,10 @@ private: // copy generated meshes, animations, lights, cameras and textures to the output scene void TransferDataToScene(); + // ------------------------------------------------------------------------------------------------ + // FBX file could have embedded textures not connected to anything + void ConvertOrphantEmbeddedTextures(); + private: // 0: not assigned yet, others: index is value - 1 unsigned int defaultMaterialIndex; @@ -435,27 +442,47 @@ private: std::vector cameras; std::vector textures; - using MaterialMap = std::map; + using MaterialMap = std::fbx_unordered_map; MaterialMap materials_converted; - using VideoMap = std::map; + using VideoMap = std::fbx_unordered_map; VideoMap textures_converted; - using MeshMap = std::map >; + using MeshMap = std::fbx_unordered_map >; MeshMap meshes_converted; // fixed node name -> which trafo chain components have animations? - using NodeAnimBitMap = std::map ; + using NodeAnimBitMap = std::fbx_unordered_map ; NodeAnimBitMap node_anim_chain_bits; // number of nodes with the same name - using NodeNameCache = std::unordered_map; + using NodeNameCache = std::fbx_unordered_map; NodeNameCache mNodeNames; + // Deformer name is not the same as a bone name - it does contain the bone name though :) + // Deformer names in FBX are always unique in an FBX file. + std::map bone_map; + double anim_fps; aiScene* const out; const FBX::Document& doc; + + static void BuildBoneList(aiNode *current_node, const aiNode *root_node, const aiScene *scene, + std::vector& bones); + + void BuildBoneStack(aiNode *current_node, const aiNode *root_node, const aiScene *scene, + const std::vector &bones, + std::map &bone_stack, + std::vector &node_stack ); + + static void BuildNodeList(aiNode *current_node, std::vector &nodes); + + static aiNode *GetNodeFromStack(const aiString &node_name, std::vector &nodes); + + static aiNode *GetArmatureRoot(aiNode *bone_node, std::vector &bone_list); + + static bool IsBoneNode(const aiString &bone_name, std::vector &bones); }; } diff --git a/code/FBX/FBXDocument.h b/code/FBX/FBXDocument.h index 18e5c38f1..a60d7d9ef 100644 --- a/code/FBX/FBXDocument.h +++ b/code/FBX/FBXDocument.h @@ -637,6 +637,20 @@ public: return ptr; } + bool operator==(const Video& other) const + { + return ( + type == other.type + && relativeFileName == other.relativeFileName + && fileName == other.fileName + ); + } + + bool operator<(const Video& other) const + { + return std::tie(type, relativeFileName, fileName) < std::tie(other.type, other.relativeFileName, other.fileName); + } + private: std::string type; std::string relativeFileName; @@ -1005,10 +1019,10 @@ public: // during their entire lifetime (Document). FBX files have // up to many thousands of objects (most of which we never use), // so the memory overhead for them should be kept at a minimum. -typedef std::map ObjectMap; +typedef std::fbx_unordered_map ObjectMap; typedef std::fbx_unordered_map > PropertyTemplateMap; -typedef std::multimap ConnectionMap; +typedef std::fbx_unordered_multimap ConnectionMap; /** DOM class for global document settings, a single instance per document can * be accessed via Document.Globals(). */ @@ -1177,4 +1191,25 @@ private: } // Namespace FBX } // Namespace Assimp +namespace std +{ + template <> + struct hash + { + std::size_t operator()(const Assimp::FBX::Video& video) const + { + using std::size_t; + using std::hash; + using std::string; + + size_t res = 17; + res = res * 31 + hash()(video.Name()); + res = res * 31 + hash()(video.RelativeFilename()); + res = res * 31 + hash()(video.Type()); + + return res; + } + }; +} + #endif // INCLUDED_AI_FBX_DOCUMENT_H diff --git a/code/FBX/FBXExportNode.cpp b/code/FBX/FBXExportNode.cpp index 06c89cee4..9b29995cc 100644 --- a/code/FBX/FBXExportNode.cpp +++ b/code/FBX/FBXExportNode.cpp @@ -325,9 +325,9 @@ void FBX::Node::BeginBinary(Assimp::StreamWriterLE &s) this->start_pos = s.Tell(); // placeholders for end pos and property section info - s.PutU4(0); // end pos - s.PutU4(0); // number of properties - s.PutU4(0); // total property section length + s.PutU8(0); // end pos + s.PutU8(0); // number of properties + s.PutU8(0); // total property section length // node name s.PutU1(uint8_t(name.size())); // length of node name @@ -352,9 +352,9 @@ void FBX::Node::EndPropertiesBinary( size_t pos = s.Tell(); ai_assert(pos > property_start); size_t property_section_size = pos - property_start; - s.Seek(start_pos + 4); - s.PutU4(uint32_t(num_properties)); - s.PutU4(uint32_t(property_section_size)); + s.Seek(start_pos + 8); // 8 bytes of uint64_t of end_pos + s.PutU8(num_properties); + s.PutU8(property_section_size); s.Seek(pos); } @@ -375,7 +375,7 @@ void FBX::Node::EndBinary( // now go back and write initial pos this->end_pos = s.Tell(); s.Seek(start_pos); - s.PutU4(uint32_t(end_pos)); + s.PutU8(end_pos); s.Seek(end_pos); } diff --git a/code/FBX/FBXExporter.cpp b/code/FBX/FBXExporter.cpp index 9316dc4f0..9767f9a0a 100644 --- a/code/FBX/FBXExporter.cpp +++ b/code/FBX/FBXExporter.cpp @@ -81,8 +81,8 @@ using namespace Assimp::FBX; // some constants that we'll use for writing metadata namespace Assimp { namespace FBX { - const std::string EXPORT_VERSION_STR = "7.4.0"; - const uint32_t EXPORT_VERSION_INT = 7400; // 7.4 == 2014/2015 + const std::string EXPORT_VERSION_STR = "7.5.0"; + const uint32_t EXPORT_VERSION_INT = 7500; // 7.5 == 2016+ // FBX files have some hashed values that depend on the creation time field, // but for now we don't actually know how to generate these. // what we can do is set them to a known-working version. diff --git a/code/FBX/FBXImporter.cpp b/code/FBX/FBXImporter.cpp index c8c1a6853..afcc1ddc7 100644 --- a/code/FBX/FBXImporter.cpp +++ b/code/FBX/FBXImporter.cpp @@ -48,26 +48,26 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "FBXImporter.h" -#include "FBXTokenizer.h" -#include "FBXParser.h" -#include "FBXUtil.h" -#include "FBXDocument.h" #include "FBXConverter.h" +#include "FBXDocument.h" +#include "FBXParser.h" +#include "FBXTokenizer.h" +#include "FBXUtil.h" -#include #include -#include +#include #include +#include namespace Assimp { -template<> -const char* LogFunctions::Prefix() { - static auto prefix = "FBX: "; - return prefix; +template <> +const char *LogFunctions::Prefix() { + static auto prefix = "FBX: "; + return prefix; } -} +} // namespace Assimp using namespace Assimp; using namespace Assimp::Formatter; @@ -76,131 +76,123 @@ using namespace Assimp::FBX; namespace { static const aiImporterDesc desc = { - "Autodesk FBX Importer", - "", - "", - "", - aiImporterFlags_SupportTextFlavour, - 0, - 0, - 0, - 0, - "fbx" + "Autodesk FBX Importer", + "", + "", + "", + aiImporterFlags_SupportTextFlavour, + 0, + 0, + 0, + 0, + "fbx" }; } // ------------------------------------------------------------------------------------------------ // Constructor to be privately used by #Importer -FBXImporter::FBXImporter() -{ +FBXImporter::FBXImporter() { } // ------------------------------------------------------------------------------------------------ // Destructor, private as well -FBXImporter::~FBXImporter() -{ +FBXImporter::~FBXImporter() { } // ------------------------------------------------------------------------------------------------ // Returns whether the class can handle the format of the given file. -bool FBXImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const -{ - const std::string& extension = GetExtension(pFile); - if (extension == std::string( desc.mFileExtensions ) ) { - return true; - } +bool FBXImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const { + const std::string &extension = GetExtension(pFile); + if (extension == std::string(desc.mFileExtensions)) { + return true; + } - else if ((!extension.length() || checkSig) && pIOHandler) { - // at least ASCII-FBX files usually have a 'FBX' somewhere in their head - const char* tokens[] = {"fbx"}; - return SearchFileHeaderForToken(pIOHandler,pFile,tokens,1); - } - return false; + else if ((!extension.length() || checkSig) && pIOHandler) { + // at least ASCII-FBX files usually have a 'FBX' somewhere in their head + const char *tokens[] = { "fbx" }; + return SearchFileHeaderForToken(pIOHandler, pFile, tokens, 1); + } + return false; } // ------------------------------------------------------------------------------------------------ // List all extensions handled by this loader -const aiImporterDesc* FBXImporter::GetInfo () const -{ - return &desc; +const aiImporterDesc *FBXImporter::GetInfo() const { + return &desc; } // ------------------------------------------------------------------------------------------------ // Setup configuration properties for the loader -void FBXImporter::SetupProperties(const Importer* pImp) -{ - settings.readAllLayers = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ALL_GEOMETRY_LAYERS, true); - settings.readAllMaterials = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ALL_MATERIALS, false); - settings.readMaterials = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_MATERIALS, true); - settings.readTextures = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_TEXTURES, true); - settings.readCameras = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_CAMERAS, true); - settings.readLights = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_LIGHTS, true); - settings.readAnimations = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ANIMATIONS, true); - settings.strictMode = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_STRICT_MODE, false); - settings.preservePivots = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_PRESERVE_PIVOTS, true); - settings.optimizeEmptyAnimationCurves = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_OPTIMIZE_EMPTY_ANIMATION_CURVES, true); - settings.useLegacyEmbeddedTextureNaming = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_EMBEDDED_TEXTURES_LEGACY_NAMING, false); - settings.removeEmptyBones = pImp->GetPropertyBool(AI_CONFIG_IMPORT_REMOVE_EMPTY_BONES, true); - settings.convertToMeters = pImp->GetPropertyBool(AI_CONFIG_FBX_CONVERT_TO_M, false); +void FBXImporter::SetupProperties(const Importer *pImp) { + settings.readAllLayers = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ALL_GEOMETRY_LAYERS, true); + settings.readAllMaterials = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ALL_MATERIALS, false); + settings.readMaterials = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_MATERIALS, true); + settings.readTextures = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_TEXTURES, true); + settings.readCameras = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_CAMERAS, true); + settings.readLights = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_LIGHTS, true); + settings.readAnimations = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ANIMATIONS, true); + settings.strictMode = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_STRICT_MODE, false); + settings.preservePivots = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_PRESERVE_PIVOTS, true); + settings.optimizeEmptyAnimationCurves = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_OPTIMIZE_EMPTY_ANIMATION_CURVES, true); + settings.useLegacyEmbeddedTextureNaming = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_EMBEDDED_TEXTURES_LEGACY_NAMING, false); + settings.removeEmptyBones = pImp->GetPropertyBool(AI_CONFIG_IMPORT_REMOVE_EMPTY_BONES, true); + settings.convertToMeters = pImp->GetPropertyBool(AI_CONFIG_FBX_CONVERT_TO_M, false); } // ------------------------------------------------------------------------------------------------ // Imports the given file into the given scene structure. -void FBXImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) -{ - std::unique_ptr stream(pIOHandler->Open(pFile,"rb")); - if (!stream) { - ThrowException("Could not open file for reading"); - } +void FBXImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) { + std::unique_ptr stream(pIOHandler->Open(pFile, "rb")); + if (!stream) { + ThrowException("Could not open file for reading"); + } - // read entire file into memory - no streaming for this, fbx - // files can grow large, but the assimp output data structure - // then becomes very large, too. Assimp doesn't support - // streaming for its output data structures so the net win with - // streaming input data would be very low. - std::vector contents; - contents.resize(stream->FileSize()+1); - stream->Read( &*contents.begin(), 1, contents.size()-1 ); - contents[ contents.size() - 1 ] = 0; - const char* const begin = &*contents.begin(); + // read entire file into memory - no streaming for this, fbx + // files can grow large, but the assimp output data structure + // then becomes very large, too. Assimp doesn't support + // streaming for its output data structures so the net win with + // streaming input data would be very low. + std::vector contents; + contents.resize(stream->FileSize() + 1); + stream->Read(&*contents.begin(), 1, contents.size() - 1); + contents[contents.size() - 1] = 0; + const char *const begin = &*contents.begin(); - // broadphase tokenizing pass in which we identify the core - // syntax elements of FBX (brackets, commas, key:value mappings) - TokenList tokens; - try { + // broadphase tokenizing pass in which we identify the core + // syntax elements of FBX (brackets, commas, key:value mappings) + TokenList tokens; + try { - bool is_binary = false; - if (!strncmp(begin,"Kaydara FBX Binary",18)) { - is_binary = true; - TokenizeBinary(tokens,begin,contents.size()); - } - else { - Tokenize(tokens,begin); - } + bool is_binary = false; + if (!strncmp(begin, "Kaydara FBX Binary", 18)) { + is_binary = true; + TokenizeBinary(tokens, begin, contents.size()); + } else { + Tokenize(tokens, begin); + } - // use this information to construct a very rudimentary - // parse-tree representing the FBX scope structure - Parser parser(tokens, is_binary); + // use this information to construct a very rudimentary + // parse-tree representing the FBX scope structure + Parser parser(tokens, is_binary); - // take the raw parse-tree and convert it to a FBX DOM - Document doc(parser,settings); + // take the raw parse-tree and convert it to a FBX DOM + Document doc(parser, settings); - // convert the FBX DOM to aiScene - ConvertToAssimpScene(pScene, doc, settings.removeEmptyBones); + // convert the FBX DOM to aiScene + ConvertToAssimpScene(pScene, doc, settings.removeEmptyBones); - // size relative to cm - float size_relative_to_cm = doc.GlobalSettings().UnitScaleFactor(); + // size relative to cm + float size_relative_to_cm = doc.GlobalSettings().UnitScaleFactor(); - // Set FBX file scale is relative to CM must be converted to M for - // assimp universal format (M) - SetFileScale( size_relative_to_cm * 0.01f); + // Set FBX file scale is relative to CM must be converted to M for + // assimp universal format (M) + SetFileScale(size_relative_to_cm * 0.01f); - std::for_each(tokens.begin(),tokens.end(),Util::delete_fun()); - } - catch(std::exception&) { - std::for_each(tokens.begin(),tokens.end(),Util::delete_fun()); - throw; - } + std::for_each(tokens.begin(), tokens.end(), Util::delete_fun()); + } catch (std::exception &) { + std::for_each(tokens.begin(), tokens.end(), Util::delete_fun()); + throw; + } } #endif // !ASSIMP_BUILD_NO_FBX_IMPORTER diff --git a/code/M3D/M3DExporter.cpp b/code/M3D/M3DExporter.cpp new file mode 100644 index 000000000..b1c7ebdba --- /dev/null +++ b/code/M3D/M3DExporter.cpp @@ -0,0 +1,420 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team +Copyright (c) 2019 bzt + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ +#ifndef ASSIMP_BUILD_NO_EXPORT +#ifndef ASSIMP_BUILD_NO_M3D_EXPORTER + +#define M3D_IMPLEMENTATION +#define M3D_NOIMPORTER +#define M3D_EXPORTER +#define M3D_ASCII +#ifndef ASSIMP_BUILD_NO_M3D_IMPORTER +#define M3D_NODUP +#endif + +// Header files, standard library. +#include // shared_ptr +#include +#include + +#include // aiGetVersion +#include +#include +#include +#include // StreamWriterLE +#include // DeadlyExportError +#include // aiTextureType +#include +#include +#include "M3DExporter.h" +#include "M3DMaterials.h" + +// RESOURCES: +// https://gitlab.com/bztsrc/model3d/blob/master/docs/m3d_format.md +// https://gitlab.com/bztsrc/model3d/blob/master/docs/a3d_format.md + +/* + * Currently supports static meshes, vertex colors, materials, textures + * + * For animation, it would require the following conversions: + * - aiNode (bones) -> m3d_t.bone (with parent id, position vector and oriantation quaternion) + * - aiMesh.aiBone -> m3d_t.skin (per vertex, with bone id, weight pairs) + * - aiAnimation -> m3d_action (frame with timestamp and list of bone id, position, orientation + * triplets, instead of per bone timestamp + lists) + */ +using namespace Assimp; + +namespace Assimp { + + // --------------------------------------------------------------------- + // Worker function for exporting a scene to binary M3D. + // Prototyped and registered in Exporter.cpp + void ExportSceneM3D ( + const char* pFile, + IOSystem* pIOSystem, + const aiScene* pScene, + const ExportProperties* pProperties + ){ + // initialize the exporter + M3DExporter exporter(pScene, pProperties); + + // perform binary export + exporter.doExport(pFile, pIOSystem, false); + } + + // --------------------------------------------------------------------- + // Worker function for exporting a scene to ASCII A3D. + // Prototyped and registered in Exporter.cpp + void ExportSceneA3D ( + const char* pFile, + IOSystem* pIOSystem, + const aiScene* pScene, + const ExportProperties* pProperties + + ){ + // initialize the exporter + M3DExporter exporter(pScene, pProperties); + + // perform ascii export + exporter.doExport(pFile, pIOSystem, true); + } + +} // end of namespace Assimp + +// ------------------------------------------------------------------------------------------------ +M3DExporter::M3DExporter ( const aiScene* pScene, const ExportProperties* pProperties ) +: mScene(pScene) +, mProperties(pProperties) +, outfile() +, m3d(nullptr) { } + +// ------------------------------------------------------------------------------------------------ +void M3DExporter::doExport ( + const char* pFile, + IOSystem* pIOSystem, + bool toAscii +){ + // TODO: convert mProperties into M3D_EXP_* flags + (void)mProperties; + + // open the indicated file for writing (in binary / ASCII mode) + outfile.reset(pIOSystem->Open(pFile, toAscii ? "wt" : "wb")); + if (!outfile) { + throw DeadlyExportError( "could not open output .m3d file: " + std::string(pFile) ); + } + + // use malloc() here because m3d_free() will call free() + m3d = (m3d_t*)calloc(1, sizeof(m3d_t)); + if(!m3d) { + throw DeadlyExportError( "memory allocation error" ); + } + m3d->name = _m3d_safestr((char*)&mScene->mRootNode->mName.data, 2); + + // Create a model from assimp structures + aiMatrix4x4 m; + NodeWalk(mScene->mRootNode, m); + + // serialize the structures + unsigned int size; + unsigned char *output = m3d_save(m3d, M3D_EXP_FLOAT, + M3D_EXP_EXTRA | (toAscii ? M3D_EXP_ASCII : 0), &size); + m3d_free(m3d); + if(!output || size < 8) { + throw DeadlyExportError( "unable to serialize into Model 3D" ); + } + + // Write out serialized model + outfile->Write(output, size, 1); + + // explicitly release file pointer, + // so we don't have to rely on class destruction. + outfile.reset(); +} + + +// ------------------------------------------------------------------------------------------------ +// helper to add a vertex (private to NodeWalk) +m3dv_t *M3DExporter::AddVrtx(m3dv_t *vrtx, uint32_t *numvrtx, m3dv_t *v, uint32_t *idx) +{ + if(v->x == (M3D_FLOAT)-0.0) v->x = (M3D_FLOAT)0.0; + if(v->y == (M3D_FLOAT)-0.0) v->y = (M3D_FLOAT)0.0; + if(v->z == (M3D_FLOAT)-0.0) v->z = (M3D_FLOAT)0.0; + if(v->w == (M3D_FLOAT)-0.0) v->w = (M3D_FLOAT)0.0; + vrtx = (m3dv_t*)M3D_REALLOC(vrtx, ((*numvrtx) + 1) * sizeof(m3dv_t)); + memcpy(&vrtx[*numvrtx], v, sizeof(m3dv_t)); + *idx = *numvrtx; + (*numvrtx)++; + return vrtx; +} + +// ------------------------------------------------------------------------------------------------ +// helper to add a tmap (private to NodeWalk) +m3dti_t *M3DExporter::AddTmap(m3dti_t *tmap, uint32_t *numtmap, m3dti_t *ti, uint32_t *idx) +{ + tmap = (m3dti_t*)M3D_REALLOC(tmap, ((*numtmap) + 1) * sizeof(m3dti_t)); + memcpy(&tmap[*numtmap], ti, sizeof(m3dti_t)); + *idx = *numtmap; + (*numtmap)++; + return tmap; +} + +// ------------------------------------------------------------------------------------------------ +// recursive node walker +void M3DExporter::NodeWalk(const aiNode* pNode, aiMatrix4x4 m) +{ + aiMatrix4x4 nm = m * pNode->mTransformation; + + for(unsigned int i = 0; i < pNode->mNumMeshes; i++) { + const aiMesh *mesh = mScene->mMeshes[pNode->mMeshes[i]]; + unsigned int mi = (M3D_INDEX)-1U; + if(mScene->mMaterials) { + // get the material for this mesh + mi = addMaterial(mScene->mMaterials[mesh->mMaterialIndex]); + } + // iterate through the mesh faces + for(unsigned int j = 0; j < mesh->mNumFaces; j++) { + unsigned int n; + const aiFace* face = &(mesh->mFaces[j]); + // only triangle meshes supported for now + if(face->mNumIndices != 3) { + throw DeadlyExportError( "use aiProcess_Triangulate before export" ); + } + // add triangle to the output + n = m3d->numface++; + m3d->face = (m3df_t*)M3D_REALLOC(m3d->face, + m3d->numface * sizeof(m3df_t)); + if(!m3d->face) { + throw DeadlyExportError( "memory allocation error" ); + } + /* set all index to -1 by default */ + m3d->face[n].vertex[0] = m3d->face[n].vertex[1] = m3d->face[n].vertex[2] = + m3d->face[n].normal[0] = m3d->face[n].normal[1] = m3d->face[n].normal[2] = + m3d->face[n].texcoord[0] = m3d->face[n].texcoord[1] = m3d->face[n].texcoord[2] = -1U; + m3d->face[n].materialid = mi; + for(unsigned int k = 0; k < face->mNumIndices; k++) { + // get the vertex's index + unsigned int l = face->mIndices[k]; + unsigned int idx; + m3dv_t vertex; + m3dti_t ti; + // multiply the position vector by the transformation matrix + aiVector3D v = mesh->mVertices[l]; + v *= nm; + vertex.x = v.x; + vertex.y = v.y; + vertex.z = v.z; + vertex.w = 1.0; + vertex.color = 0; + vertex.skinid = -1U; + // add color if defined + if(mesh->HasVertexColors(0)) + vertex.color = mkColor(&mesh->mColors[0][l]); + // save the vertex to the output + m3d->vertex = AddVrtx(m3d->vertex, &m3d->numvertex, + &vertex, &idx); + m3d->face[n].vertex[k] = (M3D_INDEX)idx; + // do we have texture coordinates? + if(mesh->HasTextureCoords(0)) { + ti.u = mesh->mTextureCoords[0][l].x; + ti.v = mesh->mTextureCoords[0][l].y; + m3d->tmap = AddTmap(m3d->tmap, &m3d->numtmap, &ti, &idx); + m3d->face[n].texcoord[k] = (M3D_INDEX)idx; + } + // do we have normal vectors? + if(mesh->HasNormals()) { + vertex.x = mesh->mNormals[l].x; + vertex.y = mesh->mNormals[l].y; + vertex.z = mesh->mNormals[l].z; + vertex.color = 0; + m3d->vertex = AddVrtx(m3d->vertex, &m3d->numvertex, &vertex, &idx); + m3d->face[n].normal[k] = (M3D_INDEX)idx; + } + } + } + } + // repeat for the children nodes + for (unsigned int i = 0; i < pNode->mNumChildren; i++) { + NodeWalk(pNode->mChildren[i], nm); + } +} + +// ------------------------------------------------------------------------------------------------ +// convert aiColor4D into uint32_t +uint32_t M3DExporter::mkColor(aiColor4D* c) +{ + return ((uint8_t)(c->a*255) << 24L) | + ((uint8_t)(c->b*255) << 16L) | + ((uint8_t)(c->g*255) << 8L) | + ((uint8_t)(c->r*255) << 0L); +} + +// ------------------------------------------------------------------------------------------------ +// add a material to the output +M3D_INDEX M3DExporter::addMaterial(const aiMaterial *mat) +{ + unsigned int mi = -1U; + aiColor4D c; + aiString name; + ai_real f; + char *fn; + + if(mat && mat->Get(AI_MATKEY_NAME, name) == AI_SUCCESS && name.length && + strcmp((char*)&name.data, AI_DEFAULT_MATERIAL_NAME)) { + // check if we have saved a material by this name. This has to be done + // because only the referenced materials should be added to the output + for(unsigned int i = 0; i < m3d->nummaterial; i++) + if(!strcmp((char*)&name.data, m3d->material[i].name)) { + mi = i; + break; + } + // if not found, add the material to the output + if(mi == -1U) { + unsigned int k; + mi = m3d->nummaterial++; + m3d->material = (m3dm_t*)M3D_REALLOC(m3d->material, m3d->nummaterial + * sizeof(m3dm_t)); + if(!m3d->material) { + throw DeadlyExportError( "memory allocation error" ); + } + m3d->material[mi].name = _m3d_safestr((char*)&name.data, 0); + m3d->material[mi].numprop = 0; + m3d->material[mi].prop = NULL; + // iterate through the material property table and see what we got + for(k = 0; k < 15; k++) { + unsigned int j; + if(m3d_propertytypes[k].format == m3dpf_map) + continue; + if(aiProps[k].pKey) { + switch(m3d_propertytypes[k].format) { + case m3dpf_color: + if(mat->Get(aiProps[k].pKey, aiProps[k].type, + aiProps[k].index, c) == AI_SUCCESS) + addProp(&m3d->material[mi], + m3d_propertytypes[k].id, mkColor(&c)); + break; + case m3dpf_float: + if(mat->Get(aiProps[k].pKey, aiProps[k].type, + aiProps[k].index, f) == AI_SUCCESS) + addProp(&m3d->material[mi], + m3d_propertytypes[k].id, + /* not (uint32_t)f, because we don't want to convert + * it, we want to see it as 32 bits of memory */ + *((uint32_t*)&f)); + break; + case m3dpf_uint8: + if(mat->Get(aiProps[k].pKey, aiProps[k].type, + aiProps[k].index, j) == AI_SUCCESS) { + // special conversion for illumination model property + if(m3d_propertytypes[k].id == m3dp_il) { + switch(j) { + case aiShadingMode_NoShading: j = 0; break; + case aiShadingMode_Phong: j = 2; break; + default: j = 1; break; + } + } + addProp(&m3d->material[mi], + m3d_propertytypes[k].id, j); + } + break; + default: + if(mat->Get(aiProps[k].pKey, aiProps[k].type, + aiProps[k].index, j) == AI_SUCCESS) + addProp(&m3d->material[mi], + m3d_propertytypes[k].id, j); + break; + } + } + if(aiTxProps[k].pKey && + mat->GetTexture((aiTextureType)aiTxProps[k].type, + aiTxProps[k].index, &name, NULL, NULL, NULL, + NULL, NULL) == AI_SUCCESS) { + unsigned int i; + for(j = name.length-1; j > 0 && name.data[j]!='.'; j++); + if(j && name.data[j]=='.' && + (name.data[j+1]=='p' || name.data[j+1]=='P') && + (name.data[j+1]=='n' || name.data[j+1]=='N') && + (name.data[j+1]=='g' || name.data[j+1]=='G')) + name.data[j]=0; + // do we have this texture saved already? + fn = _m3d_safestr((char*)&name.data, 0); + for(j = 0, i = -1U; j < m3d->numtexture; j++) + if(!strcmp(fn, m3d->texture[j].name)) { + i = j; + free(fn); + break; + } + if(i == -1U) { + i = m3d->numtexture++; + m3d->texture = (m3dtx_t*)M3D_REALLOC( + m3d->texture, + m3d->numtexture * sizeof(m3dtx_t)); + if(!m3d->texture) { + throw DeadlyExportError( "memory allocation error" ); + } + // we don't need the texture itself, only its name + m3d->texture[i].name = fn; + m3d->texture[i].w = 0; + m3d->texture[i].h = 0; + m3d->texture[i].d = NULL; + } + addProp(&m3d->material[mi], + m3d_propertytypes[k].id + 128, i); + } + } + } + } + return mi; +} + +// ------------------------------------------------------------------------------------------------ +// add a material property to the output +void M3DExporter::addProp(m3dm_t *m, uint8_t type, uint32_t value) +{ + unsigned int i; + i = m->numprop++; + m->prop = (m3dp_t*)M3D_REALLOC(m->prop, m->numprop * sizeof(m3dp_t)); + if(!m->prop) { throw DeadlyExportError( "memory allocation error" ); } + m->prop[i].type = type; + m->prop[i].value.num = value; +} + +#endif // ASSIMP_BUILD_NO_M3D_EXPORTER +#endif // ASSIMP_BUILD_NO_EXPORT diff --git a/code/M3D/M3DExporter.h b/code/M3D/M3DExporter.h new file mode 100644 index 000000000..58d8d597e --- /dev/null +++ b/code/M3D/M3DExporter.h @@ -0,0 +1,100 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team +Copyright (c) 2019 bzt + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file M3DExporter.h +* @brief Declares the exporter class to write a scene to a Model 3D file +*/ +#ifndef AI_M3DEXPORTER_H_INC +#define AI_M3DEXPORTER_H_INC + +#ifndef ASSIMP_BUILD_NO_M3D_EXPORTER + +#include "m3d.h" + +#include +//#include +#include // StreamWriterLE +#include // DeadlyExportError + +#include // shared_ptr + +struct aiScene; +struct aiNode; +struct aiMaterial; +struct aiFace; + +namespace Assimp +{ + class IOSystem; + class IOStream; + class ExportProperties; + + // --------------------------------------------------------------------- + /** Helper class to export a given scene to an M3D file. */ + // --------------------------------------------------------------------- + class M3DExporter + { + public: + /// Constructor for a specific scene to export + M3DExporter(const aiScene* pScene, const ExportProperties* pProperties); + // call this to do the actual export + void doExport(const char* pFile, IOSystem* pIOSystem, bool toAscii); + + private: + const aiScene* mScene; // the scene to export + const ExportProperties* mProperties; // currently unused + std::shared_ptr outfile; // file to write to + m3d_t *m3d; // model for the C library to convert to + + // helper to do the recursive walking + void NodeWalk(const aiNode* pNode, aiMatrix4x4 m); + m3dv_t *AddVrtx(m3dv_t *vrtx, uint32_t *numvrtx, m3dv_t *v, uint32_t *idx); + m3dti_t *AddTmap(m3dti_t *tmap, uint32_t *numtmap, m3dti_t *ti, uint32_t *idx); + uint32_t mkColor(aiColor4D* c); + M3D_INDEX addMaterial(const aiMaterial *mat); + void addProp(m3dm_t *m, uint8_t type, uint32_t value); + }; +} + +#endif // ASSIMP_BUILD_NO_M3D_EXPORTER + +#endif // AI_M3DEXPORTER_H_INC diff --git a/code/M3D/M3DImporter.cpp b/code/M3D/M3DImporter.cpp new file mode 100644 index 000000000..9371e2228 --- /dev/null +++ b/code/M3D/M3DImporter.cpp @@ -0,0 +1,766 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team +Copyright (c) 2019 bzt + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +#ifndef ASSIMP_BUILD_NO_M3D_IMPORTER + +#define M3D_IMPLEMENTATION +#define M3D_ASCII +#define M3D_NONORMALS /* leave the post-processing to Assimp */ +#define M3D_NOWEIGHTS +#define M3D_NOANIMATION + +#include +#include +#include +#include +#include +#include +#include +#include +#include "M3DImporter.h" +#include "M3DMaterials.h" + +// RESOURCES: +// https://gitlab.com/bztsrc/model3d/blob/master/docs/m3d_format.md +// https://gitlab.com/bztsrc/model3d/blob/master/docs/a3d_format.md + +/* + Unfortunately aiNode has bone structures and meshes too, yet we can't assign + the mesh to a bone aiNode as a skin may refer to several aiNodes. Therefore + I've decided to import into this structure: + + aiScene->mRootNode + | |->mMeshes (all the meshes) + | \->children (empty if there's no skeleton imported, no meshes) + | \->skeleton root aiNode* + | |->bone aiNode + | | \->subbone aiNode + | |->bone aiNode + | | ... + | \->bone aiNode + \->mMeshes[] + \->aiBone, referencing mesh-less aiNodes from above + + * - normally one, but if a model has several skeleton roots, then all of them + are listed in aiScene->mRootNode->children, but all without meshes +*/ + +static const aiImporterDesc desc = { + "Model 3D Importer", + "", + "", + "", + aiImporterFlags_SupportBinaryFlavour, + 0, + 0, + 0, + 0, + "m3d a3d" +}; + +// workaround: the SDK expects a C callback, but we want to use Assimp::IOSystem to implement that +extern "C" { + void* m3dimporter_pIOHandler; + + unsigned char *m3dimporter_readfile(char *fn, unsigned int *size) { + ai_assert( nullptr != fn ); + ai_assert( nullptr != size ); + std::string file(fn); + std::unique_ptr pStream( + (reinterpret_cast(m3dimporter_pIOHandler))->Open( file, "rb")); + size_t fileSize = 0; + unsigned char *data = NULL; + // sometimes pStream is nullptr for some reason (should be an empty object returning nothing I guess) + if(pStream) { + fileSize = pStream->FileSize(); + // should be allocated with malloc(), because the library will call free() to deallocate + data = (unsigned char*)malloc(fileSize); + if( !data || !pStream.get() || !fileSize || fileSize != pStream->Read(data,1,fileSize)) { + pStream.reset(); + *size = 0; + // don't throw a deadly exception, it's not fatal if we can't read an external asset + return nullptr; + } + pStream.reset(); + } + *size = (int)fileSize; + return data; + } +} + +namespace Assimp { + +using namespace std; + +// ------------------------------------------------------------------------------------------------ +// Default constructor +M3DImporter::M3DImporter() +: mScene(nullptr) +, m3d(nullptr) { } + +// ------------------------------------------------------------------------------------------------ +// Destructor. +M3DImporter::~M3DImporter() {} + +// ------------------------------------------------------------------------------------------------ +// Returns true, if file is a binary or ASCII Model 3D file. +bool M3DImporter::CanRead(const std::string& pFile, IOSystem* pIOHandler , bool checkSig) const { + const std::string extension = GetExtension(pFile); + + if (extension == "m3d" || extension == "a3d") + return true; + else if (!extension.length() || checkSig) { + if (!pIOHandler) { + return true; + } + /* + * don't use CheckMagicToken because that checks with swapped bytes too, leading to false + * positives. This magic is not uint32_t, but char[4], so memcmp is the best way + + const char* tokens[] = {"3DMO", "3dmo"}; + return CheckMagicToken(pIOHandler,pFile,tokens,2,0,4); + */ + std::unique_ptr pStream (pIOHandler->Open(pFile, "rb")); + unsigned char data[4]; + if(4 != pStream->Read(data,1,4)) { + return false; + } + return !memcmp(data, "3DMO", 4) /* bin */ || !memcmp(data, "3dmo", 4) /* ASCII */; + } + return false; +} + +// ------------------------------------------------------------------------------------------------ +const aiImporterDesc* M3DImporter::GetInfo() const { + return &desc; +} + +// ------------------------------------------------------------------------------------------------ +// Model 3D import implementation +void M3DImporter::InternReadFile( const std::string &file, aiScene* pScene, IOSystem* pIOHandler) { + // Read file into memory + std::unique_ptr pStream( pIOHandler->Open( file, "rb")); + if( !pStream.get() ) { + throw DeadlyImportError( "Failed to open file " + file + "." ); + } + + // Get the file-size and validate it, throwing an exception when fails + size_t fileSize = pStream->FileSize(); + if( fileSize < 8 ) { + throw DeadlyImportError( "M3D-file " + file + " is too small." ); + } + std::unique_ptr _buffer (new unsigned char[fileSize]); + unsigned char *data( _buffer.get() ); + if(fileSize != pStream->Read(data,1,fileSize)) { + throw DeadlyImportError( "Failed to read the file " + file + "." ); + } + + // Get the path for external assets + std::string folderName( "./" ); + std::string::size_type pos = file.find_last_of( "\\/" ); + if ( pos != std::string::npos ) { + folderName = file.substr( 0, pos ); + if ( !folderName.empty() ) { + pIOHandler->PushDirectory( folderName ); + } + } + // pass this IOHandler to the C callback + m3dimporter_pIOHandler = pIOHandler; + + //DefaultLogger::create("/dev/stderr", Logger::VERBOSE); + ASSIMP_LOG_DEBUG_F("M3D: loading ", file); + + // let the C SDK do the hard work for us + m3d = m3d_load(&data[0], m3dimporter_readfile, free, nullptr); + m3dimporter_pIOHandler = nullptr; + if( !m3d ) { + throw DeadlyImportError( "Unable to parse " + file + " as M3D." ); + } + + // create the root node + pScene->mRootNode = new aiNode; + pScene->mRootNode->mName = aiString(std::string(std::string(m3d->name))); + pScene->mRootNode->mTransformation = aiMatrix4x4(); + pScene->mRootNode->mNumChildren = 0; + mScene = pScene; + + ASSIMP_LOG_DEBUG("M3D: root node " + std::string(m3d->name)); + + // now we just have to fill up the Assimp structures in pScene + importMaterials(); + importTextures(); + importBones(-1U, pScene->mRootNode); + importMeshes(); + importAnimations(); + + // we don't need the SDK's version any more + m3d_free(m3d); + + // Pop directory stack + if ( pIOHandler->StackSize() > 0 ) { + pIOHandler->PopDirectory(); + } +} + +// ------------------------------------------------------------------------------------------------ +// convert materials. properties are converted using a static table in M3DMaterials.h +void M3DImporter::importMaterials() +{ + unsigned int i, j, k, l, n; + m3dm_t *m; + aiString name = aiString(AI_DEFAULT_MATERIAL_NAME); + aiColor4D c; + ai_real f; + + ai_assert(mScene != nullptr); + ai_assert(m3d != nullptr); + + mScene->mNumMaterials = m3d->nummaterial + 1; + mScene->mMaterials = new aiMaterial*[ m3d->nummaterial + 1 ]; + + ASSIMP_LOG_DEBUG_F("M3D: importMaterials ", mScene->mNumMaterials); + + // add a default material as first + aiMaterial* mat = new aiMaterial; + mat->AddProperty( &name, AI_MATKEY_NAME ); + c.a = 1.0; c.b = c.g = c.r = 0.6; + mat->AddProperty( &c, 1, AI_MATKEY_COLOR_DIFFUSE); + mScene->mMaterials[0] = mat; + + for(i = 0; i < m3d->nummaterial; i++) { + m = &m3d->material[i]; + aiMaterial* mat = new aiMaterial; + name.Set(std::string(m->name)); + mat->AddProperty( &name, AI_MATKEY_NAME ); + for(j = 0; j < m->numprop; j++) { + // look up property type + // 0 - 127 scalar values, + // 128 - 255 the same properties but for texture maps + k = 256; + for(l = 0; l < sizeof(m3d_propertytypes)/sizeof(m3d_propertytypes[0]); l++) + if(m->prop[j].type == m3d_propertytypes[l].id || + m->prop[j].type == m3d_propertytypes[l].id + 128) { + k = l; + break; + } + // should never happen, but be safe than sorry + if(k == 256) continue; + + // scalar properties + if(m->prop[j].type < 128 && aiProps[k].pKey) { + switch(m3d_propertytypes[k].format) { + case m3dpf_color: + c = mkColor(m->prop[j].value.color); + mat->AddProperty(&c, 1, aiProps[k].pKey, aiProps[k].type, aiProps[k].index); + break; + case m3dpf_float: + f = m->prop[j].value.fnum; + mat->AddProperty(&f, 1, aiProps[k].pKey, aiProps[k].type, aiProps[k].index); + break; + default: + n = m->prop[j].value.num; + if(m->prop[j].type == m3dp_il) { + switch(n) { + case 0: n = aiShadingMode_NoShading; break; + case 2: n = aiShadingMode_Phong; break; + default: n = aiShadingMode_Gouraud; break; + } + } + mat->AddProperty(&n, 1, aiProps[k].pKey, aiProps[k].type, aiProps[k].index); + break; + } + } + // texture map properties + if(m->prop[j].type >= 128 && aiTxProps[k].pKey && + // extra check, should never happen, do we have the refered texture? + m->prop[j].value.textureid < m3d->numtexture && + m3d->texture[m->prop[j].value.textureid].name) { + name.Set(std::string(std::string(m3d->texture[m->prop[j].value.textureid].name) + ".png")); + mat->AddProperty(&name, aiTxProps[k].pKey, aiTxProps[k].type, aiTxProps[k].index); + n = 0; + mat->AddProperty(&n, 1, _AI_MATKEY_UVWSRC_BASE, aiProps[k].type, aiProps[k].index); + } + } + mScene->mMaterials[i + 1] = mat; + } +} + +// ------------------------------------------------------------------------------------------------ +// import textures, this is the simplest of all +void M3DImporter::importTextures() +{ + unsigned int i; + const char *formatHint[] = { "rgba0800", "rgba0808", "rgba8880", "rgba8888" }; + m3dtx_t *t; + + ai_assert(mScene != nullptr); + ai_assert(m3d != nullptr); + + mScene->mNumTextures = m3d->numtexture; + ASSIMP_LOG_DEBUG_F("M3D: importTextures ", mScene->mNumTextures); + + if(!m3d->numtexture) + return; + + mScene->mTextures = new aiTexture*[m3d->numtexture]; + for(i = 0; i < m3d->numtexture; i++) { + unsigned int j, k; + t = &m3d->texture[i]; + if(!t->w || !t->h || !t->f || !t->d) continue; + aiTexture *tx = new aiTexture; + strcpy(tx->achFormatHint, formatHint[t->f - 1]); + tx->mFilename = aiString(std::string(t->name) + ".png"); + tx->mWidth = t->w; + tx->mHeight = t->h; + tx->pcData = new aiTexel[ tx->mWidth*tx->mHeight ]; + for(j = k = 0; j < tx->mWidth*tx->mHeight; j++) { + switch(t->f) { + case 1: tx->pcData[j].g = t->d[k++]; break; + case 2: tx->pcData[j].g = t->d[k++]; tx->pcData[j].a = t->d[k++]; break; + case 3: + tx->pcData[j].r = t->d[k++]; tx->pcData[j].g = t->d[k++]; + tx->pcData[j].b = t->d[k++]; tx->pcData[j].a = 255; + break; + case 4: + tx->pcData[j].r = t->d[k++]; tx->pcData[j].g = t->d[k++]; + tx->pcData[j].b = t->d[k++]; tx->pcData[j].a = t->d[k++]; + break; + } + } + mScene->mTextures[i] = tx; + } +} + +// ------------------------------------------------------------------------------------------------ +// this is tricky. M3D has a global vertex and UV list, and faces are indexing them +// individually. In assimp there're per mesh vertex and UV lists, and they must be +// indexed simultaneously. +void M3DImporter::importMeshes() +{ + unsigned int i, j, k, l, numpoly = 3, lastMat = -2U; + std::vector *meshes = new std::vector(); + std::vector *faces = nullptr; + std::vector *vertices = nullptr; + std::vector *normals = nullptr; + std::vector *texcoords = nullptr; + std::vector *colors = nullptr; + std::vector *vertexids = nullptr; + aiMesh *pMesh = nullptr; + + ai_assert(mScene != nullptr); + ai_assert(m3d != nullptr); + ai_assert(mScene->mRootNode != nullptr); + + ASSIMP_LOG_DEBUG_F("M3D: importMeshes ", m3d->numface); + + for(i = 0; i < m3d->numface; i++) { + // we must switch mesh if material changes + if(lastMat != m3d->face[i].materialid) { + lastMat = m3d->face[i].materialid; + if(pMesh && vertices && vertices->size() && faces && faces->size()) { + populateMesh(pMesh, faces, vertices, normals, texcoords, colors, vertexids); + meshes->push_back(pMesh); + delete faces; + delete vertices; + delete normals; + delete texcoords; + delete colors; + delete vertexids; // this is not stored in pMesh, just to collect bone vertices + } + pMesh = new aiMesh; + pMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE; + pMesh->mMaterialIndex = lastMat + 1; + faces = new std::vector(); + vertices = new std::vector(); + normals = new std::vector(); + texcoords = new std::vector(); + colors = new std::vector(); + vertexids = new std::vector(); + } + // add a face to temporary vector + aiFace *pFace = new aiFace; + pFace->mNumIndices = numpoly; + pFace->mIndices = new unsigned int[numpoly]; + for(j = 0; j < numpoly; j++) { + aiVector3D pos, uv, norm; + k = vertices->size(); + pFace->mIndices[j] = k; + l = m3d->face[i].vertex[j]; + pos.x = m3d->vertex[l].x; + pos.y = m3d->vertex[l].y; + pos.z = m3d->vertex[l].z; + vertices->push_back(pos); + colors->push_back(mkColor(m3d->vertex[l].color)); + // add a bone to temporary vector + if(m3d->vertex[l].skinid != -1U &&m3d->vertex[l].skinid != -2U && m3d->skin && m3d->bone) { + // this is complicated, because M3D stores a list of bone id / weight pairs per + // vertex but assimp uses lists of local vertex id/weight pairs per local bone list + vertexids->push_back(l); + } + l = m3d->face[i].texcoord[j]; + if(l != -1U) { + uv.x = m3d->tmap[l].u; + uv.y = m3d->tmap[l].v; + uv.z = 0.0; + texcoords->push_back(uv); + } + l = m3d->face[i].normal[j]; + if(l != -1U) { + norm.x = m3d->vertex[l].x; + norm.y = m3d->vertex[l].y; + norm.z = m3d->vertex[l].z; + normals->push_back(norm); + } + } + faces->push_back(*pFace); + delete pFace; + } + // if there's data left in the temporary vectors, flush them + if(pMesh && vertices->size() && faces->size()) { + populateMesh(pMesh, faces, vertices, normals, texcoords, colors, vertexids); + meshes->push_back(pMesh); + } + + // create global mesh list in scene + mScene->mNumMeshes = meshes->size(); + mScene->mMeshes = new aiMesh*[mScene->mNumMeshes]; + std::copy(meshes->begin(), meshes->end(), mScene->mMeshes); + + // create mesh indeces in root node + mScene->mRootNode->mNumMeshes = meshes->size(); + mScene->mRootNode->mMeshes = new unsigned int[meshes->size()]; + for(i = 0; i < meshes->size(); i++) { + mScene->mRootNode->mMeshes[i] = i; + } + + delete meshes; + if(faces) delete faces; + if(vertices) delete vertices; + if(normals) delete normals; + if(texcoords) delete texcoords; + if(colors) delete colors; + if(vertexids) delete vertexids; +} + +// ------------------------------------------------------------------------------------------------ +// a reentrant node parser. Otherwise this is simple +void M3DImporter::importBones(unsigned int parentid, aiNode *pParent) +{ + unsigned int i, n; + + ai_assert(pParent != nullptr); + ai_assert(mScene != nullptr); + ai_assert(m3d != nullptr); + + ASSIMP_LOG_DEBUG_F("M3D: importBones ", m3d->numbone, " parentid ", (int)parentid); + + for(n = 0, i = parentid + 1; i < m3d->numbone; i++) + if(m3d->bone[i].parent == parentid) n++; + pParent->mChildren = new aiNode*[n]; + + for(i = parentid + 1; i < m3d->numbone; i++) { + if(m3d->bone[i].parent == parentid) { + aiNode *pChild = new aiNode; + pChild->mParent = pParent; + pChild->mName = aiString(std::string(m3d->bone[i].name)); + convertPose(&pChild->mTransformation, m3d->bone[i].pos, m3d->bone[i].ori); + pChild->mNumChildren = 0; + pParent->mChildren[pParent->mNumChildren] = pChild; + pParent->mNumChildren++; + importBones(i, pChild); + } + } +} + +// ------------------------------------------------------------------------------------------------ +// this is another headache. M3D stores list of changed bone id/position/orientation triplets and +// a timestamp per frame, but assimp needs timestamp and lists of position, orientation lists per +// bone, so we have to convert between the two conceptually different representation forms +void M3DImporter::importAnimations() +{ + unsigned int i, j, k, l, pos, ori; + double t; + m3da_t *a; + + ai_assert(mScene != nullptr); + ai_assert(m3d != nullptr); + + mScene->mNumAnimations = m3d->numaction; + + ASSIMP_LOG_DEBUG_F("M3D: importAnimations ", mScene->mNumAnimations); + + if(!m3d->numaction || !m3d->numbone) + return; + + mScene->mAnimations = new aiAnimation*[m3d->numaction]; + for(i = 0; i < m3d->numaction; i++) { + a = &m3d->action[i]; + aiAnimation *pAnim = new aiAnimation; + pAnim->mName = aiString(std::string(a->name)); + pAnim->mDuration = ((double)a->durationmsec) / 10; + pAnim->mTicksPerSecond = 100; + // now we know how many bones are referenced in this animation + pAnim->mNumChannels = m3d->numbone; + pAnim->mChannels = new aiNodeAnim*[pAnim->mNumChannels]; + for(l = 0; l < m3d->numbone; l++) { + unsigned int n; + pAnim->mChannels[l] = new aiNodeAnim; + pAnim->mChannels[l]->mNodeName = aiString(std::string(m3d->bone[l].name)); + // now n is the size of positions / orientations arrays + pAnim->mChannels[l]->mNumPositionKeys = pAnim->mChannels[l]->mNumRotationKeys = a->numframe; + pAnim->mChannels[l]->mPositionKeys = new aiVectorKey[a->numframe]; + pAnim->mChannels[l]->mRotationKeys = new aiQuatKey[a->numframe]; + pos = m3d->bone[l].pos; + ori = m3d->bone[l].ori; + for(j = n = 0; j < a->numframe; j++) { + t = ((double)a->frame[j].msec) / 10; + for(k = 0; k < a->frame[j].numtransform; k++) { + if(a->frame[j].transform[k].boneid == l) { + pos = a->frame[j].transform[k].pos; + ori = a->frame[j].transform[k].ori; + } + } + m3dv_t *v = &m3d->vertex[pos]; + m3dv_t *q = &m3d->vertex[ori]; + pAnim->mChannels[l]->mPositionKeys[j].mTime = t; + pAnim->mChannels[l]->mPositionKeys[j].mValue.x = v->x; + pAnim->mChannels[l]->mPositionKeys[j].mValue.y = v->y; + pAnim->mChannels[l]->mPositionKeys[j].mValue.z = v->z; + pAnim->mChannels[l]->mRotationKeys[j].mTime = t; + pAnim->mChannels[l]->mRotationKeys[j].mValue.w = q->w; + pAnim->mChannels[l]->mRotationKeys[j].mValue.x = q->x; + pAnim->mChannels[l]->mRotationKeys[j].mValue.y = q->y; + pAnim->mChannels[l]->mRotationKeys[j].mValue.z = q->z; + }// foreach frame + }// foreach bones + mScene->mAnimations[i] = pAnim; + } +} + +// ------------------------------------------------------------------------------------------------ +// convert uint32_t into aiColor4D +aiColor4D M3DImporter::mkColor(uint32_t c) { + aiColor4D color; + color.a = ((float)((c >> 24)&0xff)) / 255; + color.b = ((float)((c >> 16)&0xff)) / 255; + color.g = ((float)((c >> 8)&0xff)) / 255; + color.r = ((float)((c >> 0)&0xff)) / 255; + return color; +} + +// ------------------------------------------------------------------------------------------------ +// convert a position id and orientation id into a 4 x 4 transformation matrix +void M3DImporter::convertPose(aiMatrix4x4 *m, unsigned int posid, unsigned int orientid) +{ + ai_assert(m != nullptr); + ai_assert(m3d != nullptr); + ai_assert(posid != -1U && posid < m3d->numvertex); + ai_assert(orientid != -1U && orientid < m3d->numvertex); + m3dv_t *p = &m3d->vertex[posid]; + m3dv_t *q = &m3d->vertex[orientid]; + + /* quaternion to matrix. Do NOT use aiQuaternion to aiMatrix3x3, gives bad results */ + if(q->x == 0.0 && q->y == 0.0 && q->z >= 0.7071065 && q->z <= 0.7071075 && q->w == 0.0) { + m->a2 = m->a3 = m->b1 = m->b3 = m->c1 = m->c2 = 0.0; + m->a1 = m->b2 = m->c3 = -1.0; + } else { + m->a1 = 1 - 2 * (q->y * q->y + q->z * q->z); if(m->a1 > -M3D_EPSILON && m->a1 < M3D_EPSILON) m->a1 = 0.0; + m->a2 = 2 * (q->x * q->y - q->z * q->w); if(m->a2 > -M3D_EPSILON && m->a2 < M3D_EPSILON) m->a2 = 0.0; + m->a3 = 2 * (q->x * q->z + q->y * q->w); if(m->a3 > -M3D_EPSILON && m->a3 < M3D_EPSILON) m->a3 = 0.0; + m->b1 = 2 * (q->x * q->y + q->z * q->w); if(m->b1 > -M3D_EPSILON && m->b1 < M3D_EPSILON) m->b1 = 0.0; + m->b2 = 1 - 2 * (q->x * q->x + q->z * q->z); if(m->b2 > -M3D_EPSILON && m->b2 < M3D_EPSILON) m->b2 = 0.0; + m->b3 = 2 * (q->y * q->z - q->x * q->w); if(m->b3 > -M3D_EPSILON && m->b3 < M3D_EPSILON) m->b3 = 0.0; + m->c1 = 2 * (q->x * q->z - q->y * q->w); if(m->c1 > -M3D_EPSILON && m->c1 < M3D_EPSILON) m->c1 = 0.0; + m->c2 = 2 * (q->y * q->z + q->x * q->w); if(m->c2 > -M3D_EPSILON && m->c2 < M3D_EPSILON) m->c2 = 0.0; + m->c3 = 1 - 2 * (q->x * q->x + q->y * q->y); if(m->c3 > -M3D_EPSILON && m->c3 < M3D_EPSILON) m->c3 = 0.0; + } + + /* set translation */ + m->a4 = p->x; m->b4 = p->y; m->c4 = p->z; + + m->d1 = 0; m->d2 = 0; m->d3 = 0; m->d4 = 1; +} + +// ------------------------------------------------------------------------------------------------ +// find a node by name +aiNode *M3DImporter::findNode(aiNode *pNode, aiString name) +{ + unsigned int i; + + ai_assert(pNode != nullptr); + ai_assert(mScene != nullptr); + + if(pNode->mName == name) + return pNode; + for(i = 0; i < pNode->mNumChildren; i++) { + aiNode *pChild = findNode(pNode->mChildren[i], name); + if(pChild) return pChild; + } + return nullptr; +} + +// ------------------------------------------------------------------------------------------------ +// fills up offsetmatrix in mBones +void M3DImporter::calculateOffsetMatrix(aiNode *pNode, aiMatrix4x4 *m) +{ + ai_assert(pNode != nullptr); + ai_assert(mScene != nullptr); + + if(pNode->mParent) { + calculateOffsetMatrix(pNode->mParent, m); + *m *= pNode->mTransformation; + } else { + *m = pNode->mTransformation; + } +} + +// ------------------------------------------------------------------------------------------------ +// because M3D has a global mesh, global vertex ids and stores materialid on the face, we need +// temporary lists to collect data for an aiMesh, which requires local arrays and local indeces +// this function fills up an aiMesh with those temporary lists +void M3DImporter::populateMesh(aiMesh *pMesh, std::vector *faces, std::vector *vertices, + std::vector *normals, std::vector *texcoords, std::vector *colors, + std::vector *vertexids) { + + ai_assert(pMesh != nullptr); + ai_assert(faces != nullptr); + ai_assert(vertices != nullptr); + ai_assert(normals != nullptr); + ai_assert(texcoords != nullptr); + ai_assert(colors != nullptr); + ai_assert(vertexids != nullptr); + ai_assert(m3d != nullptr); + + ASSIMP_LOG_DEBUG_F("M3D: populateMesh numvertices ", vertices->size(), " numfaces ", faces->size(), + " numnormals ", normals->size(), " numtexcoord ", texcoords->size(), " numbones ", m3d->numbone); + + if(vertices->size() && faces->size()) { + pMesh->mNumFaces = faces->size(); + pMesh->mFaces = new aiFace[pMesh->mNumFaces]; + std::copy(faces->begin(), faces->end(), pMesh->mFaces); + pMesh->mNumVertices = vertices->size(); + pMesh->mVertices = new aiVector3D[pMesh->mNumVertices]; + std::copy(vertices->begin(), vertices->end(), pMesh->mVertices); + if(normals->size() == vertices->size()) { + pMesh->mNormals = new aiVector3D[pMesh->mNumVertices]; + std::copy(normals->begin(), normals->end(), pMesh->mNormals); + } + if(texcoords->size() == vertices->size()) { + pMesh->mTextureCoords[0] = new aiVector3D[pMesh->mNumVertices]; + std::copy(texcoords->begin(), texcoords->end(), pMesh->mTextureCoords[0]); + pMesh->mNumUVComponents[0] = 2; + } + if(colors->size() == vertices->size()) { + pMesh->mColors[0] = new aiColor4D[pMesh->mNumVertices]; + std::copy(colors->begin(), colors->end(), pMesh->mColors[0]); + } + // this is complicated, because M3D stores a list of bone id / weight pairs per + // vertex but assimp uses lists of local vertex id/weight pairs per local bone list + pMesh->mNumBones = m3d->numbone; + /* we need aiBone with mOffsetMatrix for bones without weights as well */ + if(pMesh->mNumBones) { + pMesh->mBones = new aiBone*[pMesh->mNumBones]; + for(unsigned int i = 0; i < m3d->numbone; i++) { + aiNode *pNode; + pMesh->mBones[i] = new aiBone; + pMesh->mBones[i]->mName = aiString(std::string(m3d->bone[i].name)); + pMesh->mBones[i]->mNumWeights = 0; + pNode = findNode(mScene->mRootNode, pMesh->mBones[i]->mName); + if(pNode) { + calculateOffsetMatrix(pNode, &pMesh->mBones[i]->mOffsetMatrix); + pMesh->mBones[i]->mOffsetMatrix.Inverse(); + } else + pMesh->mBones[i]->mOffsetMatrix = aiMatrix4x4(); + } + if(vertexids->size()) { + unsigned int i, j; + // first count how many vertices we have per bone + for(i = 0; i < vertexids->size(); i++) { + unsigned int s = m3d->vertex[vertexids->at(i)].skinid; + if(s != -1U && s!= -2U) { + for(unsigned int k = 0; k < M3D_NUMBONE && m3d->skin[s].weight[k] > 0.0; k++) { + aiString name = aiString(std::string(m3d->bone[m3d->skin[s].boneid[k]].name)); + for(j = 0; j < pMesh->mNumBones; j++) { + if(pMesh->mBones[j]->mName == name) { + pMesh->mBones[j]->mNumWeights++; + break; + } + } + } + } + } + // allocate mWeights + for(j = 0; j < pMesh->mNumBones; j++) { + aiBone *pBone = pMesh->mBones[j]; + if(pBone->mNumWeights) { + pBone->mWeights = new aiVertexWeight[pBone->mNumWeights]; + pBone->mNumWeights = 0; + } + } + // fill up with data + for(i = 0; i < vertexids->size(); i++) { + unsigned int s = m3d->vertex[vertexids->at(i)].skinid; + if(s != -1U && s!= -2U) { + for(unsigned int k = 0; k < M3D_NUMBONE && m3d->skin[s].weight[k] > 0.0; k++) { + aiString name = aiString(std::string(m3d->bone[m3d->skin[s].boneid[k]].name)); + for(j = 0; j < pMesh->mNumBones; j++) { + if(pMesh->mBones[j]->mName == name) { + aiBone *pBone = pMesh->mBones[j]; + pBone->mWeights[pBone->mNumWeights].mVertexId = i; + pBone->mWeights[pBone->mNumWeights].mWeight = m3d->skin[s].weight[k]; + pBone->mNumWeights++; + break; + } + } + } // foreach skin + } + } // foreach vertexids + } + } + } +} + +// ------------------------------------------------------------------------------------------------ + +} // Namespace Assimp + +#endif // !! ASSIMP_BUILD_NO_M3D_IMPORTER diff --git a/code/M3D/M3DImporter.h b/code/M3D/M3DImporter.h new file mode 100644 index 000000000..06cc757b6 --- /dev/null +++ b/code/M3D/M3DImporter.h @@ -0,0 +1,106 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team +Copyright (c) 2019 bzt + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file M3DImporter.h +* @brief Declares the importer class to read a scene from a Model 3D file +*/ +#ifndef AI_M3DIMPORTER_H_INC +#define AI_M3DIMPORTER_H_INC + +#ifndef ASSIMP_BUILD_NO_M3D_IMPORTER + +#include "m3d.h" +#include +#include +#include + +struct aiMesh; +struct aiNode; +struct aiMaterial; +struct aiFace; + +namespace Assimp { + +class M3DImporter : public BaseImporter { +public: + /// \brief Default constructor + M3DImporter(); + + /// \brief Destructor + ~M3DImporter(); + +public: + /// \brief Returns whether the class can handle the format of the given file. + /// \remark See BaseImporter::CanRead() for details. + bool CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const; + +private: + aiScene* mScene; // the scene to import to + m3d_t *m3d; // model for the C library to convert from + + //! \brief Appends the supported extension. + const aiImporterDesc* GetInfo () const; + + //! \brief File import implementation. + void InternReadFile(const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler); + + void importMaterials(); + void importTextures(); + void importMeshes(); + void importBones(unsigned int parentid, aiNode *pParent); + void importAnimations(); + + // helper functions + aiColor4D mkColor(uint32_t c); + void convertPose(aiMatrix4x4 *m, unsigned int posid, unsigned int orientid); + aiNode *findNode(aiNode *pNode, aiString name); + void calculateOffsetMatrix(aiNode *pNode, aiMatrix4x4 *m); + void populateMesh(aiMesh *pMesh, std::vector *faces, std::vector *verteces, + std::vector *normals, std::vector *texcoords, std::vector *colors, + std::vector *vertexids); +}; + +} // Namespace Assimp + +#endif // ASSIMP_BUILD_NO_M3D_IMPORTER + +#endif // AI_M3DIMPORTER_H_INC diff --git a/code/M3D/M3DMaterials.h b/code/M3D/M3DMaterials.h new file mode 100644 index 000000000..3d6fe246d --- /dev/null +++ b/code/M3D/M3DMaterials.h @@ -0,0 +1,106 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team +Copyright (c) 2019 bzt + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ + +/** @file M3DMaterials.h +* @brief Declares the Assimp and Model 3D file material type relations +*/ +#ifndef AI_M3DMATERIALS_H_INC +#define AI_M3DMATERIALS_H_INC + +/* + * In the m3d.h header, there's a static array which defines the material + * properties, called m3d_propertytypes. These must have the same size, and + * list the matching Assimp materials for those properties. Used by both the + * M3DImporter and the M3DExporter, so you have to define these relations + * only once. D.R.Y. and K.I.S.S. + */ +typedef struct { + const char *pKey; + unsigned int type; + unsigned int index; +} aiMatProp; + +/* --- Scalar Properties --- !!!!! must match m3d_propertytypes !!!!! */ +static const aiMatProp aiProps[] = { + { AI_MATKEY_COLOR_DIFFUSE }, /* m3dp_Kd */ + { AI_MATKEY_COLOR_AMBIENT }, /* m3dp_Ka */ + { AI_MATKEY_COLOR_SPECULAR }, /* m3dp_Ks */ + { AI_MATKEY_SHININESS }, /* m3dp_Ns */ + { AI_MATKEY_COLOR_EMISSIVE }, /* m3dp_Ke */ + { AI_MATKEY_COLOR_REFLECTIVE }, /* m3dp_Tf */ + { AI_MATKEY_BUMPSCALING }, /* m3dp_Km */ + { AI_MATKEY_OPACITY }, /* m3dp_d */ + { AI_MATKEY_SHADING_MODEL }, /* m3dp_il */ + + { NULL, 0, 0 }, /* m3dp_Pr */ + { AI_MATKEY_REFLECTIVITY }, /* m3dp_Pm */ + { NULL, 0, 0 }, /* m3dp_Ps */ + { AI_MATKEY_REFRACTI }, /* m3dp_Ni */ + { NULL, 0, 0 }, /* m3dp_Nt */ + { NULL, 0, 0 }, + { NULL, 0, 0 }, + { NULL, 0, 0 } +}; + +/* --- Texture Map Properties --- !!!!! must match m3d_propertytypes !!!!! */ +static const aiMatProp aiTxProps[] = { + { AI_MATKEY_TEXTURE_DIFFUSE(0) }, /* m3dp_map_Kd */ + { AI_MATKEY_TEXTURE_AMBIENT(0) }, /* m3dp_map_Ka */ + { AI_MATKEY_TEXTURE_SPECULAR(0) }, /* m3dp_map_Ks */ + { AI_MATKEY_TEXTURE_SHININESS(0) }, /* m3dp_map_Ns */ + { AI_MATKEY_TEXTURE_EMISSIVE(0) }, /* m3dp_map_Ke */ + { NULL, 0, 0 }, /* m3dp_map_Tf */ + { AI_MATKEY_TEXTURE_HEIGHT(0) }, /* m3dp_bump */ + { AI_MATKEY_TEXTURE_OPACITY(0) }, /* m3dp_map_d */ + { AI_MATKEY_TEXTURE_REFLECTION(0) }, /* m3dp_refl */ + + { AI_MATKEY_TEXTURE(aiTextureType_DIFFUSE_ROUGHNESS,0) },/* m3dp_map_Pr */ + { AI_MATKEY_TEXTURE(aiTextureType_METALNESS,0) }, /* m3dp_map_Pm */ + { NULL, 0, 0 }, /* m3dp_map_Ps */ + { AI_MATKEY_TEXTURE(aiTextureType_AMBIENT_OCCLUSION,0) },/* m3dp_map_Ni */ + { NULL, 0, 0 }, /* m3dp_map_Nt */ + { NULL, 0, 0 }, + { NULL, 0, 0 }, + { NULL, 0, 0 } +}; + +#endif // AI_M3DMATERIALS_H_INC diff --git a/code/M3D/m3d.h b/code/M3D/m3d.h new file mode 100644 index 000000000..a73e7a02c --- /dev/null +++ b/code/M3D/m3d.h @@ -0,0 +1,5568 @@ +/* + * m3d.h + * + * Copyright (C) 2019 bzt (bztsrc@gitlab) + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * @brief ANSI C89 / C++11 single header importer / exporter SDK for the Model 3D (.M3D) format + * https://gitlab.com/bztsrc/model3d + * + * PNG decompressor included from (with minor modifications to make it C89 valid): + * stb_image - v2.13 - public domain image loader - http://nothings.org/stb_image.h + * + * @version: 1.0.0 + */ + +#ifndef _M3D_H_ +#define _M3D_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/*** configuration ***/ +#ifndef M3D_MALLOC +# define M3D_MALLOC(sz) malloc(sz) +#endif +#ifndef M3D_REALLOC +# define M3D_REALLOC(p,nsz) realloc(p,nsz) +#endif +#ifndef M3D_FREE +# define M3D_FREE(p) free(p) +#endif +#ifndef M3D_LOG +# define M3D_LOG(x) +#endif +#ifndef M3D_APIVERSION +#define M3D_APIVERSION 0x0100 +#ifndef M3D_DOUBLE +typedef float M3D_FLOAT; +#ifndef M3D_EPSILON +/* carefully choosen for IEEE 754 don't change */ +#define M3D_EPSILON ((M3D_FLOAT)1e-7) +#endif +#else +typedef double M3D_FLOAT; +#ifndef M3D_EPSILON +#define M3D_EPSILON ((M3D_FLOAT)1e-14) +#endif +#endif +#if !defined(M3D_SMALLINDEX) +typedef uint32_t M3D_INDEX; +#define M3D_INDEXMAX 0xfffffffe +#else +typedef uint16_t M3D_INDEX; +#define M3D_INDEXMAX 0xfffe +#endif +#ifndef M3D_NUMBONE +#define M3D_NUMBONE 4 +#endif +#ifndef M3D_BONEMAXLEVEL +#define M3D_BONEMAXLEVEL 8 +#endif +#ifndef _MSC_VER +#define _inline __inline__ +#define _pack __attribute__((packed)) +#define _unused __attribute__((unused)) +#else +#define _inline +#define _pack +#define _unused +#endif +#ifndef __cplusplus +#define _register register +#else +#define _register +#endif + +/*** File format structures ***/ + +/** + * M3D file format structure + * 3DMO m3dchunk_t file header chunk, may followed by compressed data + * HEAD m3dhdr_t model header chunk + * n x m3dchunk_t more chunks follow + * PRVW preview chunk (optional) + * CMAP color map chunk (optional) + * TMAP texture map chunk (optional) + * VRTS vertex data chunk (optional if it's a material library) + * BONE bind-pose skeleton, bone hierarchy chunk (optional) + * n x m3db_t contains propably more, but at least one bone + * n x m3ds_t skin group records + * MTRL* material chunk(s), can be more (optional) + * n x m3dp_t each material contains propapbly more, but at least one property + * the properties are configurable with a static array, see m3d_propertytypes + * n x m3dchunk_t at least one, but maybe more face chunks + * PROC* procedural face, or + * MESH* triangle mesh (vertex index list) or + * SHPE* mathematical shapes like parameterized surfaces + * LBLS* annotation label chunks, can be more (optional) + * ACTN* action chunk(s), animation-pose skeletons, can be more (optional) + * n x m3dfr_t each action contains probably more, but at least one frame + * n x m3dtr_t each frame contains probably more, but at least one transformation + * ASET* inlined asset chunk(s), can be more (optional) + * OMD3 end chunk + * + * Typical chunks for a game engine: 3DMO, HEAD, CMAP, TMAP, VRTS, BONE, MTRL, MESH, ACTN, OMD3 + * Typical chunks for CAD software: 3DMO, HEAD, PRVW, CMAP, TMAP, VRTS, MTRL, SHPE, LBLS, OMD3 + */ +#ifdef _MSC_VER +#pragma pack(push) +#pragma pack(1) +#endif + +typedef struct { + char magic[4]; + uint32_t length; + float scale; /* deliberately not M3D_FLOAT */ + uint32_t types; +} _pack m3dhdr_t; + +typedef struct { + char magic[4]; + uint32_t length; +} _pack m3dchunk_t; + +#ifdef _MSC_VER +#pragma pack(pop) +#endif + +/*** in-memory model structure ***/ + +/* textmap entry */ +typedef struct { + M3D_FLOAT u; + M3D_FLOAT v; +} m3dti_t; +#define m3d_textureindex_t m3dti_t + +/* texture */ +typedef struct { + char *name; /* texture name */ + uint8_t *d; /* pixels data */ + uint16_t w; /* width */ + uint16_t h; /* height */ + uint8_t f; /* format, 1 = grayscale, 2 = grayscale+alpha, 3 = rgb, 4 = rgba */ +} m3dtx_t; +#define m3d_texturedata_t m3dtx_t + +typedef struct { + M3D_INDEX vertexid; + M3D_FLOAT weight; +} m3dw_t; +#define m3d_weight_t m3dw_t + +/* bone entry */ +typedef struct { + M3D_INDEX parent; /* parent bone index */ + char *name; /* name for this bone */ + M3D_INDEX pos; /* vertex index position */ + M3D_INDEX ori; /* vertex index orientation (quaternion) */ + M3D_INDEX numweight; /* number of controlled vertices */ + m3dw_t *weight; /* weights for those vertices */ + M3D_FLOAT mat4[16]; /* transformation matrix */ +} m3db_t; +#define m3d_bone_t m3db_t + +/* skin: bone per vertex entry */ +typedef struct { + M3D_INDEX boneid[M3D_NUMBONE]; + M3D_FLOAT weight[M3D_NUMBONE]; +} m3ds_t; +#define m3d_skin_t m3ds_t + +/* vertex entry */ +typedef struct { + M3D_FLOAT x; /* 3D coordinates and weight */ + M3D_FLOAT y; + M3D_FLOAT z; + M3D_FLOAT w; + uint32_t color; /* default vertex color */ + M3D_INDEX skinid; /* skin index */ +#ifdef M3D_VERTEXTYPE + uint8_t type; +#endif +} m3dv_t; +#define m3d_vertex_t m3dv_t + +/* material property formats */ +enum { + m3dpf_color, + m3dpf_uint8, + m3dpf_uint16, + m3dpf_uint32, + m3dpf_float, + m3dpf_map +}; +typedef struct { + uint8_t format; + uint8_t id; +#ifdef M3D_ASCII +#define M3D_PROPERTYDEF(f,i,n) { (f), (i), (char*)(n) } + char *key; +#else +#define M3D_PROPERTYDEF(f,i,n) { (f), (i) } +#endif +} m3dpd_t; + +/* material property types */ +/* You shouldn't change the first 8 display and first 4 physical property. Assign the rest as you like. */ +enum { + m3dp_Kd = 0, /* scalar display properties */ + m3dp_Ka, + m3dp_Ks, + m3dp_Ns, + m3dp_Ke, + m3dp_Tf, + m3dp_Km, + m3dp_d, + m3dp_il, + + m3dp_Pr = 64, /* scalar physical properties */ + m3dp_Pm, + m3dp_Ps, + m3dp_Ni, + m3dp_Nt, + + m3dp_map_Kd = 128, /* textured display map properties */ + m3dp_map_Ka, + m3dp_map_Ks, + m3dp_map_Ns, + m3dp_map_Ke, + m3dp_map_Tf, + m3dp_map_Km, /* bump map */ + m3dp_map_D, + m3dp_map_il, /* reflection map */ + + m3dp_map_Pr = 192, /* textured physical map properties */ + m3dp_map_Pm, + m3dp_map_Ps, + m3dp_map_Ni, + m3dp_map_Nt +}; +enum { /* aliases */ + m3dp_bump = m3dp_map_Km, + m3dp_refl = m3dp_map_Pm +}; + +/* material property */ +typedef struct { + uint8_t type; /* property type, see "m3dp_*" enumeration */ + union { + uint32_t color; /* if value is a color, m3dpf_color */ + uint32_t num; /* if value is a number, m3dpf_uint8, m3pf_uint16, m3dpf_uint32 */ + float fnum; /* if value is a floating point number, m3dpf_float */ + M3D_INDEX textureid; /* if value is a texture, m3dpf_map */ + } value; +} m3dp_t; +#define m3d_property_t m3dp_t + +/* material entry */ +typedef struct { + char *name; /* name of the material */ + uint8_t numprop; /* number of properties */ + m3dp_t *prop; /* properties array */ +} m3dm_t; +#define m3d_material_t m3dm_t + +/* face entry */ +typedef struct { + M3D_INDEX materialid; /* material index */ + M3D_INDEX vertex[3]; /* 3D points of the triangle in CCW order */ + M3D_INDEX normal[3]; /* normal vectors */ + M3D_INDEX texcoord[3]; /* UV coordinates */ +} m3df_t; +#define m3d_face_t m3df_t + +/* shape command types. must match the row in m3d_commandtypes */ +enum { + /* special commands */ + m3dc_use = 0, /* use material */ + m3dc_inc, /* include another shape */ + m3dc_mesh, /* include part of polygon mesh */ + /* approximations */ + m3dc_div, /* subdivision by constant resolution for both u, v */ + m3dc_sub, /* subdivision by constant, different for u and v */ + m3dc_len, /* spacial subdivision by maxlength */ + m3dc_dist, /* subdivision by maxdistance and maxangle */ + /* modifiers */ + m3dc_degu, /* degree for both u, v */ + m3dc_deg, /* separate degree for u and v */ + m3dc_rangeu, /* range for u */ + m3dc_range, /* range for u and v */ + m3dc_paru, /* u parameters (knots) */ + m3dc_parv, /* v parameters */ + m3dc_trim, /* outer trimming curve */ + m3dc_hole, /* inner trimming curve */ + m3dc_scrv, /* spacial curve */ + m3dc_sp, /* special points */ + /* helper curves */ + m3dc_bez1, /* Bezier 1D */ + m3dc_bsp1, /* B-spline 1D */ + m3dc_bez2, /* bezier 2D */ + m3dc_bsp2, /* B-spline 2D */ + /* surfaces */ + m3dc_bezun, /* Bezier 3D with control, UV, normal */ + m3dc_bezu, /* with control and UV */ + m3dc_bezn, /* with control and normal */ + m3dc_bez, /* control points only */ + m3dc_nurbsun, /* B-spline 3D */ + m3dc_nurbsu, + m3dc_nurbsn, + m3dc_nurbs, + m3dc_conn, /* connect surfaces */ + /* geometrical */ + m3dc_line, + m3dc_polygon, + m3dc_circle, + m3dc_cylinder, + m3dc_shpere, + m3dc_torus, + m3dc_cone, + m3dc_cube +}; + +/* shape command argument types */ +enum { + m3dcp_mi_t = 1, /* material index */ + m3dcp_hi_t, /* shape index */ + m3dcp_fi_t, /* face index */ + m3dcp_ti_t, /* texture map index */ + m3dcp_vi_t, /* vertex index */ + m3dcp_qi_t, /* vertex index for quaternions */ + m3dcp_vc_t, /* coordinate or radius, float scalar */ + m3dcp_i1_t, /* int8 scalar */ + m3dcp_i2_t, /* int16 scalar */ + m3dcp_i4_t, /* int32 scalar */ + m3dcp_va_t /* variadic arguments */ +}; + +#define M3D_CMDMAXARG 8 /* if you increase this, add more arguments to the macro below */ +typedef struct { +#ifdef M3D_ASCII +#define M3D_CMDDEF(t,n,p,a,b,c,d,e,f,g,h) { (char*)(n), (p), { (a), (b), (c), (d), (e), (f), (g), (h) } } + char *key; +#else +#define M3D_CMDDEF(t,n,p,a,b,c,d,e,f,g,h) { (p), { (a), (b), (c), (d), (e), (f), (g), (h) } } +#endif + uint8_t p; + uint8_t a[M3D_CMDMAXARG]; +} m3dcd_t; + +/* shape command */ +typedef struct { + uint16_t type; /* shape type */ + uint32_t *arg; /* arguments array */ +} m3dc_t; +#define m3d_shapecommand_t m3dc_t + +/* shape entry */ +typedef struct { + char *name; /* name of the mathematical shape */ + M3D_INDEX group; /* group this shape belongs to or -1 */ + uint32_t numcmd; /* number of commands */ + m3dc_t *cmd; /* commands array */ +} m3dh_t; +#define m3d_shape_t m3dh_t + +/* label entry */ +typedef struct { + char *name; /* name of the annotation group or NULL */ + char *lang; /* language code or NULL */ + char *text; /* the label text */ + uint32_t color; /* color */ + M3D_INDEX vertexid; /* the vertex the label refers to */ +} m3dl_t; +#define m3d_label_t m3dl_t + +/* frame transformations / working copy skeleton entry */ +typedef struct { + M3D_INDEX boneid; /* selects a node in bone hierarchy */ + M3D_INDEX pos; /* vertex index new position */ + M3D_INDEX ori; /* vertex index new orientation (quaternion) */ +} m3dtr_t; +#define m3d_transform_t m3dtr_t + +/* animation frame entry */ +typedef struct { + uint32_t msec; /* frame's position on the timeline, timestamp */ + M3D_INDEX numtransform; /* number of transformations in this frame */ + m3dtr_t *transform; /* transformations */ +} m3dfr_t; +#define m3d_frame_t m3dfr_t + +/* model action entry */ +typedef struct { + char *name; /* name of the action */ + uint32_t durationmsec; /* duration in millisec (1/1000 sec) */ + M3D_INDEX numframe; /* number of frames in this animation */ + m3dfr_t *frame; /* frames array */ +} m3da_t; +#define m3d_action_t m3da_t + +/* inlined asset */ +typedef struct { + char *name; /* asset name (same pointer as in texture[].name) */ + uint8_t *data; /* compressed asset data */ + uint32_t length; /* compressed data length */ +} m3di_t; +#define m3d_inlinedasset_t m3di_t + +/*** in-memory model structure ***/ +#define M3D_FLG_FREERAW (1<<0) +#define M3D_FLG_FREESTR (1<<1) +#define M3D_FLG_MTLLIB (1<<2) +#define M3D_FLG_GENNORM (1<<3) + +typedef struct { + m3dhdr_t *raw; /* pointer to raw data */ + char flags; /* internal flags */ + char errcode; /* returned error code */ + char vc_s, vi_s, si_s, ci_s, ti_s, bi_s, nb_s, sk_s, fc_s, hi_s,fi_s; /* decoded sizes for types */ + char *name; /* name of the model, like "Utah teapot" */ + char *license; /* usage condition or license, like "MIT", "LGPL" or "BSD-3clause" */ + char *author; /* nickname, email, homepage or github URL etc. */ + char *desc; /* comments, descriptions. May contain '\n' newline character */ + M3D_FLOAT scale; /* the model's bounding cube's size in SI meters */ + M3D_INDEX numcmap; + uint32_t *cmap; /* color map */ + M3D_INDEX numtmap; + m3dti_t *tmap; /* texture map indices */ + M3D_INDEX numtexture; + m3dtx_t *texture; /* uncompressed textures */ + M3D_INDEX numbone; + m3db_t *bone; /* bone hierarchy */ + M3D_INDEX numvertex; + m3dv_t *vertex; /* vertex data */ + M3D_INDEX numskin; + m3ds_t *skin; /* skin data */ + M3D_INDEX nummaterial; + m3dm_t *material; /* material list */ + M3D_INDEX numface; + m3df_t *face; /* model face, polygon (triangle) mesh */ + M3D_INDEX numshape; + m3dh_t *shape; /* model face, shape commands */ + M3D_INDEX numlabel; + m3dl_t *label; /* annotation labels */ + M3D_INDEX numaction; + m3da_t *action; /* action animations */ + M3D_INDEX numinlined; + m3di_t *inlined; /* inlined assets */ + M3D_INDEX numextra; + m3dchunk_t **extra; /* unknown chunks, application / engine specific data probably */ + m3di_t preview; /* preview chunk */ +} m3d_t; + +/*** export parameters ***/ +#define M3D_EXP_INT8 0 +#define M3D_EXP_INT16 1 +#define M3D_EXP_FLOAT 2 +#define M3D_EXP_DOUBLE 3 + +#define M3D_EXP_NOCMAP (1<<0) +#define M3D_EXP_NOMATERIAL (1<<1) +#define M3D_EXP_NOFACE (1<<2) +#define M3D_EXP_NONORMAL (1<<3) +#define M3D_EXP_NOTXTCRD (1<<4) +#define M3D_EXP_FLIPTXTCRD (1<<5) +#define M3D_EXP_NORECALC (1<<6) +#define M3D_EXP_IDOSUCK (1<<7) +#define M3D_EXP_NOBONE (1<<8) +#define M3D_EXP_NOACTION (1<<9) +#define M3D_EXP_INLINE (1<<10) +#define M3D_EXP_EXTRA (1<<11) +#define M3D_EXP_NOZLIB (1<<14) +#define M3D_EXP_ASCII (1<<15) + +/*** error codes ***/ +#define M3D_SUCCESS 0 +#define M3D_ERR_ALLOC -1 +#define M3D_ERR_BADFILE -2 +#define M3D_ERR_UNIMPL -65 +#define M3D_ERR_UNKPROP -66 +#define M3D_ERR_UNKMESH -67 +#define M3D_ERR_UNKIMG -68 +#define M3D_ERR_UNKFRAME -69 +#define M3D_ERR_UNKCMD -70 +#define M3D_ERR_TRUNC -71 +#define M3D_ERR_CMAP -72 +#define M3D_ERR_TMAP -73 +#define M3D_ERR_VRTS -74 +#define M3D_ERR_BONE -75 +#define M3D_ERR_MTRL -76 +#define M3D_ERR_SHPE -77 + +#define M3D_ERR_ISFATAL(x) ((x) < 0 && (x) > -65) + +/* callbacks */ +typedef unsigned char *(*m3dread_t)(char *filename, unsigned int *size); /* read file contents into buffer */ +typedef void (*m3dfree_t)(void *buffer); /* free file contents buffer */ +typedef int (*m3dtxsc_t)(const char *name, const void *script, uint32_t len, m3dtx_t *output); /* interpret texture script */ +typedef int (*m3dprsc_t)(const char *name, const void *script, uint32_t len, m3d_t *model); /* interpret surface script */ +#endif /* ifndef M3D_APIVERSION */ + +/*** C prototypes ***/ +/* import / export */ +m3d_t *m3d_load(unsigned char *data, m3dread_t readfilecb, m3dfree_t freecb, m3d_t *mtllib); +unsigned char *m3d_save(m3d_t *model, int quality, int flags, unsigned int *size); +void m3d_free(m3d_t *model); +/* generate animation pose skeleton */ +m3dtr_t *m3d_frame(m3d_t *model, M3D_INDEX actionid, M3D_INDEX frameid, m3dtr_t *skeleton); +m3db_t *m3d_pose(m3d_t *model, M3D_INDEX actionid, uint32_t msec); + +/* private prototypes used by both importer and exporter */ +char *_m3d_safestr(char *in, int morelines); + +/*** C implementation ***/ +#ifdef M3D_IMPLEMENTATION +#if !defined(M3D_NOIMPORTER) || defined(M3D_EXPORTER) +/* material property definitions */ +static m3dpd_t m3d_propertytypes[] = { + M3D_PROPERTYDEF(m3dpf_color, m3dp_Kd, "Kd"), /* diffuse color */ + M3D_PROPERTYDEF(m3dpf_color, m3dp_Ka, "Ka"), /* ambient color */ + M3D_PROPERTYDEF(m3dpf_color, m3dp_Ks, "Ks"), /* specular color */ + M3D_PROPERTYDEF(m3dpf_float, m3dp_Ns, "Ns"), /* specular exponent */ + M3D_PROPERTYDEF(m3dpf_color, m3dp_Ke, "Ke"), /* emissive (emitting light of this color) */ + M3D_PROPERTYDEF(m3dpf_color, m3dp_Tf, "Tf"), /* transmission color */ + M3D_PROPERTYDEF(m3dpf_float, m3dp_Km, "Km"), /* bump strength */ + M3D_PROPERTYDEF(m3dpf_float, m3dp_d, "d"), /* dissolve (transparency) */ + M3D_PROPERTYDEF(m3dpf_uint8, m3dp_il, "il"), /* illumination model (informational, ignored by PBR-shaders) */ + + M3D_PROPERTYDEF(m3dpf_float, m3dp_Pr, "Pr"), /* roughness */ + M3D_PROPERTYDEF(m3dpf_float, m3dp_Pm, "Pm"), /* metallic, also reflection */ + M3D_PROPERTYDEF(m3dpf_float, m3dp_Ps, "Ps"), /* sheen */ + M3D_PROPERTYDEF(m3dpf_float, m3dp_Ni, "Ni"), /* index of refraction (optical density) */ + M3D_PROPERTYDEF(m3dpf_float, m3dp_Nt, "Nt"), /* thickness of face in millimeter, for printing */ + + /* aliases, note that "map_*" aliases are handled automatically */ + M3D_PROPERTYDEF(m3dpf_map, m3dp_map_Km, "bump"), + M3D_PROPERTYDEF(m3dpf_map, m3dp_map_Pm, "refl") +}; +/* shape command definitions. if more commands start with the same string, the longer must come first */ +static m3dcd_t m3d_commandtypes[] = { + /* technical */ + M3D_CMDDEF(m3dc_use, "use", 1, m3dcp_mi_t, 0, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_inc, "inc", 3, m3dcp_hi_t, m3dcp_vi_t, m3dcp_qi_t, m3dcp_vi_t, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_mesh, "mesh", 1, m3dcp_fi_t, m3dcp_fi_t, m3dcp_vi_t, m3dcp_qi_t, m3dcp_vi_t, 0, 0, 0), + /* approximations */ + M3D_CMDDEF(m3dc_div, "div", 1, m3dcp_vc_t, 0, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_sub, "sub", 2, m3dcp_vc_t, m3dcp_vc_t, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_len, "len", 1, m3dcp_vc_t, 0, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_dist, "dist", 2, m3dcp_vc_t, m3dcp_vc_t, 0, 0, 0, 0, 0, 0), + /* modifiers */ + M3D_CMDDEF(m3dc_degu, "degu", 1, m3dcp_i1_t, 0, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_deg, "deg", 2, m3dcp_i1_t, m3dcp_i1_t, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_rangeu, "rangeu", 1, m3dcp_ti_t, 0, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_range, "range", 2, m3dcp_ti_t, m3dcp_ti_t, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_paru, "paru", 2, m3dcp_va_t, m3dcp_vc_t, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_parv, "parv", 2, m3dcp_va_t, m3dcp_vc_t, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_trim, "trim", 3, m3dcp_va_t, m3dcp_ti_t, m3dcp_i2_t, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_hole, "hole", 3, m3dcp_va_t, m3dcp_ti_t, m3dcp_i2_t, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_scrv, "scrv", 3, m3dcp_va_t, m3dcp_ti_t, m3dcp_i2_t, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_sp, "sp", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0), + /* helper curves */ + M3D_CMDDEF(m3dc_bez1, "bez1", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_bsp1, "bsp1", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_bez2, "bez2", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_bsp2, "bsp2", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0), + /* surfaces */ + M3D_CMDDEF(m3dc_bezun, "bezun", 4, m3dcp_va_t, m3dcp_vi_t, m3dcp_ti_t, m3dcp_vi_t, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_bezu, "bezu", 3, m3dcp_va_t, m3dcp_vi_t, m3dcp_ti_t, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_bezn, "bezn", 3, m3dcp_va_t, m3dcp_vi_t, m3dcp_vi_t, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_bez, "bez", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_nurbsun, "nurbsun", 4, m3dcp_va_t, m3dcp_vi_t, m3dcp_ti_t, m3dcp_vi_t, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_nurbsu, "nurbsu", 3, m3dcp_va_t, m3dcp_vi_t, m3dcp_ti_t, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_nurbsn, "nurbsn", 3, m3dcp_va_t, m3dcp_vi_t, m3dcp_vi_t, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_nurbs, "nurbs", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_conn, "conn", 6, m3dcp_i2_t, m3dcp_ti_t, m3dcp_i2_t, m3dcp_i2_t, m3dcp_ti_t, m3dcp_i2_t, 0, 0), + /* geometrical */ + M3D_CMDDEF(m3dc_line, "line", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_polygon, "polygon", 2, m3dcp_va_t, m3dcp_vi_t, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_circle, "circle", 3, m3dcp_vi_t, m3dcp_qi_t, m3dcp_vc_t, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_cylinder,"cylinder",6, m3dcp_vi_t, m3dcp_qi_t, m3dcp_vc_t, m3dcp_vi_t, m3dcp_qi_t, m3dcp_vc_t, 0, 0), + M3D_CMDDEF(m3dc_shpere, "shpere", 2, m3dcp_vi_t, m3dcp_vc_t, 0, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_torus, "torus", 4, m3dcp_vi_t, m3dcp_qi_t, m3dcp_vc_t, m3dcp_vc_t, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_cone, "cone", 3, m3dcp_vi_t, m3dcp_vi_t, m3dcp_vi_t, 0, 0, 0, 0, 0), + M3D_CMDDEF(m3dc_cube, "cube", 3, m3dcp_vi_t, m3dcp_vi_t, m3dcp_vi_t, 0, 0, 0, 0, 0) +}; +#endif + +#include +#include + +#if !defined(M3D_NOIMPORTER) && !defined(STBI_INCLUDE_STB_IMAGE_H) +/* PNG decompressor from + + stb_image - v2.23 - public domain image loader - http://nothings.org/stb_image.h +*/ +static const char *_m3dstbi__g_failure_reason; + +enum +{ + STBI_default = 0, + + STBI_grey = 1, + STBI_grey_alpha = 2, + STBI_rgb = 3, + STBI_rgb_alpha = 4 +}; + +enum +{ + STBI__SCAN_load=0, + STBI__SCAN_type, + STBI__SCAN_header +}; + +typedef unsigned short _m3dstbi_us; + +typedef uint16_t _m3dstbi__uint16; +typedef int16_t _m3dstbi__int16; +typedef uint32_t _m3dstbi__uint32; +typedef int32_t _m3dstbi__int32; + +typedef struct +{ + _m3dstbi__uint32 img_x, img_y; + int img_n, img_out_n; + + void *io_user_data; + + int read_from_callbacks; + int buflen; + unsigned char buffer_start[128]; + + unsigned char *img_buffer, *img_buffer_end; + unsigned char *img_buffer_original, *img_buffer_original_end; +} _m3dstbi__context; + +typedef struct +{ + int bits_per_channel; + int num_channels; + int channel_order; +} _m3dstbi__result_info; + +#define STBI_ASSERT(v) +#define STBI_NOTUSED(v) (void)sizeof(v) +#define STBI__BYTECAST(x) ((unsigned char) ((x) & 255)) +#define STBI_MALLOC(sz) M3D_MALLOC(sz) +#define STBI_REALLOC(p,newsz) M3D_REALLOC(p,newsz) +#define STBI_FREE(p) M3D_FREE(p) +#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz) + +_inline static unsigned char _m3dstbi__get8(_m3dstbi__context *s) +{ + if (s->img_buffer < s->img_buffer_end) + return *s->img_buffer++; + return 0; +} + +_inline static int _m3dstbi__at_eof(_m3dstbi__context *s) +{ + return s->img_buffer >= s->img_buffer_end; +} + +static void _m3dstbi__skip(_m3dstbi__context *s, int n) +{ + if (n < 0) { + s->img_buffer = s->img_buffer_end; + return; + } + s->img_buffer += n; +} + +static int _m3dstbi__getn(_m3dstbi__context *s, unsigned char *buffer, int n) +{ + if (s->img_buffer+n <= s->img_buffer_end) { + memcpy(buffer, s->img_buffer, n); + s->img_buffer += n; + return 1; + } else + return 0; +} + +static int _m3dstbi__get16be(_m3dstbi__context *s) +{ + int z = _m3dstbi__get8(s); + return (z << 8) + _m3dstbi__get8(s); +} + +static _m3dstbi__uint32 _m3dstbi__get32be(_m3dstbi__context *s) +{ + _m3dstbi__uint32 z = _m3dstbi__get16be(s); + return (z << 16) + _m3dstbi__get16be(s); +} + +#define _m3dstbi__err(x,y) _m3dstbi__errstr(y) +static int _m3dstbi__errstr(const char *str) +{ + _m3dstbi__g_failure_reason = str; + return 0; +} + +_inline static void *_m3dstbi__malloc(size_t size) +{ + return STBI_MALLOC(size); +} + +static int _m3dstbi__addsizes_valid(int a, int b) +{ + if (b < 0) return 0; + return a <= 2147483647 - b; +} + +static int _m3dstbi__mul2sizes_valid(int a, int b) +{ + if (a < 0 || b < 0) return 0; + if (b == 0) return 1; + return a <= 2147483647/b; +} + +static int _m3dstbi__mad2sizes_valid(int a, int b, int add) +{ + return _m3dstbi__mul2sizes_valid(a, b) && _m3dstbi__addsizes_valid(a*b, add); +} + +static int _m3dstbi__mad3sizes_valid(int a, int b, int c, int add) +{ + return _m3dstbi__mul2sizes_valid(a, b) && _m3dstbi__mul2sizes_valid(a*b, c) && + _m3dstbi__addsizes_valid(a*b*c, add); +} + +static void *_m3dstbi__malloc_mad2(int a, int b, int add) +{ + if (!_m3dstbi__mad2sizes_valid(a, b, add)) return NULL; + return _m3dstbi__malloc(a*b + add); +} + +static void *_m3dstbi__malloc_mad3(int a, int b, int c, int add) +{ + if (!_m3dstbi__mad3sizes_valid(a, b, c, add)) return NULL; + return _m3dstbi__malloc(a*b*c + add); +} + +static unsigned char _m3dstbi__compute_y(int r, int g, int b) +{ + return (unsigned char) (((r*77) + (g*150) + (29*b)) >> 8); +} + +static unsigned char *_m3dstbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + unsigned char *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (unsigned char *) _m3dstbi__malloc_mad3(req_comp, x, y, 0); + if (good == NULL) { + STBI_FREE(data); + _m3dstbi__err("outofmem", "Out of memory"); + return NULL; + } + + for (j=0; j < (int) y; ++j) { + unsigned char *src = data + j * x * img_n ; + unsigned char *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0], dest[1]=255; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=255; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2],dest[3]=255; } break; + STBI__CASE(3,1) { dest[0]=_m3dstbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=_m3dstbi__compute_y(src[0],src[1],src[2]), dest[1] = 255; } break; + STBI__CASE(4,1) { dest[0]=_m3dstbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=_m3dstbi__compute_y(src[0],src[1],src[2]), dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2]; } break; + default: STBI_ASSERT(0); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} + +static _m3dstbi__uint16 _m3dstbi__compute_y_16(int r, int g, int b) +{ + return (_m3dstbi__uint16) (((r*77) + (g*150) + (29*b)) >> 8); +} + +static _m3dstbi__uint16 *_m3dstbi__convert_format16(_m3dstbi__uint16 *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + _m3dstbi__uint16 *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (_m3dstbi__uint16 *) _m3dstbi__malloc(req_comp * x * y * 2); + if (good == NULL) { + STBI_FREE(data); + _m3dstbi__err("outofmem", "Out of memory"); + return NULL; + } + + for (j=0; j < (int) y; ++j) { + _m3dstbi__uint16 *src = data + j * x * img_n ; + _m3dstbi__uint16 *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0], dest[1]=0xffff; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=0xffff; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0], dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2],dest[3]=0xffff; } break; + STBI__CASE(3,1) { dest[0]=_m3dstbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=_m3dstbi__compute_y_16(src[0],src[1],src[2]), dest[1] = 0xffff; } break; + STBI__CASE(4,1) { dest[0]=_m3dstbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=_m3dstbi__compute_y_16(src[0],src[1],src[2]), dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0],dest[1]=src[1],dest[2]=src[2]; } break; + default: STBI_ASSERT(0); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} + +#define STBI__ZFAST_BITS 9 +#define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) + +typedef struct +{ + _m3dstbi__uint16 fast[1 << STBI__ZFAST_BITS]; + _m3dstbi__uint16 firstcode[16]; + int maxcode[17]; + _m3dstbi__uint16 firstsymbol[16]; + unsigned char size[288]; + _m3dstbi__uint16 value[288]; +} _m3dstbi__zhuffman; + +_inline static int _m3dstbi__bitreverse16(int n) +{ + n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); + n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); + n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); + n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); + return n; +} + +_inline static int _m3dstbi__bit_reverse(int v, int bits) +{ + STBI_ASSERT(bits <= 16); + return _m3dstbi__bitreverse16(v) >> (16-bits); +} + +static int _m3dstbi__zbuild_huffman(_m3dstbi__zhuffman *z, unsigned char *sizelist, int num) +{ + int i,k=0; + int code, next_code[16], sizes[17]; + + memset(sizes, 0, sizeof(sizes)); + memset(z->fast, 0, sizeof(z->fast)); + for (i=0; i < num; ++i) + ++sizes[sizelist[i]]; + sizes[0] = 0; + for (i=1; i < 16; ++i) + if (sizes[i] > (1 << i)) + return _m3dstbi__err("bad sizes", "Corrupt PNG"); + code = 0; + for (i=1; i < 16; ++i) { + next_code[i] = code; + z->firstcode[i] = (_m3dstbi__uint16) code; + z->firstsymbol[i] = (_m3dstbi__uint16) k; + code = (code + sizes[i]); + if (sizes[i]) + if (code-1 >= (1 << i)) return _m3dstbi__err("bad codelengths","Corrupt PNG"); + z->maxcode[i] = code << (16-i); + code <<= 1; + k += sizes[i]; + } + z->maxcode[16] = 0x10000; + for (i=0; i < num; ++i) { + int s = sizelist[i]; + if (s) { + int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; + _m3dstbi__uint16 fastv = (_m3dstbi__uint16) ((s << 9) | i); + z->size [c] = (unsigned char ) s; + z->value[c] = (_m3dstbi__uint16) i; + if (s <= STBI__ZFAST_BITS) { + int j = _m3dstbi__bit_reverse(next_code[s],s); + while (j < (1 << STBI__ZFAST_BITS)) { + z->fast[j] = fastv; + j += (1 << s); + } + } + ++next_code[s]; + } + } + return 1; +} + +typedef struct +{ + unsigned char *zbuffer, *zbuffer_end; + int num_bits; + _m3dstbi__uint32 code_buffer; + + char *zout; + char *zout_start; + char *zout_end; + int z_expandable; + + _m3dstbi__zhuffman z_length, z_distance; +} _m3dstbi__zbuf; + +_inline static unsigned char _m3dstbi__zget8(_m3dstbi__zbuf *z) +{ + if (z->zbuffer >= z->zbuffer_end) return 0; + return *z->zbuffer++; +} + +static void _m3dstbi__fill_bits(_m3dstbi__zbuf *z) +{ + do { + STBI_ASSERT(z->code_buffer < (1U << z->num_bits)); + z->code_buffer |= (unsigned int) _m3dstbi__zget8(z) << z->num_bits; + z->num_bits += 8; + } while (z->num_bits <= 24); +} + +_inline static unsigned int _m3dstbi__zreceive(_m3dstbi__zbuf *z, int n) +{ + unsigned int k; + if (z->num_bits < n) _m3dstbi__fill_bits(z); + k = z->code_buffer & ((1 << n) - 1); + z->code_buffer >>= n; + z->num_bits -= n; + return k; +} + +static int _m3dstbi__zhuffman_decode_slowpath(_m3dstbi__zbuf *a, _m3dstbi__zhuffman *z) +{ + int b,s,k; + k = _m3dstbi__bit_reverse(a->code_buffer, 16); + for (s=STBI__ZFAST_BITS+1; ; ++s) + if (k < z->maxcode[s]) + break; + if (s == 16) return -1; + b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; + STBI_ASSERT(z->size[b] == s); + a->code_buffer >>= s; + a->num_bits -= s; + return z->value[b]; +} + +_inline static int _m3dstbi__zhuffman_decode(_m3dstbi__zbuf *a, _m3dstbi__zhuffman *z) +{ + int b,s; + if (a->num_bits < 16) _m3dstbi__fill_bits(a); + b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; + if (b) { + s = b >> 9; + a->code_buffer >>= s; + a->num_bits -= s; + return b & 511; + } + return _m3dstbi__zhuffman_decode_slowpath(a, z); +} + +static int _m3dstbi__zexpand(_m3dstbi__zbuf *z, char *zout, int n) +{ + char *q; + int cur, limit, old_limit; + z->zout = zout; + if (!z->z_expandable) return _m3dstbi__err("output buffer limit","Corrupt PNG"); + cur = (int) (z->zout - z->zout_start); + limit = old_limit = (int) (z->zout_end - z->zout_start); + while (cur + n > limit) + limit *= 2; + q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit); + STBI_NOTUSED(old_limit); + if (q == NULL) return _m3dstbi__err("outofmem", "Out of memory"); + z->zout_start = q; + z->zout = q + cur; + z->zout_end = q + limit; + return 1; +} + +static int _m3dstbi__zlength_base[31] = { + 3,4,5,6,7,8,9,10,11,13, + 15,17,19,23,27,31,35,43,51,59, + 67,83,99,115,131,163,195,227,258,0,0 }; + +static int _m3dstbi__zlength_extra[31]= +{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; + +static int _m3dstbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, +257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; + +static int _m3dstbi__zdist_extra[32] = +{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + +static int _m3dstbi__parse_huffman_block(_m3dstbi__zbuf *a) +{ + char *zout = a->zout; + for(;;) { + int z = _m3dstbi__zhuffman_decode(a, &a->z_length); + if (z < 256) { + if (z < 0) return _m3dstbi__err("bad huffman code","Corrupt PNG"); + if (zout >= a->zout_end) { + if (!_m3dstbi__zexpand(a, zout, 1)) return 0; + zout = a->zout; + } + *zout++ = (char) z; + } else { + unsigned char *p; + int len,dist; + if (z == 256) { + a->zout = zout; + return 1; + } + z -= 257; + len = _m3dstbi__zlength_base[z]; + if (_m3dstbi__zlength_extra[z]) len += _m3dstbi__zreceive(a, _m3dstbi__zlength_extra[z]); + z = _m3dstbi__zhuffman_decode(a, &a->z_distance); + if (z < 0) return _m3dstbi__err("bad huffman code","Corrupt PNG"); + dist = _m3dstbi__zdist_base[z]; + if (_m3dstbi__zdist_extra[z]) dist += _m3dstbi__zreceive(a, _m3dstbi__zdist_extra[z]); + if (zout - a->zout_start < dist) return _m3dstbi__err("bad dist","Corrupt PNG"); + if (zout + len > a->zout_end) { + if (!_m3dstbi__zexpand(a, zout, len)) return 0; + zout = a->zout; + } + p = (unsigned char *) (zout - dist); + if (dist == 1) { + unsigned char v = *p; + if (len) { do *zout++ = v; while (--len); } + } else { + if (len) { do *zout++ = *p++; while (--len); } + } + } + } +} + +static int _m3dstbi__compute_huffman_codes(_m3dstbi__zbuf *a) +{ + static unsigned char length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + _m3dstbi__zhuffman z_codelength; + unsigned char lencodes[286+32+137]; + unsigned char codelength_sizes[19]; + int i,n; + + int hlit = _m3dstbi__zreceive(a,5) + 257; + int hdist = _m3dstbi__zreceive(a,5) + 1; + int hclen = _m3dstbi__zreceive(a,4) + 4; + int ntot = hlit + hdist; + + memset(codelength_sizes, 0, sizeof(codelength_sizes)); + for (i=0; i < hclen; ++i) { + int s = _m3dstbi__zreceive(a,3); + codelength_sizes[length_dezigzag[i]] = (unsigned char) s; + } + if (!_m3dstbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; + + n = 0; + while (n < ntot) { + int c = _m3dstbi__zhuffman_decode(a, &z_codelength); + if (c < 0 || c >= 19) return _m3dstbi__err("bad codelengths", "Corrupt PNG"); + if (c < 16) + lencodes[n++] = (unsigned char) c; + else { + unsigned char fill = 0; + if (c == 16) { + c = _m3dstbi__zreceive(a,2)+3; + if (n == 0) return _m3dstbi__err("bad codelengths", "Corrupt PNG"); + fill = lencodes[n-1]; + } else if (c == 17) + c = _m3dstbi__zreceive(a,3)+3; + else { + STBI_ASSERT(c == 18); + c = _m3dstbi__zreceive(a,7)+11; + } + if (ntot - n < c) return _m3dstbi__err("bad codelengths", "Corrupt PNG"); + memset(lencodes+n, fill, c); + n += c; + } + } + if (n != ntot) return _m3dstbi__err("bad codelengths","Corrupt PNG"); + if (!_m3dstbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; + if (!_m3dstbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; + return 1; +} + +_inline static int _m3dstbi__parse_uncompressed_block(_m3dstbi__zbuf *a) +{ + unsigned char header[4]; + int len,nlen,k; + if (a->num_bits & 7) + _m3dstbi__zreceive(a, a->num_bits & 7); + k = 0; + while (a->num_bits > 0) { + header[k++] = (unsigned char) (a->code_buffer & 255); + a->code_buffer >>= 8; + a->num_bits -= 8; + } + STBI_ASSERT(a->num_bits == 0); + while (k < 4) + header[k++] = _m3dstbi__zget8(a); + len = header[1] * 256 + header[0]; + nlen = header[3] * 256 + header[2]; + if (nlen != (len ^ 0xffff)) return _m3dstbi__err("zlib corrupt","Corrupt PNG"); + if (a->zbuffer + len > a->zbuffer_end) return _m3dstbi__err("read past buffer","Corrupt PNG"); + if (a->zout + len > a->zout_end) + if (!_m3dstbi__zexpand(a, a->zout, len)) return 0; + memcpy(a->zout, a->zbuffer, len); + a->zbuffer += len; + a->zout += len; + return 1; +} + +static int _m3dstbi__parse_zlib_header(_m3dstbi__zbuf *a) +{ + int cmf = _m3dstbi__zget8(a); + int cm = cmf & 15; + /* int cinfo = cmf >> 4; */ + int flg = _m3dstbi__zget8(a); + if ((cmf*256+flg) % 31 != 0) return _m3dstbi__err("bad zlib header","Corrupt PNG"); + if (flg & 32) return _m3dstbi__err("no preset dict","Corrupt PNG"); + if (cm != 8) return _m3dstbi__err("bad compression","Corrupt PNG"); + return 1; +} + +static unsigned char _m3dstbi__zdefault_length[288], _m3dstbi__zdefault_distance[32]; +static void _m3dstbi__init_zdefaults(void) +{ + int i; + for (i=0; i <= 143; ++i) _m3dstbi__zdefault_length[i] = 8; + for ( ; i <= 255; ++i) _m3dstbi__zdefault_length[i] = 9; + for ( ; i <= 279; ++i) _m3dstbi__zdefault_length[i] = 7; + for ( ; i <= 287; ++i) _m3dstbi__zdefault_length[i] = 8; + + for (i=0; i <= 31; ++i) _m3dstbi__zdefault_distance[i] = 5; +} + +static int _m3dstbi__parse_zlib(_m3dstbi__zbuf *a, int parse_header) +{ + int final, type; + if (parse_header) + if (!_m3dstbi__parse_zlib_header(a)) return 0; + a->num_bits = 0; + a->code_buffer = 0; + do { + final = _m3dstbi__zreceive(a,1); + type = _m3dstbi__zreceive(a,2); + if (type == 0) { + if (!_m3dstbi__parse_uncompressed_block(a)) return 0; + } else if (type == 3) { + return 0; + } else { + if (type == 1) { + _m3dstbi__init_zdefaults(); + if (!_m3dstbi__zbuild_huffman(&a->z_length , _m3dstbi__zdefault_length , 288)) return 0; + if (!_m3dstbi__zbuild_huffman(&a->z_distance, _m3dstbi__zdefault_distance, 32)) return 0; + } else { + if (!_m3dstbi__compute_huffman_codes(a)) return 0; + } + if (!_m3dstbi__parse_huffman_block(a)) return 0; + } + } while (!final); + return 1; +} + +static int _m3dstbi__do_zlib(_m3dstbi__zbuf *a, char *obuf, int olen, int exp, int parse_header) +{ + a->zout_start = obuf; + a->zout = obuf; + a->zout_end = obuf + olen; + a->z_expandable = exp; + + return _m3dstbi__parse_zlib(a, parse_header); +} + +char *_m3dstbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) +{ + _m3dstbi__zbuf a; + char *p = (char *) _m3dstbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (unsigned char *) buffer; + a.zbuffer_end = (unsigned char *) buffer + len; + if (_m3dstbi__do_zlib(&a, p, initial_size, 1, parse_header)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +typedef struct +{ + _m3dstbi__uint32 length; + _m3dstbi__uint32 type; +} _m3dstbi__pngchunk; + +static _m3dstbi__pngchunk _m3dstbi__get_chunk_header(_m3dstbi__context *s) +{ + _m3dstbi__pngchunk c; + c.length = _m3dstbi__get32be(s); + c.type = _m3dstbi__get32be(s); + return c; +} + +_inline static int _m3dstbi__check_png_header(_m3dstbi__context *s) +{ + static unsigned char png_sig[8] = { 137,80,78,71,13,10,26,10 }; + int i; + for (i=0; i < 8; ++i) + if (_m3dstbi__get8(s) != png_sig[i]) return _m3dstbi__err("bad png sig","Not a PNG"); + return 1; +} + +typedef struct +{ + _m3dstbi__context *s; + unsigned char *idata, *expanded, *out; + int depth; +} _m3dstbi__png; + + +enum { + STBI__F_none=0, + STBI__F_sub=1, + STBI__F_up=2, + STBI__F_avg=3, + STBI__F_paeth=4, + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static unsigned char first_row_filter[5] = +{ + STBI__F_none, + STBI__F_sub, + STBI__F_none, + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static int _m3dstbi__paeth(int a, int b, int c) +{ + int p = a + b - c; + int pa = abs(p-a); + int pb = abs(p-b); + int pc = abs(p-c); + if (pa <= pb && pa <= pc) return a; + if (pb <= pc) return b; + return c; +} + +static unsigned char _m3dstbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; + +static int _m3dstbi__create_png_image_raw(_m3dstbi__png *a, unsigned char *raw, _m3dstbi__uint32 raw_len, int out_n, _m3dstbi__uint32 x, _m3dstbi__uint32 y, int depth, int color) +{ + int bytes = (depth == 16? 2 : 1); + _m3dstbi__context *s = a->s; + _m3dstbi__uint32 i,j,stride = x*out_n*bytes; + _m3dstbi__uint32 img_len, img_width_bytes; + int k; + int img_n = s->img_n; + + int output_bytes = out_n*bytes; + int filter_bytes = img_n*bytes; + int width = x; + + STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1); + a->out = (unsigned char *) _m3dstbi__malloc_mad3(x, y, output_bytes, 0); + if (!a->out) return _m3dstbi__err("outofmem", "Out of memory"); + + if (!_m3dstbi__mad3sizes_valid(img_n, x, depth, 7)) return _m3dstbi__err("too large", "Corrupt PNG"); + img_width_bytes = (((img_n * x * depth) + 7) >> 3); + img_len = (img_width_bytes + 1) * y; + if (s->img_x == x && s->img_y == y) { + if (raw_len != img_len) return _m3dstbi__err("not enough pixels","Corrupt PNG"); + } else { + if (raw_len < img_len) return _m3dstbi__err("not enough pixels","Corrupt PNG"); + } + + for (j=0; j < y; ++j) { + unsigned char *cur = a->out + stride*j; + unsigned char *prior = cur - stride; + int filter = *raw++; + + if (filter > 4) + return _m3dstbi__err("invalid filter","Corrupt PNG"); + + if (depth < 8) { + STBI_ASSERT(img_width_bytes <= x); + cur += x*out_n - img_width_bytes; + filter_bytes = 1; + width = img_width_bytes; + } + prior = cur - stride; + + if (j == 0) filter = first_row_filter[filter]; + + for (k=0; k < filter_bytes; ++k) { + switch (filter) { + case STBI__F_none : cur[k] = raw[k]; break; + case STBI__F_sub : cur[k] = raw[k]; break; + case STBI__F_up : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; + case STBI__F_avg : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break; + case STBI__F_paeth : cur[k] = STBI__BYTECAST(raw[k] + _m3dstbi__paeth(0,prior[k],0)); break; + case STBI__F_avg_first : cur[k] = raw[k]; break; + case STBI__F_paeth_first: cur[k] = raw[k]; break; + } + } + + if (depth == 8) { + if (img_n != out_n) + cur[img_n] = 255; + raw += img_n; + cur += out_n; + prior += out_n; + } else if (depth == 16) { + if (img_n != out_n) { + cur[filter_bytes] = 255; + cur[filter_bytes+1] = 255; + } + raw += filter_bytes; + cur += output_bytes; + prior += output_bytes; + } else { + raw += 1; + cur += 1; + prior += 1; + } + + if (depth < 8 || img_n == out_n) { + int nk = (width - 1)*filter_bytes; + #define STBI__CASE(f) \ + case f: \ + for (k=0; k < nk; ++k) + switch (filter) { + case STBI__F_none: memcpy(cur, raw, nk); break; + STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); } break; + STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; + STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); } break; + STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + _m3dstbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); } break; + STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); } break; + STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + _m3dstbi__paeth(cur[k-filter_bytes],0,0)); } break; + } + #undef STBI__CASE + raw += nk; + } else { + STBI_ASSERT(img_n+1 == out_n); + #define STBI__CASE(f) \ + case f: \ + for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \ + for (k=0; k < filter_bytes; ++k) + switch (filter) { + STBI__CASE(STBI__F_none) { cur[k] = raw[k]; } break; + STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); } break; + STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; + STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); } break; + STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + _m3dstbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); } break; + STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); } break; + STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + _m3dstbi__paeth(cur[k- output_bytes],0,0)); } break; + } + #undef STBI__CASE + + if (depth == 16) { + cur = a->out + stride*j; + for (i=0; i < x; ++i,cur+=output_bytes) { + cur[filter_bytes+1] = 255; + } + } + } + } + + if (depth < 8) { + for (j=0; j < y; ++j) { + unsigned char *cur = a->out + stride*j; + unsigned char *in = a->out + stride*j + x*out_n - img_width_bytes; + unsigned char scale = (color == 0) ? _m3dstbi__depth_scale_table[depth] : 1; + + if (depth == 4) { + for (k=x*img_n; k >= 2; k-=2, ++in) { + *cur++ = scale * ((*in >> 4) ); + *cur++ = scale * ((*in ) & 0x0f); + } + if (k > 0) *cur++ = scale * ((*in >> 4) ); + } else if (depth == 2) { + for (k=x*img_n; k >= 4; k-=4, ++in) { + *cur++ = scale * ((*in >> 6) ); + *cur++ = scale * ((*in >> 4) & 0x03); + *cur++ = scale * ((*in >> 2) & 0x03); + *cur++ = scale * ((*in ) & 0x03); + } + if (k > 0) *cur++ = scale * ((*in >> 6) ); + if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03); + if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03); + } else if (depth == 1) { + for (k=x*img_n; k >= 8; k-=8, ++in) { + *cur++ = scale * ((*in >> 7) ); + *cur++ = scale * ((*in >> 6) & 0x01); + *cur++ = scale * ((*in >> 5) & 0x01); + *cur++ = scale * ((*in >> 4) & 0x01); + *cur++ = scale * ((*in >> 3) & 0x01); + *cur++ = scale * ((*in >> 2) & 0x01); + *cur++ = scale * ((*in >> 1) & 0x01); + *cur++ = scale * ((*in ) & 0x01); + } + if (k > 0) *cur++ = scale * ((*in >> 7) ); + if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01); + if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01); + if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01); + if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01); + if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01); + if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01); + } + if (img_n != out_n) { + int q; + cur = a->out + stride*j; + if (img_n == 1) { + for (q=x-1; q >= 0; --q) { + cur[q*2+1] = 255; + cur[q*2+0] = cur[q]; + } + } else { + STBI_ASSERT(img_n == 3); + for (q=x-1; q >= 0; --q) { + cur[q*4+3] = 255; + cur[q*4+2] = cur[q*3+2]; + cur[q*4+1] = cur[q*3+1]; + cur[q*4+0] = cur[q*3+0]; + } + } + } + } + } else if (depth == 16) { + unsigned char *cur = a->out; + _m3dstbi__uint16 *cur16 = (_m3dstbi__uint16*)cur; + + for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) { + *cur16 = (cur[0] << 8) | cur[1]; + } + } + + return 1; +} + +static int _m3dstbi__create_png_image(_m3dstbi__png *a, unsigned char *image_data, _m3dstbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced) +{ + int bytes = (depth == 16 ? 2 : 1); + int out_bytes = out_n * bytes; + unsigned char *final; + int p; + if (!interlaced) + return _m3dstbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color); + + final = (unsigned char *) _m3dstbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0); + for (p=0; p < 7; ++p) { + int xorig[] = { 0,4,0,2,0,1,0 }; + int yorig[] = { 0,0,4,0,2,0,1 }; + int xspc[] = { 8,8,4,4,2,2,1 }; + int yspc[] = { 8,8,8,4,4,2,2 }; + int i,j,x,y; + x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p]; + y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p]; + if (x && y) { + _m3dstbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y; + if (!_m3dstbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) { + STBI_FREE(final); + return 0; + } + for (j=0; j < y; ++j) { + for (i=0; i < x; ++i) { + int out_y = j*yspc[p]+yorig[p]; + int out_x = i*xspc[p]+xorig[p]; + memcpy(final + out_y*a->s->img_x*out_bytes + out_x*out_bytes, + a->out + (j*x+i)*out_bytes, out_bytes); + } + } + STBI_FREE(a->out); + image_data += img_len; + image_data_len -= img_len; + } + } + a->out = final; + + return 1; +} + +static int _m3dstbi__compute_transparency(_m3dstbi__png *z, unsigned char tc[3], int out_n) +{ + _m3dstbi__context *s = z->s; + _m3dstbi__uint32 i, pixel_count = s->img_x * s->img_y; + unsigned char *p = z->out; + + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i=0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 255); + p += 2; + } + } else { + for (i=0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int _m3dstbi__compute_transparency16(_m3dstbi__png *z, _m3dstbi__uint16 tc[3], int out_n) +{ + _m3dstbi__context *s = z->s; + _m3dstbi__uint32 i, pixel_count = s->img_x * s->img_y; + _m3dstbi__uint16 *p = (_m3dstbi__uint16*) z->out; + + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i = 0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 65535); + p += 2; + } + } else { + for (i = 0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int _m3dstbi__expand_png_palette(_m3dstbi__png *a, unsigned char *palette, int len, int pal_img_n) +{ + _m3dstbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; + unsigned char *p, *temp_out, *orig = a->out; + + p = (unsigned char *) _m3dstbi__malloc_mad2(pixel_count, pal_img_n, 0); + if (p == NULL) return _m3dstbi__err("outofmem", "Out of memory"); + + temp_out = p; + + if (pal_img_n == 3) { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p += 3; + } + } else { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p[3] = palette[n+3]; + p += 4; + } + } + STBI_FREE(a->out); + a->out = temp_out; + + STBI_NOTUSED(len); + + return 1; +} + +#define STBI__PNG_TYPE(a,b,c,d) (((unsigned) (a) << 24) + ((unsigned) (b) << 16) + ((unsigned) (c) << 8) + (unsigned) (d)) + +static int _m3dstbi__parse_png_file(_m3dstbi__png *z, int scan, int req_comp) +{ + unsigned char palette[1024], pal_img_n=0; + unsigned char has_trans=0, tc[3]; + _m3dstbi__uint16 tc16[3]; + _m3dstbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; + int first=1,k,interlace=0, color=0; + _m3dstbi__context *s = z->s; + + z->expanded = NULL; + z->idata = NULL; + z->out = NULL; + + if (!_m3dstbi__check_png_header(s)) return 0; + + if (scan == STBI__SCAN_type) return 1; + + for (;;) { + _m3dstbi__pngchunk c = _m3dstbi__get_chunk_header(s); + switch (c.type) { + case STBI__PNG_TYPE('C','g','B','I'): + _m3dstbi__skip(s, c.length); + break; + case STBI__PNG_TYPE('I','H','D','R'): { + int comp,filter; + if (!first) return _m3dstbi__err("multiple IHDR","Corrupt PNG"); + first = 0; + if (c.length != 13) return _m3dstbi__err("bad IHDR len","Corrupt PNG"); + s->img_x = _m3dstbi__get32be(s); if (s->img_x > (1 << 24)) return _m3dstbi__err("too large","Very large image (corrupt?)"); + s->img_y = _m3dstbi__get32be(s); if (s->img_y > (1 << 24)) return _m3dstbi__err("too large","Very large image (corrupt?)"); + z->depth = _m3dstbi__get8(s); if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16) return _m3dstbi__err("1/2/4/8/16-bit only","PNG not supported: 1/2/4/8/16-bit only"); + color = _m3dstbi__get8(s); if (color > 6) return _m3dstbi__err("bad ctype","Corrupt PNG"); + if (color == 3 && z->depth == 16) return _m3dstbi__err("bad ctype","Corrupt PNG"); + if (color == 3) pal_img_n = 3; else if (color & 1) return _m3dstbi__err("bad ctype","Corrupt PNG"); + comp = _m3dstbi__get8(s); if (comp) return _m3dstbi__err("bad comp method","Corrupt PNG"); + filter= _m3dstbi__get8(s); if (filter) return _m3dstbi__err("bad filter method","Corrupt PNG"); + interlace = _m3dstbi__get8(s); if (interlace>1) return _m3dstbi__err("bad interlace method","Corrupt PNG"); + if (!s->img_x || !s->img_y) return _m3dstbi__err("0-pixel image","Corrupt PNG"); + if (!pal_img_n) { + s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return _m3dstbi__err("too large", "Image too large to decode"); + if (scan == STBI__SCAN_header) return 1; + } else { + s->img_n = 1; + if ((1 << 30) / s->img_x / 4 < s->img_y) return _m3dstbi__err("too large","Corrupt PNG"); + } + break; + } + + case STBI__PNG_TYPE('P','L','T','E'): { + if (first) return _m3dstbi__err("first not IHDR", "Corrupt PNG"); + if (c.length > 256*3) return _m3dstbi__err("invalid PLTE","Corrupt PNG"); + pal_len = c.length / 3; + if (pal_len * 3 != c.length) return _m3dstbi__err("invalid PLTE","Corrupt PNG"); + for (i=0; i < pal_len; ++i) { + palette[i*4+0] = _m3dstbi__get8(s); + palette[i*4+1] = _m3dstbi__get8(s); + palette[i*4+2] = _m3dstbi__get8(s); + palette[i*4+3] = 255; + } + break; + } + + case STBI__PNG_TYPE('t','R','N','S'): { + if (first) return _m3dstbi__err("first not IHDR", "Corrupt PNG"); + if (z->idata) return _m3dstbi__err("tRNS after IDAT","Corrupt PNG"); + if (pal_img_n) { + if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; } + if (pal_len == 0) return _m3dstbi__err("tRNS before PLTE","Corrupt PNG"); + if (c.length > pal_len) return _m3dstbi__err("bad tRNS len","Corrupt PNG"); + pal_img_n = 4; + for (i=0; i < c.length; ++i) + palette[i*4+3] = _m3dstbi__get8(s); + } else { + if (!(s->img_n & 1)) return _m3dstbi__err("tRNS with alpha","Corrupt PNG"); + if (c.length != (_m3dstbi__uint32) s->img_n*2) return _m3dstbi__err("bad tRNS len","Corrupt PNG"); + has_trans = 1; + if (z->depth == 16) { + for (k = 0; k < s->img_n; ++k) tc16[k] = (_m3dstbi__uint16)_m3dstbi__get16be(s); + } else { + for (k = 0; k < s->img_n; ++k) tc[k] = (unsigned char)(_m3dstbi__get16be(s) & 255) * _m3dstbi__depth_scale_table[z->depth]; + } + } + break; + } + + case STBI__PNG_TYPE('I','D','A','T'): { + if (first) return _m3dstbi__err("first not IHDR", "Corrupt PNG"); + if (pal_img_n && !pal_len) return _m3dstbi__err("no PLTE","Corrupt PNG"); + if (scan == STBI__SCAN_header) { s->img_n = pal_img_n; return 1; } + if ((int)(ioff + c.length) < (int)ioff) return 0; + if (ioff + c.length > idata_limit) { + _m3dstbi__uint32 idata_limit_old = idata_limit; + unsigned char *p; + if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; + while (ioff + c.length > idata_limit) + idata_limit *= 2; + STBI_NOTUSED(idata_limit_old); + p = (unsigned char *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return _m3dstbi__err("outofmem", "Out of memory"); + z->idata = p; + } + if (!_m3dstbi__getn(s, z->idata+ioff,c.length)) return _m3dstbi__err("outofdata","Corrupt PNG"); + ioff += c.length; + break; + } + + case STBI__PNG_TYPE('I','E','N','D'): { + _m3dstbi__uint32 raw_len, bpl; + if (first) return _m3dstbi__err("first not IHDR", "Corrupt PNG"); + if (scan != STBI__SCAN_load) return 1; + if (z->idata == NULL) return _m3dstbi__err("no IDAT","Corrupt PNG"); + bpl = (s->img_x * z->depth + 7) / 8; + raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */; + z->expanded = (unsigned char *) _m3dstbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, 1); + if (z->expanded == NULL) return 0; + STBI_FREE(z->idata); z->idata = NULL; + if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) + s->img_out_n = s->img_n+1; + else + s->img_out_n = s->img_n; + if (!_m3dstbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0; + if (has_trans) { + if (z->depth == 16) { + if (!_m3dstbi__compute_transparency16(z, tc16, s->img_out_n)) return 0; + } else { + if (!_m3dstbi__compute_transparency(z, tc, s->img_out_n)) return 0; + } + } + if (pal_img_n) { + s->img_n = pal_img_n; + s->img_out_n = pal_img_n; + if (req_comp >= 3) s->img_out_n = req_comp; + if (!_m3dstbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) + return 0; + } else if (has_trans) { + ++s->img_n; + } + STBI_FREE(z->expanded); z->expanded = NULL; + return 1; + } + + default: + if (first) return _m3dstbi__err("first not IHDR", "Corrupt PNG"); + if ((c.type & (1 << 29)) == 0) { + return _m3dstbi__err("invalid_chunk", "PNG not supported: unknown PNG chunk type"); + } + _m3dstbi__skip(s, c.length); + break; + } + _m3dstbi__get32be(s); + } +} + +static void *_m3dstbi__do_png(_m3dstbi__png *p, int *x, int *y, int *n, int req_comp, _m3dstbi__result_info *ri) +{ + void *result=NULL; + if (req_comp < 0 || req_comp > 4) { _m3dstbi__err("bad req_comp", "Internal error"); return NULL; } + if (_m3dstbi__parse_png_file(p, STBI__SCAN_load, req_comp)) { + if (p->depth < 8) + ri->bits_per_channel = 8; + else + ri->bits_per_channel = p->depth; + result = p->out; + p->out = NULL; + if (req_comp && req_comp != p->s->img_out_n) { + if (ri->bits_per_channel == 8) + result = _m3dstbi__convert_format((unsigned char *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + else + result = _m3dstbi__convert_format16((_m3dstbi__uint16 *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + p->s->img_out_n = req_comp; + if (result == NULL) return result; + } + *x = p->s->img_x; + *y = p->s->img_y; + if (n) *n = p->s->img_n; + } + STBI_FREE(p->out); p->out = NULL; + STBI_FREE(p->expanded); p->expanded = NULL; + STBI_FREE(p->idata); p->idata = NULL; + + return result; +} + +static void *_m3dstbi__png_load(_m3dstbi__context *s, int *x, int *y, int *comp, int req_comp, _m3dstbi__result_info *ri) +{ + _m3dstbi__png p; + p.s = s; + return _m3dstbi__do_png(&p, x,y,comp,req_comp, ri); +} +#define stbi__context _m3dstbi__context +#define stbi__result_info _m3dstbi__result_info +#define stbi__png_load _m3dstbi__png_load +#define stbi_zlib_decode_malloc_guesssize_headerflag _m3dstbi_zlib_decode_malloc_guesssize_headerflag +#endif + +#if defined(M3D_EXPORTER) && !defined(INCLUDE_STB_IMAGE_WRITE_H) +/* zlib_compressor from + + stb_image_write - v1.13 - public domain - http://nothings.org/stb/stb_image_write.h +*/ +typedef unsigned char _m3dstbiw__uc; +typedef unsigned short _m3dstbiw__us; + +typedef uint16_t _m3dstbiw__uint16; +typedef int16_t _m3dstbiw__int16; +typedef uint32_t _m3dstbiw__uint32; +typedef int32_t _m3dstbiw__int32; + +#define STBIW_MALLOC(s) M3D_MALLOC(s) +#define STBIW_REALLOC(p,ns) M3D_REALLOC(p,ns) +#define STBIW_REALLOC_SIZED(p,oldsz,newsz) STBIW_REALLOC(p,newsz) +#define STBIW_FREE M3D_FREE +#define STBIW_MEMMOVE memmove +#define STBIW_UCHAR (uint8_t) +#define STBIW_ASSERT(x) +#define _m3dstbiw___sbraw(a) ((int *) (a) - 2) +#define _m3dstbiw___sbm(a) _m3dstbiw___sbraw(a)[0] +#define _m3dstbiw___sbn(a) _m3dstbiw___sbraw(a)[1] + +#define _m3dstbiw___sbneedgrow(a,n) ((a)==0 || _m3dstbiw___sbn(a)+n >= _m3dstbiw___sbm(a)) +#define _m3dstbiw___sbmaybegrow(a,n) (_m3dstbiw___sbneedgrow(a,(n)) ? _m3dstbiw___sbgrow(a,n) : 0) +#define _m3dstbiw___sbgrow(a,n) _m3dstbiw___sbgrowf((void **) &(a), (n), sizeof(*(a))) + +#define _m3dstbiw___sbpush(a, v) (_m3dstbiw___sbmaybegrow(a,1), (a)[_m3dstbiw___sbn(a)++] = (v)) +#define _m3dstbiw___sbcount(a) ((a) ? _m3dstbiw___sbn(a) : 0) +#define _m3dstbiw___sbfree(a) ((a) ? STBIW_FREE(_m3dstbiw___sbraw(a)),0 : 0) + +static void *_m3dstbiw___sbgrowf(void **arr, int increment, int itemsize) +{ + int m = *arr ? 2*_m3dstbiw___sbm(*arr)+increment : increment+1; + void *p = STBIW_REALLOC_SIZED(*arr ? _m3dstbiw___sbraw(*arr) : 0, *arr ? (_m3dstbiw___sbm(*arr)*itemsize + sizeof(int)*2) : 0, itemsize * m + sizeof(int)*2); + STBIW_ASSERT(p); + if (p) { + if (!*arr) ((int *) p)[1] = 0; + *arr = (void *) ((int *) p + 2); + _m3dstbiw___sbm(*arr) = m; + } + return *arr; +} + +static unsigned char *_m3dstbiw___zlib_flushf(unsigned char *data, unsigned int *bitbuffer, int *bitcount) +{ + while (*bitcount >= 8) { + _m3dstbiw___sbpush(data, STBIW_UCHAR(*bitbuffer)); + *bitbuffer >>= 8; + *bitcount -= 8; + } + return data; +} + +static int _m3dstbiw___zlib_bitrev(int code, int codebits) +{ + int res=0; + while (codebits--) { + res = (res << 1) | (code & 1); + code >>= 1; + } + return res; +} + +static unsigned int _m3dstbiw___zlib_countm(unsigned char *a, unsigned char *b, int limit) +{ + int i; + for (i=0; i < limit && i < 258; ++i) + if (a[i] != b[i]) break; + return i; +} + +static unsigned int _m3dstbiw___zhash(unsigned char *data) +{ + _m3dstbiw__uint32 hash = data[0] + (data[1] << 8) + (data[2] << 16); + hash ^= hash << 3; + hash += hash >> 5; + hash ^= hash << 4; + hash += hash >> 17; + hash ^= hash << 25; + hash += hash >> 6; + return hash; +} + +#define _m3dstbiw___zlib_flush() (out = _m3dstbiw___zlib_flushf(out, &bitbuf, &bitcount)) +#define _m3dstbiw___zlib_add(code,codebits) \ + (bitbuf |= (code) << bitcount, bitcount += (codebits), _m3dstbiw___zlib_flush()) +#define _m3dstbiw___zlib_huffa(b,c) _m3dstbiw___zlib_add(_m3dstbiw___zlib_bitrev(b,c),c) +#define _m3dstbiw___zlib_huff1(n) _m3dstbiw___zlib_huffa(0x30 + (n), 8) +#define _m3dstbiw___zlib_huff2(n) _m3dstbiw___zlib_huffa(0x190 + (n)-144, 9) +#define _m3dstbiw___zlib_huff3(n) _m3dstbiw___zlib_huffa(0 + (n)-256,7) +#define _m3dstbiw___zlib_huff4(n) _m3dstbiw___zlib_huffa(0xc0 + (n)-280,8) +#define _m3dstbiw___zlib_huff(n) ((n) <= 143 ? _m3dstbiw___zlib_huff1(n) : (n) <= 255 ? _m3dstbiw___zlib_huff2(n) : (n) <= 279 ? _m3dstbiw___zlib_huff3(n) : _m3dstbiw___zlib_huff4(n)) +#define _m3dstbiw___zlib_huffb(n) ((n) <= 143 ? _m3dstbiw___zlib_huff1(n) : _m3dstbiw___zlib_huff2(n)) + +#define _m3dstbiw___ZHASH 16384 + +unsigned char * _m3dstbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality) +{ + static unsigned short lengthc[] = { 3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258, 259 }; + static unsigned char lengtheb[]= { 0,0,0,0,0,0,0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 }; + static unsigned short distc[] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577, 32768 }; + static unsigned char disteb[] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13 }; + unsigned int bitbuf=0; + int i,j, bitcount=0; + unsigned char *out = NULL; + unsigned char ***hash_table = (unsigned char***) STBIW_MALLOC(_m3dstbiw___ZHASH * sizeof(char**)); + if (hash_table == NULL) + return NULL; + if (quality < 5) quality = 5; + + _m3dstbiw___sbpush(out, 0x78); + _m3dstbiw___sbpush(out, 0x5e); + _m3dstbiw___zlib_add(1,1); + _m3dstbiw___zlib_add(1,2); + + for (i=0; i < _m3dstbiw___ZHASH; ++i) + hash_table[i] = NULL; + + i=0; + while (i < data_len-3) { + int h = _m3dstbiw___zhash(data+i)&(_m3dstbiw___ZHASH-1), best=3; + unsigned char *bestloc = 0; + unsigned char **hlist = hash_table[h]; + int n = _m3dstbiw___sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32768) { + int d = _m3dstbiw___zlib_countm(hlist[j], data+i, data_len-i); + if (d >= best) best=d,bestloc=hlist[j]; + } + } + if (hash_table[h] && _m3dstbiw___sbn(hash_table[h]) == 2*quality) { + STBIW_MEMMOVE(hash_table[h], hash_table[h]+quality, sizeof(hash_table[h][0])*quality); + _m3dstbiw___sbn(hash_table[h]) = quality; + } + _m3dstbiw___sbpush(hash_table[h],data+i); + + if (bestloc) { + h = _m3dstbiw___zhash(data+i+1)&(_m3dstbiw___ZHASH-1); + hlist = hash_table[h]; + n = _m3dstbiw___sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32767) { + int e = _m3dstbiw___zlib_countm(hlist[j], data+i+1, data_len-i-1); + if (e > best) { + bestloc = NULL; + break; + } + } + } + } + + if (bestloc) { + int d = (int) (data+i - bestloc); + STBIW_ASSERT(d <= 32767 && best <= 258); + for (j=0; best > lengthc[j+1]-1; ++j); + _m3dstbiw___zlib_huff(j+257); + if (lengtheb[j]) _m3dstbiw___zlib_add(best - lengthc[j], lengtheb[j]); + for (j=0; d > distc[j+1]-1; ++j); + _m3dstbiw___zlib_add(_m3dstbiw___zlib_bitrev(j,5),5); + if (disteb[j]) _m3dstbiw___zlib_add(d - distc[j], disteb[j]); + i += best; + } else { + _m3dstbiw___zlib_huffb(data[i]); + ++i; + } + } + for (;i < data_len; ++i) + _m3dstbiw___zlib_huffb(data[i]); + _m3dstbiw___zlib_huff(256); + while (bitcount) + _m3dstbiw___zlib_add(0,1); + + for (i=0; i < _m3dstbiw___ZHASH; ++i) + (void) _m3dstbiw___sbfree(hash_table[i]); + STBIW_FREE(hash_table); + + { + unsigned int s1=1, s2=0; + int blocklen = (int) (data_len % 5552); + j=0; + while (j < data_len) { + for (i=0; i < blocklen; ++i) s1 += data[j+i], s2 += s1; + s1 %= 65521, s2 %= 65521; + j += blocklen; + blocklen = 5552; + } + _m3dstbiw___sbpush(out, STBIW_UCHAR(s2 >> 8)); + _m3dstbiw___sbpush(out, STBIW_UCHAR(s2)); + _m3dstbiw___sbpush(out, STBIW_UCHAR(s1 >> 8)); + _m3dstbiw___sbpush(out, STBIW_UCHAR(s1)); + } + *out_len = _m3dstbiw___sbn(out); + STBIW_MEMMOVE(_m3dstbiw___sbraw(out), out, *out_len); + return (unsigned char *) _m3dstbiw___sbraw(out); +} +#define stbi_zlib_compress _m3dstbi_zlib_compress +#else +unsigned char * _m3dstbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality); +#endif + +#define M3D_CHUNKMAGIC(m, a,b,c,d) ((m)[0]==(a) && (m)[1]==(b) && (m)[2]==(c) && (m)[3]==(d)) + +#ifdef M3D_ASCII +#include /* get sprintf */ +#include /* sprintf and strtod cares about number locale */ +#endif + +#if !defined(M3D_NOIMPORTER) && defined(M3D_ASCII) +/* helper functions for the ASCII parser */ +static char *_m3d_findarg(char *s) { + while(s && *s && *s != ' ' && *s != '\t' && *s != '\r' && *s != '\n') s++; + while(s && *s && (*s == ' ' || *s == '\t')) s++; + return s; +} +static char *_m3d_findnl(char *s) { + while(s && *s && *s != '\r' && *s != '\n') s++; + if(*s == '\r') s++; + if(*s == '\n') s++; + return s; +} +static char *_m3d_gethex(char *s, uint32_t *ret) +{ + if(*s == '#') s++; + *ret = 0; + for(; *s; s++) { + if(*s >= '0' && *s <= '9') { *ret <<= 4; *ret += (uint32_t)(*s-'0'); } + else if(*s >= 'a' && *s <= 'f') { *ret <<= 4; *ret += (uint32_t)(*s-'a'+10); } + else if(*s >= 'A' && *s <= 'F') { *ret <<= 4; *ret += (uint32_t)(*s-'A'+10); } + else break; + } + return _m3d_findarg(s); +} +static char *_m3d_getint(char *s, uint32_t *ret) +{ + char *e = s; + if(!s || !*s || *s == '\r' || *s == '\n') return s; + for(; *e >= '0' && *e <= '9'; e++); + *ret = atoi(s); + return e; +} +static char *_m3d_getfloat(char *s, M3D_FLOAT *ret) +{ + char *e = s; + if(!s || !*s || *s == '\r' || *s == '\n') return s; + for(; *e == '-' || *e == '+' || *e == '.' || (*e >= '0' && *e <= '9') || *e == 'e' || *e == 'E'; e++); + *ret = (M3D_FLOAT)strtod(s, NULL); + return _m3d_findarg(e); +} +#endif +#if !defined(M3D_NODUP) && (defined(M3D_ASCII) || defined(M3D_EXPORTER)) +/* helper function to create safe strings */ +char *_m3d_safestr(char *in, int morelines) +{ + char *out, *o, *i = in; + int l; + if(!in || !*in) { + out = (char*)M3D_MALLOC(1); + if(!out) return NULL; + out[0] =0; + } else { + for(o = in, l = 0; *o && ((morelines & 1) || (*o != '\r' && *o != '\n')) && l < 256; o++, l++); + out = o = (char*)M3D_MALLOC(l+1); + if(!out) return NULL; + while(*i == ' ' || *i == '\t' || *i == '\r' || (morelines && *i == '\n')) i++; + for(; *i && (morelines || (*i != '\r' && *i != '\n')); i++) { + if(*i == '\r') continue; + if(*i == '\n') { + if(morelines >= 3 && o > out && *(o-1) == '\n') break; + if(i > in && *(i-1) == '\n') continue; + if(morelines & 1) { + if(morelines == 1) *o++ = '\r'; + *o++ = '\n'; + } else + break; + } else + if(*i == ' ' || *i == '\t') { + *o++ = morelines? ' ' : '_'; + } else + *o++ = !morelines && (*i == '/' || *i == '\\') ? '_' : *i; + } + for(; o > out && (*(o-1) == ' ' || *(o-1) == '\t' || *(o-1) == '\r' || *(o-1) == '\n'); o--); + *o = 0; + out = (char*)M3D_REALLOC(out, (uint64_t)o - (uint64_t)out + 1); + } + return out; +} +#endif +#ifndef M3D_NOIMPORTER +/* helper function to load and decode/generate a texture */ +M3D_INDEX _m3d_gettx(m3d_t *model, m3dread_t readfilecb, m3dfree_t freecb, char *fn) +{ + unsigned int i, len = 0; + unsigned char *buff = NULL; + char *fn2; +#ifdef STBI__PNG_TYPE + unsigned int w, h; + stbi__context s; + stbi__result_info ri; +#endif + + /* do we have loaded this texture already? */ + for(i = 0; i < model->numtexture; i++) + if(!strcmp(fn, model->texture[i].name)) return i; + /* see if it's inlined in the model */ + if(model->inlined) { + for(i = 0; i < model->numinlined; i++) + if(!strcmp(fn, model->inlined[i].name)) { + buff = model->inlined[i].data; + len = model->inlined[i].length; + freecb = NULL; + break; + } + } + /* try to load from external source */ + if(!buff && readfilecb) { + i = strlen(fn); + if(i < 5 || fn[i - 4] != '.') { + fn2 = (char*)M3D_MALLOC(i + 5); + if(!fn2) { model->errcode = M3D_ERR_ALLOC; return (M3D_INDEX)-1U; } + memcpy(fn2, fn, i); + memcpy(fn2+i, ".png", 5); + buff = (*readfilecb)(fn2, &len); + M3D_FREE(fn2); + } + if(!buff) + buff = (*readfilecb)(fn, &len); + } + if(!buff) return (M3D_INDEX)-1U; + /* add to textures array */ + i = model->numtexture++; + model->texture = (m3dtx_t*)M3D_REALLOC(model->texture, model->numtexture * sizeof(m3dtx_t)); + if(!model->texture) { + if(freecb) (*freecb)(buff); + model->errcode = M3D_ERR_ALLOC; + return (M3D_INDEX)-1U; + } + model->texture[i].name = fn; + model->texture[i].w = model->texture[i].h = 0; model->texture[i].d = NULL; + if(buff[0] == 0x89 && buff[1] == 'P' && buff[2] == 'N' && buff[3] == 'G') { +#ifdef STBI__PNG_TYPE + s.read_from_callbacks = 0; + s.img_buffer = s.img_buffer_original = (unsigned char *) buff; + s.img_buffer_end = s.img_buffer_original_end = (unsigned char *) buff+len; + /* don't use model->texture[i].w directly, it's a uint16_t */ + w = h = len = 0; + ri.bits_per_channel = 8; + model->texture[i].d = (uint8_t*)stbi__png_load(&s, (int*)&w, (int*)&h, (int*)&len, 0, &ri); + model->texture[i].w = w; + model->texture[i].h = h; + model->texture[i].f = (uint8_t)len; +#endif + } else { +#ifdef M3D_TX_INTERP + if((model->errcode = M3D_TX_INTERP(fn, buff, len, &model->texture[i])) != M3D_SUCCESS) { + M3D_LOG("Unable to generate texture"); + M3D_LOG(fn); + } +#else + M3D_LOG("Unimplemented interpreter"); + M3D_LOG(fn); +#endif + } + if(freecb) (*freecb)(buff); + if(!model->texture[i].d) + model->errcode = M3D_ERR_UNKIMG; + return i; +} + +/* helper function to load and generate a procedural surface */ +void _m3d_getpr(m3d_t *model, _unused m3dread_t readfilecb, _unused m3dfree_t freecb, _unused char *fn) +{ +#ifdef M3D_PR_INTERP + unsigned int i, len = 0; + unsigned char *buff = readfilecb ? (*readfilecb)(fn, &len) : NULL; + + if(!buff && model->inlined) { + for(i = 0; i < model->numinlined; i++) + if(!strcmp(fn, model->inlined[i].name)) { + buff = model->inlined[i].data; + len = model->inlined[i].length; + freecb = NULL; + break; + } + } + if(!buff || !len || (model->errcode = M3D_PR_INTERP(fn, buff, len, model)) != M3D_SUCCESS) { + M3D_LOG("Unable to generate procedural surface"); + M3D_LOG(fn); + model->errcode = M3D_ERR_UNKIMG; + } + if(freecb && buff) (*freecb)(buff); +#else + M3D_LOG("Unimplemented interpreter"); + M3D_LOG(fn); + model->errcode = M3D_ERR_UNIMPL; +#endif +} +/* helpers to read indices from data stream */ +#define M3D_GETSTR(x) do{offs=0;data=_m3d_getidx(data,model->si_s,&offs);x=offs?((char*)model->raw+16+offs):NULL;}while(0) +_inline static unsigned char *_m3d_getidx(unsigned char *data, char type, M3D_INDEX *idx) +{ + switch(type) { + case 1: *idx = data[0] > 253 ? (int8_t)data[0] : data[0]; data++; break; + case 2: *idx = *((uint16_t*)data) > 65533 ? *((int16_t*)data) : *((uint16_t*)data); data += 2; break; + case 4: *idx = *((int32_t*)data); data += 4; break; + } + return data; +} + +#ifndef M3D_NOANIMATION +/* multiply 4 x 4 matrices. Do not use float *r[16] as argument, because some compilers misinterpret that as + * 16 pointers each pointing to a float, but we need a single pointer to 16 floats. */ +void _m3d_mul(M3D_FLOAT *r, M3D_FLOAT *a, M3D_FLOAT *b) +{ + r[ 0] = b[ 0] * a[ 0] + b[ 4] * a[ 1] + b[ 8] * a[ 2] + b[12] * a[ 3]; + r[ 1] = b[ 1] * a[ 0] + b[ 5] * a[ 1] + b[ 9] * a[ 2] + b[13] * a[ 3]; + r[ 2] = b[ 2] * a[ 0] + b[ 6] * a[ 1] + b[10] * a[ 2] + b[14] * a[ 3]; + r[ 3] = b[ 3] * a[ 0] + b[ 7] * a[ 1] + b[11] * a[ 2] + b[15] * a[ 3]; + r[ 4] = b[ 0] * a[ 4] + b[ 4] * a[ 5] + b[ 8] * a[ 6] + b[12] * a[ 7]; + r[ 5] = b[ 1] * a[ 4] + b[ 5] * a[ 5] + b[ 9] * a[ 6] + b[13] * a[ 7]; + r[ 6] = b[ 2] * a[ 4] + b[ 6] * a[ 5] + b[10] * a[ 6] + b[14] * a[ 7]; + r[ 7] = b[ 3] * a[ 4] + b[ 7] * a[ 5] + b[11] * a[ 6] + b[15] * a[ 7]; + r[ 8] = b[ 0] * a[ 8] + b[ 4] * a[ 9] + b[ 8] * a[10] + b[12] * a[11]; + r[ 9] = b[ 1] * a[ 8] + b[ 5] * a[ 9] + b[ 9] * a[10] + b[13] * a[11]; + r[10] = b[ 2] * a[ 8] + b[ 6] * a[ 9] + b[10] * a[10] + b[14] * a[11]; + r[11] = b[ 3] * a[ 8] + b[ 7] * a[ 9] + b[11] * a[10] + b[15] * a[11]; + r[12] = b[ 0] * a[12] + b[ 4] * a[13] + b[ 8] * a[14] + b[12] * a[15]; + r[13] = b[ 1] * a[12] + b[ 5] * a[13] + b[ 9] * a[14] + b[13] * a[15]; + r[14] = b[ 2] * a[12] + b[ 6] * a[13] + b[10] * a[14] + b[14] * a[15]; + r[15] = b[ 3] * a[12] + b[ 7] * a[13] + b[11] * a[14] + b[15] * a[15]; +} +/* calculate 4 x 4 matrix inverse */ +void _m3d_inv(M3D_FLOAT *m) +{ + M3D_FLOAT r[16]; + M3D_FLOAT det = + m[ 0]*m[ 5]*m[10]*m[15] - m[ 0]*m[ 5]*m[11]*m[14] + m[ 0]*m[ 6]*m[11]*m[13] - m[ 0]*m[ 6]*m[ 9]*m[15] + + m[ 0]*m[ 7]*m[ 9]*m[14] - m[ 0]*m[ 7]*m[10]*m[13] - m[ 1]*m[ 6]*m[11]*m[12] + m[ 1]*m[ 6]*m[ 8]*m[15] + - m[ 1]*m[ 7]*m[ 8]*m[14] + m[ 1]*m[ 7]*m[10]*m[12] - m[ 1]*m[ 4]*m[10]*m[15] + m[ 1]*m[ 4]*m[11]*m[14] + + m[ 2]*m[ 7]*m[ 8]*m[13] - m[ 2]*m[ 7]*m[ 9]*m[12] + m[ 2]*m[ 4]*m[ 9]*m[15] - m[ 2]*m[ 4]*m[11]*m[13] + + m[ 2]*m[ 5]*m[11]*m[12] - m[ 2]*m[ 5]*m[ 8]*m[15] - m[ 3]*m[ 4]*m[ 9]*m[14] + m[ 3]*m[ 4]*m[10]*m[13] + - m[ 3]*m[ 5]*m[10]*m[12] + m[ 3]*m[ 5]*m[ 8]*m[14] - m[ 3]*m[ 6]*m[ 8]*m[13] + m[ 3]*m[ 6]*m[ 9]*m[12]; + if(det == (M3D_FLOAT)0.0 || det == (M3D_FLOAT)-0.0) det = (M3D_FLOAT)1.0; else det = (M3D_FLOAT)1.0 / det; + r[ 0] = det *(m[ 5]*(m[10]*m[15] - m[11]*m[14]) + m[ 6]*(m[11]*m[13] - m[ 9]*m[15]) + m[ 7]*(m[ 9]*m[14] - m[10]*m[13])); + r[ 1] = -det*(m[ 1]*(m[10]*m[15] - m[11]*m[14]) + m[ 2]*(m[11]*m[13] - m[ 9]*m[15]) + m[ 3]*(m[ 9]*m[14] - m[10]*m[13])); + r[ 2] = det *(m[ 1]*(m[ 6]*m[15] - m[ 7]*m[14]) + m[ 2]*(m[ 7]*m[13] - m[ 5]*m[15]) + m[ 3]*(m[ 5]*m[14] - m[ 6]*m[13])); + r[ 3] = -det*(m[ 1]*(m[ 6]*m[11] - m[ 7]*m[10]) + m[ 2]*(m[ 7]*m[ 9] - m[ 5]*m[11]) + m[ 3]*(m[ 5]*m[10] - m[ 6]*m[ 9])); + r[ 4] = -det*(m[ 4]*(m[10]*m[15] - m[11]*m[14]) + m[ 6]*(m[11]*m[12] - m[ 8]*m[15]) + m[ 7]*(m[ 8]*m[14] - m[10]*m[12])); + r[ 5] = det *(m[ 0]*(m[10]*m[15] - m[11]*m[14]) + m[ 2]*(m[11]*m[12] - m[ 8]*m[15]) + m[ 3]*(m[ 8]*m[14] - m[10]*m[12])); + r[ 6] = -det*(m[ 0]*(m[ 6]*m[15] - m[ 7]*m[14]) + m[ 2]*(m[ 7]*m[12] - m[ 4]*m[15]) + m[ 3]*(m[ 4]*m[14] - m[ 6]*m[12])); + r[ 7] = det *(m[ 0]*(m[ 6]*m[11] - m[ 7]*m[10]) + m[ 2]*(m[ 7]*m[ 8] - m[ 4]*m[11]) + m[ 3]*(m[ 4]*m[10] - m[ 6]*m[ 8])); + r[ 8] = det *(m[ 4]*(m[ 9]*m[15] - m[11]*m[13]) + m[ 5]*(m[11]*m[12] - m[ 8]*m[15]) + m[ 7]*(m[ 8]*m[13] - m[ 9]*m[12])); + r[ 9] = -det*(m[ 0]*(m[ 9]*m[15] - m[11]*m[13]) + m[ 1]*(m[11]*m[12] - m[ 8]*m[15]) + m[ 3]*(m[ 8]*m[13] - m[ 9]*m[12])); + r[10] = det *(m[ 0]*(m[ 5]*m[15] - m[ 7]*m[13]) + m[ 1]*(m[ 7]*m[12] - m[ 4]*m[15]) + m[ 3]*(m[ 4]*m[13] - m[ 5]*m[12])); + r[11] = -det*(m[ 0]*(m[ 5]*m[11] - m[ 7]*m[ 9]) + m[ 1]*(m[ 7]*m[ 8] - m[ 4]*m[11]) + m[ 3]*(m[ 4]*m[ 9] - m[ 5]*m[ 8])); + r[12] = -det*(m[ 4]*(m[ 9]*m[14] - m[10]*m[13]) + m[ 5]*(m[10]*m[12] - m[ 8]*m[14]) + m[ 6]*(m[ 8]*m[13] - m[ 9]*m[12])); + r[13] = det *(m[ 0]*(m[ 9]*m[14] - m[10]*m[13]) + m[ 1]*(m[10]*m[12] - m[ 8]*m[14]) + m[ 2]*(m[ 8]*m[13] - m[ 9]*m[12])); + r[14] = -det*(m[ 0]*(m[ 5]*m[14] - m[ 6]*m[13]) + m[ 1]*(m[ 6]*m[12] - m[ 4]*m[14]) + m[ 2]*(m[ 4]*m[13] - m[ 5]*m[12])); + r[15] = det *(m[ 0]*(m[ 5]*m[10] - m[ 6]*m[ 9]) + m[ 1]*(m[ 6]*m[ 8] - m[ 4]*m[10]) + m[ 2]*(m[ 4]*m[ 9] - m[ 5]*m[ 8])); + memcpy(m, &r, sizeof(r)); +} +/* compose a coloumn major 4 x 4 matrix from vec3 position and vec4 orientation/rotation quaternion */ +void _m3d_mat(M3D_FLOAT *r, m3dv_t *p, m3dv_t *q) +{ + if(q->x == (M3D_FLOAT)0.0 && q->y == (M3D_FLOAT)0.0 && q->z >=(M3D_FLOAT) 0.7071065 && q->z <= (M3D_FLOAT)0.7071075 && + q->w == (M3D_FLOAT)0.0) { + r[ 1] = r[ 2] = r[ 4] = r[ 6] = r[ 8] = r[ 9] = (M3D_FLOAT)0.0; + r[ 0] = r[ 5] = r[10] = (M3D_FLOAT)-1.0; + } else { + r[ 0] = 1 - 2 * (q->y * q->y + q->z * q->z); if(r[ 0]>-M3D_EPSILON && r[ 0]x * q->y - q->z * q->w); if(r[ 1]>-M3D_EPSILON && r[ 1]x * q->z + q->y * q->w); if(r[ 2]>-M3D_EPSILON && r[ 2]x * q->y + q->z * q->w); if(r[ 4]>-M3D_EPSILON && r[ 4]x * q->x + q->z * q->z); if(r[ 5]>-M3D_EPSILON && r[ 5]y * q->z - q->x * q->w); if(r[ 6]>-M3D_EPSILON && r[ 6]x * q->z - q->y * q->w); if(r[ 8]>-M3D_EPSILON && r[ 8]y * q->z + q->x * q->w); if(r[ 9]>-M3D_EPSILON && r[ 9]x * q->x + q->y * q->y); if(r[10]>-M3D_EPSILON && r[10]x; r[ 7] = p->y; r[11] = p->z; + r[12] = 0; r[13] = 0; r[14] = 0; r[15] = 1; +} +#endif +#if !defined(M3D_NOANIMATION) || !defined(M3D_NONORMALS) +/* fast inverse square root calculation. returns 1/sqrt(x) */ +static M3D_FLOAT _m3d_rsq(M3D_FLOAT x) +{ +#ifdef M3D_DOUBLE + return ((M3D_FLOAT)15.0/(M3D_FLOAT)8.0) + ((M3D_FLOAT)-5.0/(M3D_FLOAT)4.0)*x + ((M3D_FLOAT)3.0/(M3D_FLOAT)8.0)*x*x; +#else + /* John Carmack's */ + float x2 = x * 0.5f; + *((uint32_t*)&x) = (0x5f3759df - (*((uint32_t*)&x) >> 1)); + return x * (1.5f - (x2 * x * x)); +#endif +} +#endif + +/** + * Function to decode a Model 3D into in-memory format + */ +m3d_t *m3d_load(unsigned char *data, m3dread_t readfilecb, m3dfree_t freecb, m3d_t *mtllib) +{ + unsigned char *end, *chunk, *buff, weights[8]; + unsigned int i, j, k, l, n, am, len = 0, reclen, offs; + char *name, *lang; + float f; + m3d_t *model; + M3D_INDEX mi; + M3D_FLOAT w; + m3dcd_t *cd; + m3dtx_t *tx; + m3dh_t *h; + m3dm_t *m; + m3da_t *a; + m3di_t *t; +#ifndef M3D_NONORMALS + m3dv_t *norm = NULL, *v0, *v1, *v2, va, vb; +#endif +#ifndef M3D_NOANIMATION + M3D_FLOAT r[16]; +#endif +#if !defined(M3D_NOWEIGHTS) || !defined(M3D_NOANIMATION) + m3db_t *b; +#endif +#ifndef M3D_NOWEIGHTS + m3ds_t *sk; +#endif +#ifdef M3D_ASCII + m3ds_t s; + M3D_INDEX bi[M3D_BONEMAXLEVEL+1], level; + const char *ol; + char *ptr, *pe, *fn; +#endif + + if(!data || (!M3D_CHUNKMAGIC(data, '3','D','M','O') +#ifdef M3D_ASCII + && !M3D_CHUNKMAGIC(data, '3','d','m','o') +#endif + )) return NULL; + model = (m3d_t*)M3D_MALLOC(sizeof(m3d_t)); + if(!model) { + M3D_LOG("Out of memory"); + return NULL; + } + memset(model, 0, sizeof(m3d_t)); + + if(mtllib) { + model->nummaterial = mtllib->nummaterial; + model->material = mtllib->material; + model->numtexture = mtllib->numtexture; + model->texture = mtllib->texture; + model->flags |= M3D_FLG_MTLLIB; + } +#ifdef M3D_ASCII + /* ASCII variant? */ + if(M3D_CHUNKMAGIC(data, '3','d','m','o')) { + model->errcode = M3D_ERR_BADFILE; + model->flags |= M3D_FLG_FREESTR; + model->raw = (m3dhdr_t*)data; + ptr = (char*)data; + ol = setlocale(LC_NUMERIC, NULL); + setlocale(LC_NUMERIC, "C"); + /* parse header. Don't use sscanf, that's incredibly slow */ + ptr = _m3d_findarg(ptr); + if(!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; + pe = _m3d_findnl(ptr); + model->scale = (float)strtod(ptr, NULL); ptr = pe; + if(model->scale <= (M3D_FLOAT)0.0) model->scale = (M3D_FLOAT)1.0; + model->name = _m3d_safestr(ptr, 2); ptr = _m3d_findnl(ptr); + if(!*ptr) goto asciiend; + model->license = _m3d_safestr(ptr, 2); ptr = _m3d_findnl(ptr); + if(!*ptr) goto asciiend; + model->author = _m3d_safestr(ptr, 2); ptr = _m3d_findnl(ptr); + if(!*ptr) goto asciiend; + if(*ptr != '\r' && *ptr != '\n') + model->desc = _m3d_safestr(ptr, 3); + while(*ptr) { + while(*ptr && *ptr!='\n') ptr++; + ptr++; if(*ptr=='\r') ptr++; + if(*ptr == '\n') break; + } + + /* the main chunk reader loop */ + while(*ptr) { + while(*ptr && (*ptr == '\r' || *ptr == '\n')) ptr++; + if(!*ptr || (ptr[0]=='E' && ptr[1]=='n' && ptr[2]=='d')) break; + /* make sure there's at least one data row */ + pe = ptr; ptr = _m3d_findnl(ptr); + if(!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; + /* Preview chunk */ + if(!memcmp(pe, "Preview", 7)) { + if(readfilecb) { + pe = _m3d_safestr(ptr, 0); + if(!pe || !*pe) goto asciiend; + model->preview.data = (*readfilecb)(pe, &model->preview.length); + M3D_FREE(pe); + } + while(*ptr && *ptr != '\r' && *ptr != '\n') + ptr = _m3d_findnl(ptr); + } else + /* texture map chunk */ + if(!memcmp(pe, "Textmap", 7)) { + if(model->tmap) { M3D_LOG("More texture map chunks, should be unique"); goto asciiend; } + while(*ptr && *ptr != '\r' && *ptr != '\n') { + i = model->numtmap++; + model->tmap = (m3dti_t*)M3D_REALLOC(model->tmap, model->numtmap * sizeof(m3dti_t)); + if(!model->tmap) goto memerr; + ptr = _m3d_getfloat(ptr, &model->tmap[i].u); + if(!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; + _m3d_getfloat(ptr, &model->tmap[i].v); + ptr = _m3d_findnl(ptr); + } + } else + /* vertex chunk */ + if(!memcmp(pe, "Vertex", 6)) { + if(model->vertex) { M3D_LOG("More vertex chunks, should be unique"); goto asciiend; } + while(*ptr && *ptr != '\r' && *ptr != '\n') { + i = model->numvertex++; + model->vertex = (m3dv_t*)M3D_REALLOC(model->vertex, model->numvertex * sizeof(m3dv_t)); + if(!model->vertex) goto memerr; + memset(&model->vertex[i], 0, sizeof(m3dv_t)); + model->vertex[i].skinid = (M3D_INDEX)-1U; + model->vertex[i].color = 0; + model->vertex[i].w = (M3D_FLOAT)1.0; + ptr = _m3d_getfloat(ptr, &model->vertex[i].x); + if(!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; + ptr = _m3d_getfloat(ptr, &model->vertex[i].y); + if(!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; + ptr = _m3d_getfloat(ptr, &model->vertex[i].z); + if(!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; + ptr = _m3d_getfloat(ptr, &model->vertex[i].w); + if(!*ptr) goto asciiend; + if(*ptr == '#') { + ptr = _m3d_gethex(ptr, &model->vertex[i].color); + if(!*ptr) goto asciiend; + } + /* parse skin */ + memset(&s, 0, sizeof(m3ds_t)); + for(j = 0, w = (M3D_FLOAT)0.0; j < M3D_NUMBONE && *ptr && *ptr != '\r' && *ptr != '\n'; j++) { + ptr = _m3d_findarg(ptr); + if(!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; + ptr = _m3d_getint(ptr, &k); + s.boneid[j] = (M3D_INDEX)k; + if(*ptr == ':') { + ptr++; + ptr = _m3d_getfloat(ptr, &s.weight[j]); + w += s.weight[j]; + } else if(!j) + s.weight[j] = (M3D_FLOAT)1.0; + if(!*ptr) goto asciiend; + } + if(s.boneid[0] != (M3D_INDEX)-1U && s.weight[0] > (M3D_FLOAT)0.0) { + if(w != (M3D_FLOAT)1.0 && w != (M3D_FLOAT)0.0) + for(j = 0; j < M3D_NUMBONE && s.weight[j] > (M3D_FLOAT)0.0; j++) + s.weight[j] /= w; + k = -1U; + if(model->skin) { + for(j = 0; j < model->numskin; j++) + if(!memcmp(&model->skin[j], &s, sizeof(m3ds_t))) { k = j; break; } + } + if(k == -1U) { + k = model->numskin++; + model->skin = (m3ds_t*)M3D_REALLOC(model->skin, model->numskin * sizeof(m3ds_t)); + memcpy(&model->skin[k], &s, sizeof(m3ds_t)); + } + model->vertex[i].skinid = (M3D_INDEX)k; + } + ptr = _m3d_findnl(ptr); + } + } else + /* Skeleton, bone hierarchy */ + if(!memcmp(pe, "Bones", 5)) { + if(model->bone) { M3D_LOG("More bones chunks, should be unique"); goto asciiend; } + bi[0] = (M3D_INDEX)-1U; + while(*ptr && *ptr != '\r' && *ptr != '\n') { + i = model->numbone++; + model->bone = (m3db_t*)M3D_REALLOC(model->bone, model->numbone * sizeof(m3db_t)); + if(!model->bone) goto memerr; + for(level = 0; *ptr == '/'; ptr++, level++); + if(level > M3D_BONEMAXLEVEL || !*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; + bi[level+1] = i; + model->bone[i].numweight = 0; + model->bone[i].weight = NULL; + model->bone[i].parent = bi[level]; + ptr = _m3d_getint(ptr, &k); + ptr = _m3d_findarg(ptr); + if(!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; + model->bone[i].pos = (M3D_INDEX)k; + ptr = _m3d_getint(ptr, &k); + ptr = _m3d_findarg(ptr); + if(!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; + model->bone[i].ori = (M3D_INDEX)k; + model->vertex[k].skinid = (M3D_INDEX)-2U; + pe = _m3d_safestr(ptr, 0); + if(!pe || !*pe) goto asciiend; + model->bone[i].name = pe; + ptr = _m3d_findnl(ptr); + } + } else + /* material chunk */ + if(!memcmp(pe, "Material", 8)) { + pe = _m3d_findarg(pe); + if(!*pe || *pe == '\r' || *pe == '\n') goto asciiend; + pe = _m3d_safestr(pe, 0); + if(!pe || !*pe) goto asciiend; + for(i = 0; i < model->nummaterial; i++) + if(!strcmp(pe, model->material[i].name)) { + M3D_LOG("Multiple definitions for material"); + M3D_LOG(pe); + M3D_FREE(pe); + pe = NULL; + while(*ptr && *ptr != '\r' && *ptr != '\n') ptr = _m3d_findnl(ptr); + break; + } + if(!pe) continue; + i = model->nummaterial++; + if(model->flags & M3D_FLG_MTLLIB) { + m = model->material; + model->material = (m3dm_t*)M3D_MALLOC(model->nummaterial * sizeof(m3dm_t)); + if(!model->material) goto memerr; + memcpy(model->material, m, (model->nummaterial - 1) * sizeof(m3dm_t)); + if(model->texture) { + tx = model->texture; + model->texture = (m3dtx_t*)M3D_MALLOC(model->numtexture * sizeof(m3dtx_t)); + if(!model->texture) goto memerr; + memcpy(model->texture, tx, model->numtexture * sizeof(m3dm_t)); + } + model->flags &= ~M3D_FLG_MTLLIB; + } else { + model->material = (m3dm_t*)M3D_REALLOC(model->material, model->nummaterial * sizeof(m3dm_t)); + if(!model->material) goto memerr; + } + m = &model->material[i]; + m->name = pe; + m->numprop = 0; + m->prop = NULL; + while(*ptr && *ptr != '\r' && *ptr != '\n') { + k = n = 256; + if(*ptr == 'm' && *(ptr+1) == 'a' && *(ptr+2) == 'p' && *(ptr+3) == '_') { + k = m3dpf_map; + ptr += 4; + } + for(j = 0; j < sizeof(m3d_propertytypes)/sizeof(m3d_propertytypes[0]); j++) + if(!memcmp(ptr, m3d_propertytypes[j].key, strlen(m3d_propertytypes[j].key))) { + n = m3d_propertytypes[j].id; + if(k != m3dpf_map) k = m3d_propertytypes[j].format; + break; + } + if(n != 256 && k != 256) { + ptr = _m3d_findarg(ptr); + if(!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; + j = m->numprop++; + m->prop = (m3dp_t*)M3D_REALLOC(m->prop, m->numprop * sizeof(m3dp_t)); + if(!m->prop) goto memerr; + m->prop[j].type = n + (k == m3dpf_map && n < 128 ? 128 : 0); + switch(k) { + case m3dpf_color: ptr = _m3d_gethex(ptr, &m->prop[j].value.color); break; + case m3dpf_uint8: + case m3dpf_uint16: + case m3dpf_uint32: ptr = _m3d_getint(ptr, &m->prop[j].value.num); break; + case m3dpf_float: ptr = _m3d_getfloat(ptr, &m->prop[j].value.fnum); break; + case m3dpf_map: + pe = _m3d_safestr(ptr, 0); + if(!pe || !*pe) goto asciiend; + m->prop[j].value.textureid = _m3d_gettx(model, readfilecb, freecb, pe); + if(model->errcode == M3D_ERR_ALLOC) { M3D_FREE(pe); goto memerr; } + if(m->prop[j].value.textureid == (M3D_INDEX)-1U) { + M3D_LOG("Texture not found"); + M3D_LOG(pe); + m->numprop--; + } + M3D_FREE(pe); + break; + } + } else { + M3D_LOG("Unknown material property in"); + M3D_LOG(m->name); + model->errcode = M3D_ERR_UNKPROP; + } + ptr = _m3d_findnl(ptr); + } + if(!m->numprop) model->nummaterial--; + } else + /* procedural */ + if(!memcmp(pe, "Procedural", 10)) { + pe = _m3d_safestr(ptr, 0); + _m3d_getpr(model, readfilecb, freecb, pe); + M3D_FREE(pe); + while(*ptr && *ptr != '\r' && *ptr != '\n') ptr = _m3d_findnl(ptr); + } else + /* mesh */ + if(!memcmp(pe, "Mesh", 4)) { + mi = (M3D_INDEX)-1U; + while(*ptr && *ptr != '\r' && *ptr != '\n') { + if(*ptr == 'u') { + ptr = _m3d_findarg(ptr); + if(!*ptr) goto asciiend; + mi = (M3D_INDEX)-1U; + if(*ptr != '\r' && *ptr != '\n') { + pe = _m3d_safestr(ptr, 0); + if(!pe || !*pe) goto asciiend; + for(j = 0; j < model->nummaterial; j++) + if(!strcmp(pe, model->material[j].name)) { mi = (M3D_INDEX)j; break; } + if(mi == (M3D_INDEX)-1U && !(model->flags & M3D_FLG_MTLLIB)) { + mi = model->nummaterial++; + model->material = (m3dm_t*)M3D_REALLOC(model->material, model->nummaterial * sizeof(m3dm_t)); + if(!model->material) goto memerr; + model->material[mi].name = pe; + model->material[mi].numprop = 1; + model->material[mi].prop = NULL; + } else + M3D_FREE(pe); + } + } else { + i = model->numface++; + model->face = (m3df_t*)M3D_REALLOC(model->face, model->numface * sizeof(m3df_t)); + if(!model->face) goto memerr; + memset(&model->face[i], 255, sizeof(m3df_t)); /* set all index to -1 by default */ + model->face[i].materialid = mi; + /* hardcoded triangles. */ + for(j = 0; j < 3; j++) { + /* vertex */ + ptr = _m3d_getint(ptr, &k); + model->face[i].vertex[j] = (M3D_INDEX)k; + if(!*ptr) goto asciiend; + if(*ptr == '/') { + ptr++; + if(*ptr != '/') { + /* texcoord */ + ptr = _m3d_getint(ptr, &k); + model->face[i].texcoord[j] = (M3D_INDEX)k; + if(!*ptr) goto asciiend; + } + if(*ptr == '/') { + ptr++; + /* normal */ + ptr = _m3d_getint(ptr, &k); + model->face[i].normal[j] = (M3D_INDEX)k; + if(!*ptr) goto asciiend; + } + } + ptr = _m3d_findarg(ptr); + } + } + ptr = _m3d_findnl(ptr); + } + } else + /* mathematical shape */ + if(!memcmp(pe, "Shape", 5)) { + pe = _m3d_findarg(pe); + if(!*pe || *pe == '\r' || *pe == '\n') goto asciiend; + pe = _m3d_safestr(pe, 0); + if(!pe || !*pe) goto asciiend; + i = model->numshape++; + model->shape = (m3dh_t*)M3D_REALLOC(model->shape, model->numshape * sizeof(m3ds_t)); + if(!model->shape) goto memerr; + h = &model->shape[i]; + h->name = pe; + h->group = (M3D_INDEX)-1U; + h->numcmd = 0; + h->cmd = NULL; + while(*ptr && *ptr != '\r' && *ptr != '\n') { + if(!memcmp(ptr, "group", 5)) { + ptr = _m3d_findarg(ptr); + ptr = _m3d_getint(ptr, &h->group); + ptr = _m3d_findnl(ptr); + if(h->group != (M3D_INDEX)-1U && h->group >= model->numbone) { + M3D_LOG("Unknown bone id as shape group in shape"); + M3D_LOG(pe); + h->group = (M3D_INDEX)-1U; + model->errcode = M3D_ERR_SHPE; + } + continue; + } + for(cd = NULL, k = 0; k < (unsigned int)(sizeof(m3d_commandtypes)/sizeof(m3d_commandtypes[0])); k++) { + j = strlen(m3d_commandtypes[k].key); + if(!memcmp(ptr, m3d_commandtypes[k].key, j) && (ptr[j] == ' ' || ptr[j] == '\r' || ptr[j] == '\n')) + { cd = &m3d_commandtypes[k]; break; } + } + if(cd) { + j = h->numcmd++; + h->cmd = (m3dc_t*)M3D_REALLOC(h->cmd, h->numcmd * sizeof(m3dc_t)); + if(!h->cmd) goto memerr; + h->cmd[j].type = k; + h->cmd[j].arg = (uint32_t*)M3D_MALLOC(cd->p * sizeof(uint32_t)); + if(!h->cmd[j].arg) goto memerr; + memset(h->cmd[j].arg, 0, cd->p * sizeof(uint32_t)); + for(k = n = 0, l = cd->p; k < l; k++) { + ptr = _m3d_findarg(ptr); + if(!*ptr) goto asciiend; + if(*ptr == '[') { + ptr = _m3d_findarg(ptr + 1); + if(!*ptr) goto asciiend; + } + if(*ptr == ']' || *ptr == '\r' || *ptr == '\n') break; + switch(cd->a[((k - n) % (cd->p - n)) + n]) { + case m3dcp_mi_t: + mi = (M3D_INDEX)-1U; + if(*ptr != '\r' && *ptr != '\n') { + pe = _m3d_safestr(ptr, 0); + if(!pe || !*pe) goto asciiend; + for(n = 0; n < model->nummaterial; n++) + if(!strcmp(pe, model->material[n].name)) { mi = (M3D_INDEX)n; break; } + if(mi == (M3D_INDEX)-1U && !(model->flags & M3D_FLG_MTLLIB)) { + mi = model->nummaterial++; + model->material = (m3dm_t*)M3D_REALLOC(model->material, + model->nummaterial * sizeof(m3dm_t)); + if(!model->material) goto memerr; + model->material[mi].name = pe; + model->material[mi].numprop = 1; + model->material[mi].prop = NULL; + } else + M3D_FREE(pe); + } + h->cmd[j].arg[k] = mi; + break; + case m3dcp_vc_t: + _m3d_getfloat(ptr, &w); + h->cmd[j].arg[k] = *((uint32_t*)&w); + break; + case m3dcp_va_t: + ptr = _m3d_getint(ptr, &h->cmd[j].arg[k]); + n = k + 1; l += (h->cmd[j].arg[k] - 1) * (cd->p - k - 1); + h->cmd[j].arg = (uint32_t*)M3D_REALLOC(h->cmd[j].arg, l * sizeof(uint32_t)); + if(!h->cmd[j].arg) goto memerr; + memset(&h->cmd[j].arg[k + 1], 0, (l - k - 1) * sizeof(uint32_t)); + break; + case m3dcp_qi_t: + ptr = _m3d_getint(ptr, &h->cmd[j].arg[k]); + model->vertex[h->cmd[i].arg[k]].skinid = (M3D_INDEX)-2U; + break; + default: + ptr = _m3d_getint(ptr, &h->cmd[j].arg[k]); + break; + } + } + } else { + M3D_LOG("Unknown shape command in"); + M3D_LOG(h->name); + model->errcode = M3D_ERR_UNKCMD; + } + ptr = _m3d_findnl(ptr); + } + if(!h->numcmd) model->numshape--; + } else + /* annotation labels */ + if(!memcmp(pe, "Labels", 6)) { + pe = _m3d_findarg(pe); + if(!*pe) goto asciiend; + if(*pe == '\r' || *pe == '\n') pe = NULL; + else pe = _m3d_safestr(pe, 0); + k = 0; fn = NULL; + while(*ptr && *ptr != '\r' && *ptr != '\n') { + if(*ptr == 'c') { + ptr = _m3d_findarg(ptr); + if(!*pe || *pe == '\r' || *pe == '\n') goto asciiend; + ptr = _m3d_gethex(ptr, &k); + } else + if(*ptr == 'l') { + ptr = _m3d_findarg(ptr); + if(!*pe || *pe == '\r' || *pe == '\n') goto asciiend; + fn = _m3d_safestr(ptr, 2); + } else { + i = model->numlabel++; + model->label = (m3dl_t*)M3D_REALLOC(model->label, model->numlabel * sizeof(m3dl_t)); + if(!model->label) goto memerr; + model->label[i].name = pe; + model->label[i].lang = fn; + model->label[i].color = k; + ptr = _m3d_getint(ptr, &j); + model->label[i].vertexid = (M3D_INDEX)j; + ptr = _m3d_findarg(ptr); + if(!*pe || *pe == '\r' || *pe == '\n') goto asciiend; + model->label[i].text = _m3d_safestr(ptr, 2); + } + ptr = _m3d_findnl(ptr); + } + } else + /* action */ + if(!memcmp(pe, "Action", 6)) { + pe = _m3d_findarg(pe); + if(!*pe || *pe == '\r' || *pe == '\n') goto asciiend; + pe = _m3d_getint(pe, &k); + pe = _m3d_findarg(pe); + if(!*pe || *pe == '\r' || *pe == '\n') goto asciiend; + pe = _m3d_safestr(pe, 0); + if(!pe || !*pe) goto asciiend; + i = model->numaction++; + model->action = (m3da_t*)M3D_REALLOC(model->action, model->numaction * sizeof(m3da_t)); + if(!model->action) goto memerr; + a = &model->action[i]; + a->name = pe; + a->durationmsec = k; + /* skip the first frame marker as there's always at least one frame */ + a->numframe = 1; + a->frame = (m3dfr_t*)M3D_MALLOC(sizeof(m3dfr_t)); + if(!a->frame) goto memerr; + a->frame[0].msec = 0; + a->frame[0].numtransform = 0; + a->frame[0].transform = NULL; + i = 0; + if(*ptr == 'f') + ptr = _m3d_findnl(ptr); + while(*ptr && *ptr != '\r' && *ptr != '\n') { + if(*ptr == 'f') { + i = a->numframe++; + a->frame = (m3dfr_t*)M3D_REALLOC(a->frame, a->numframe * sizeof(m3dfr_t)); + if(!a->frame) goto memerr; + ptr = _m3d_findarg(ptr); + ptr = _m3d_getint(ptr, &a->frame[i].msec); + a->frame[i].numtransform = 0; + a->frame[i].transform = NULL; + } else { + j = a->frame[i].numtransform++; + a->frame[i].transform = (m3dtr_t*)M3D_REALLOC(a->frame[i].transform, + a->frame[i].numtransform * sizeof(m3dtr_t)); + if(!a->frame[i].transform) goto memerr; + ptr = _m3d_getint(ptr, &k); + ptr = _m3d_findarg(ptr); + if(!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; + a->frame[i].transform[j].boneid = (M3D_INDEX)k; + ptr = _m3d_getint(ptr, &k); + ptr = _m3d_findarg(ptr); + if(!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; + a->frame[i].transform[j].pos = (M3D_INDEX)k; + ptr = _m3d_getint(ptr, &k); + if(!*ptr || *ptr == '\r' || *ptr == '\n') goto asciiend; + a->frame[i].transform[j].ori = (M3D_INDEX)k; + model->vertex[k].skinid = (M3D_INDEX)-2U; + } + ptr = _m3d_findnl(ptr); + } + } else + /* inlined assets chunk */ + if(!memcmp(pe, "Assets", 6)) { + while(*ptr && *ptr != '\r' && *ptr != '\n') { + if(readfilecb) { + pe = _m3d_safestr(ptr, 2); + if(!pe || !*pe) goto asciiend; + i = model->numinlined++; + model->inlined = (m3di_t*)M3D_REALLOC(model->inlined, model->numinlined * sizeof(m3di_t)); + if(!model->inlined) goto memerr; + t = &model->inlined[i]; + model->inlined[i].data = (*readfilecb)(pe, &model->inlined[i].length); + if(model->inlined[i].data) { + fn = strrchr(pe, '.'); + if(fn && (fn[1] == 'p' || fn[1] == 'P') && (fn[2] == 'n' || fn[2] == 'N') && + (fn[3] == 'g' || fn[3] == 'G')) *fn = 0; + fn = strrchr(pe, '/'); + if(!fn) fn = strrchr(pe, '\\'); + if(!fn) fn = pe; else fn++; + model->inlined[i].name = _m3d_safestr(fn, 0); + } else + model->numinlined--; + M3D_FREE(pe); + } + ptr = _m3d_findnl(ptr); + } + } else + /* extra chunks */ + if(!memcmp(pe, "Extra", 5)) { + pe = _m3d_findarg(pe); + if(!*pe || *pe == '\r' || *pe == '\n') goto asciiend; + buff = (unsigned char*)_m3d_findnl(ptr); + k = ((uint32_t)((uint64_t)buff - (uint64_t)ptr) / 3) + 1; + i = model->numextra++; + model->extra = (m3dchunk_t**)M3D_REALLOC(model->extra, model->numextra * sizeof(m3dchunk_t*)); + if(!model->extra) goto memerr; + model->extra[i] = (m3dchunk_t*)M3D_MALLOC(k + sizeof(m3dchunk_t)); + if(!model->extra[i]) goto memerr; + memcpy(&model->extra[i]->magic, pe, 4); + model->extra[i]->length = sizeof(m3dchunk_t); + pe = (char*)model->extra[i] + sizeof(m3dchunk_t); + while(*ptr && *ptr != '\r' && *ptr != '\n') { + ptr = _m3d_gethex(ptr, &k); + *pe++ = (uint8_t)k; + model->extra[i]->length++; + } + } else + goto asciiend; + } + model->errcode = M3D_SUCCESS; +asciiend: + setlocale(LC_NUMERIC, ol); + goto postprocess; + } + /* Binary variant */ +#endif + if(!M3D_CHUNKMAGIC(data + 8, 'H','E','A','D')) { + buff = (unsigned char *)stbi_zlib_decode_malloc_guesssize_headerflag((const char*)data+8, ((m3dchunk_t*)data)->length-8, + 4096, (int*)&len, 1); + if(!buff || !len || !M3D_CHUNKMAGIC(buff, 'H','E','A','D')) { + if(buff) M3D_FREE(buff); + M3D_FREE(model); + return NULL; + } + buff = (unsigned char*)M3D_REALLOC(buff, len); + model->flags |= M3D_FLG_FREERAW; /* mark that we have to free the raw buffer */ + data = buff; + } else { + len = ((m3dhdr_t*)data)->length; + data += 8; + } + model->raw = (m3dhdr_t*)data; + end = data + len; + + /* parse header */ + data += sizeof(m3dhdr_t); + M3D_LOG(data); + model->name = (char*)data; + for(; data < end && *data; data++) {}; data++; + model->license = (char*)data; + for(; data < end && *data; data++) {}; data++; + model->author = (char*)data; + for(; data < end && *data; data++) {}; data++; + model->desc = (char*)data; + chunk = (unsigned char*)model->raw + model->raw->length; + model->scale = (M3D_FLOAT)model->raw->scale; + if(model->scale <= (M3D_FLOAT)0.0) model->scale = (M3D_FLOAT)1.0; + model->vc_s = 1 << ((model->raw->types >> 0) & 3); /* vertex coordinate size */ + model->vi_s = 1 << ((model->raw->types >> 2) & 3); /* vertex index size */ + model->si_s = 1 << ((model->raw->types >> 4) & 3); /* string offset size */ + model->ci_s = 1 << ((model->raw->types >> 6) & 3); /* color index size */ + model->ti_s = 1 << ((model->raw->types >> 8) & 3); /* tmap index size */ + model->bi_s = 1 << ((model->raw->types >>10) & 3); /* bone index size */ + model->nb_s = 1 << ((model->raw->types >>12) & 3); /* number of bones per vertex */ + model->sk_s = 1 << ((model->raw->types >>14) & 3); /* skin index size */ + model->fc_s = 1 << ((model->raw->types >>16) & 3); /* frame counter size */ + model->hi_s = 1 << ((model->raw->types >>18) & 3); /* shape index size */ + model->fi_s = 1 << ((model->raw->types >>20) & 3); /* face index size */ + if(model->ci_s == 8) model->ci_s = 0; /* optional indices */ + if(model->ti_s == 8) model->ti_s = 0; + if(model->bi_s == 8) model->bi_s = 0; + if(model->sk_s == 8) model->sk_s = 0; + if(model->fc_s == 8) model->fc_s = 0; + if(model->hi_s == 8) model->hi_s = 0; + if(model->fi_s == 8) model->fi_s = 0; + + /* variable limit checks */ + if(sizeof(M3D_FLOAT) == 4 && model->vc_s > 4) { + M3D_LOG("Double precision coordinates not supported, truncating to float..."); + model->errcode = M3D_ERR_TRUNC; + } + if(sizeof(M3D_INDEX) == 2 && (model->vi_s > 2 || model->si_s > 2 || model->ci_s > 2 || model->ti_s > 2 || + model->bi_s > 2 || model->sk_s > 2 || model->fc_s > 2 || model->hi_s > 2 || model->fi_s > 2)) { + M3D_LOG("32 bit indices not supported, unable to load model"); + M3D_FREE(model); + return NULL; + } + if(model->vi_s > 4 || model->si_s > 4) { + M3D_LOG("Invalid index size, unable to load model"); + M3D_FREE(model); + return NULL; + } + if(model->nb_s > M3D_NUMBONE) { + M3D_LOG("Model has more bones per vertex than what importer configured to support"); + model->errcode = M3D_ERR_TRUNC; + } + + /* look for inlined assets in advance, material and procedural chunks may need them */ + buff = chunk; + while(buff < end && !M3D_CHUNKMAGIC(buff, 'O','M','D','3')) { + data = buff; + len = ((m3dchunk_t*)data)->length; + if(len < sizeof(m3dchunk_t)) { + M3D_LOG("Invalid chunk size"); + break; + } + buff += len; + len -= sizeof(m3dchunk_t) + model->si_s; + + /* inlined assets */ + if(M3D_CHUNKMAGIC(data, 'A','S','E','T') && len > 0) { + M3D_LOG("Inlined asset"); + i = model->numinlined++; + model->inlined = (m3di_t*)M3D_REALLOC(model->inlined, model->numinlined * sizeof(m3di_t)); + if(!model->inlined) { +memerr: M3D_LOG("Out of memory"); + model->errcode = M3D_ERR_ALLOC; + return model; + } + data += sizeof(m3dchunk_t); + t = &model->inlined[i]; + M3D_GETSTR(t->name); + M3D_LOG(t->name); + t->data = (uint8_t*)data; + t->length = len; + } + } + + /* parse chunks */ + while(chunk < end && !M3D_CHUNKMAGIC(chunk, 'O','M','D','3')) { + data = chunk; + len = ((m3dchunk_t*)chunk)->length; + if(len < sizeof(m3dchunk_t)) { + M3D_LOG("Invalid chunk size"); + break; + } + chunk += len; + len -= sizeof(m3dchunk_t); + + /* preview chunk */ + if(M3D_CHUNKMAGIC(data, 'P','R','V','W') && len > 0) { + model->preview.length = len; + model->preview.data = data + sizeof(m3dchunk_t); + } else + /* color map */ + if(M3D_CHUNKMAGIC(data, 'C','M','A','P')) { + M3D_LOG("Color map"); + if(model->cmap) { M3D_LOG("More color map chunks, should be unique"); model->errcode = M3D_ERR_CMAP; continue; } + if(!model->ci_s) { M3D_LOG("Color map chunk, shouldn't be any"); model->errcode = M3D_ERR_CMAP; continue; } + model->numcmap = len / sizeof(uint32_t); + model->cmap = (uint32_t*)(data + sizeof(m3dchunk_t)); + } else + /* texture map */ + if(M3D_CHUNKMAGIC(data, 'T','M','A','P')) { + M3D_LOG("Texture map"); + if(model->tmap) { M3D_LOG("More texture map chunks, should be unique"); model->errcode = M3D_ERR_TMAP; continue; } + if(!model->ti_s) { M3D_LOG("Texture map chunk, shouldn't be any"); model->errcode = M3D_ERR_TMAP; continue; } + reclen = model->vc_s + model->vc_s; + model->numtmap = len / reclen; + model->tmap = (m3dti_t*)M3D_MALLOC(model->numtmap * sizeof(m3dti_t)); + if(!model->tmap) goto memerr; + for(i = 0, data += sizeof(m3dchunk_t); data < chunk; i++) { + switch(model->vc_s) { + case 1: + model->tmap[i].u = (M3D_FLOAT)(data[0]) / 255; + model->tmap[i].v = (M3D_FLOAT)(data[1]) / 255; + break; + case 2: + model->tmap[i].u = (M3D_FLOAT)(*((int16_t*)(data+0))) / 65535; + model->tmap[i].v = (M3D_FLOAT)(*((int16_t*)(data+2))) / 65535; + break; + case 4: + model->tmap[i].u = (M3D_FLOAT)(*((float*)(data+0))); + model->tmap[i].v = (M3D_FLOAT)(*((float*)(data+4))); + break; + case 8: + model->tmap[i].u = (M3D_FLOAT)(*((double*)(data+0))); + model->tmap[i].v = (M3D_FLOAT)(*((double*)(data+8))); + break; + } + data += reclen; + } + } else + /* vertex list */ + if(M3D_CHUNKMAGIC(data, 'V','R','T','S')) { + M3D_LOG("Vertex list"); + if(model->vertex) { M3D_LOG("More vertex chunks, should be unique"); model->errcode = M3D_ERR_VRTS; continue; } + if(model->ci_s && model->ci_s < 4 && !model->cmap) model->errcode = M3D_ERR_CMAP; + reclen = model->ci_s + model->sk_s + 4 * model->vc_s; + model->numvertex = len / reclen; + model->vertex = (m3dv_t*)M3D_MALLOC(model->numvertex * sizeof(m3dv_t)); + if(!model->vertex) goto memerr; + memset(model->vertex, 0, model->numvertex * sizeof(m3dv_t)); + for(i = 0, data += sizeof(m3dchunk_t); data < chunk && i < model->numvertex; i++) { + switch(model->vc_s) { + case 1: + model->vertex[i].x = (M3D_FLOAT)((int8_t)data[0]) / 127; + model->vertex[i].y = (M3D_FLOAT)((int8_t)data[1]) / 127; + model->vertex[i].z = (M3D_FLOAT)((int8_t)data[2]) / 127; + model->vertex[i].w = (M3D_FLOAT)((int8_t)data[3]) / 127; + data += 4; + break; + case 2: + model->vertex[i].x = (M3D_FLOAT)(*((int16_t*)(data+0))) / 32767; + model->vertex[i].y = (M3D_FLOAT)(*((int16_t*)(data+2))) / 32767; + model->vertex[i].z = (M3D_FLOAT)(*((int16_t*)(data+4))) / 32767; + model->vertex[i].w = (M3D_FLOAT)(*((int16_t*)(data+6))) / 32767; + data += 8; + break; + case 4: + model->vertex[i].x = (M3D_FLOAT)(*((float*)(data+0))); + model->vertex[i].y = (M3D_FLOAT)(*((float*)(data+4))); + model->vertex[i].z = (M3D_FLOAT)(*((float*)(data+8))); + model->vertex[i].w = (M3D_FLOAT)(*((float*)(data+12))); + data += 16; + break; + case 8: + model->vertex[i].x = (M3D_FLOAT)(*((double*)(data+0))); + model->vertex[i].y = (M3D_FLOAT)(*((double*)(data+8))); + model->vertex[i].z = (M3D_FLOAT)(*((double*)(data+16))); + model->vertex[i].w = (M3D_FLOAT)(*((double*)(data+24))); + data += 32; + break; + } + switch(model->ci_s) { + case 1: model->vertex[i].color = model->cmap ? model->cmap[data[0]] : 0; data++; break; + case 2: model->vertex[i].color = model->cmap ? model->cmap[*((uint16_t*)data)] : 0; data += 2; break; + case 4: model->vertex[i].color = *((uint32_t*)data); data += 4; break; + /* case 8: break; */ + } + model->vertex[i].skinid = (M3D_INDEX)-1U; + data = _m3d_getidx(data, model->sk_s, &model->vertex[i].skinid); + } + } else + /* skeleton: bone hierarchy and skin */ + if(M3D_CHUNKMAGIC(data, 'B','O','N','E')) { + M3D_LOG("Skeleton"); + if(model->bone) { M3D_LOG("More bone chunks, should be unique"); model->errcode = M3D_ERR_BONE; continue; } + if(!model->bi_s) { M3D_LOG("Bone chunk, shouldn't be any"); model->errcode=M3D_ERR_BONE; continue; } + if(!model->vertex) { M3D_LOG("No vertex chunk before bones"); model->errcode = M3D_ERR_VRTS; break; } + data += sizeof(m3dchunk_t); + model->numbone = 0; + data = _m3d_getidx(data, model->bi_s, &model->numbone); + if(model->numbone) { + model->bone = (m3db_t*)M3D_MALLOC(model->numbone * sizeof(m3db_t)); + if(!model->bone) goto memerr; + } + model->numskin = 0; + data = _m3d_getidx(data, model->sk_s, &model->numskin); + /* read bone hierarchy */ + for(i = 0; i < model->numbone; i++) { + data = _m3d_getidx(data, model->bi_s, &model->bone[i].parent); + M3D_GETSTR(model->bone[i].name); + data = _m3d_getidx(data, model->vi_s, &model->bone[i].pos); + data = _m3d_getidx(data, model->vi_s, &model->bone[i].ori); + model->bone[i].numweight = 0; + model->bone[i].weight = NULL; + } + /* read skin definitions */ + if(model->numskin) { + model->skin = (m3ds_t*)M3D_MALLOC(model->numskin * sizeof(m3ds_t)); + if(!model->skin) goto memerr; + for(i = 0; data < chunk && i < model->numskin; i++) { + for(j = 0; j < M3D_NUMBONE; j++) { + model->skin[i].boneid[j] = (M3D_INDEX)-1U; + model->skin[i].weight[j] = (M3D_FLOAT)0.0; + } + memset(&weights, 0, sizeof(weights)); + if(model->nb_s == 1) weights[0] = 255; + else { + memcpy(&weights, data, model->nb_s); + data += model->nb_s; + } + for(j = 0, w = (M3D_FLOAT)0.0; j < (unsigned int)model->nb_s; j++) { + if(weights[j]) { + if(j >= M3D_NUMBONE) + data += model->bi_s; + else { + model->skin[i].weight[j] = (M3D_FLOAT)(weights[j]) / 255; + w += model->skin[i].weight[j]; + data = _m3d_getidx(data, model->bi_s, &model->skin[i].boneid[j]); + } + } + } + /* this can occur if model has more bones than what the importer is configured to handle */ + if(w != (M3D_FLOAT)1.0 && w != (M3D_FLOAT)0.0) { + for(j = 0; j < M3D_NUMBONE; j++) + model->skin[i].weight[j] /= w; + } + } + } + } else + /* material */ + if(M3D_CHUNKMAGIC(data, 'M','T','R','L')) { + data += sizeof(m3dchunk_t); + M3D_GETSTR(name); + M3D_LOG("Material"); + M3D_LOG(name); + if(model->ci_s < 4 && !model->numcmap) model->errcode = M3D_ERR_CMAP; + for(i = 0; i < model->nummaterial; i++) + if(!strcmp(name, model->material[i].name)) { + model->errcode = M3D_ERR_MTRL; + M3D_LOG("Multiple definitions for material"); + M3D_LOG(name); + name = NULL; + break; + } + if(name) { + i = model->nummaterial++; + if(model->flags & M3D_FLG_MTLLIB) { + m = model->material; + model->material = (m3dm_t*)M3D_MALLOC(model->nummaterial * sizeof(m3dm_t)); + if(!model->material) goto memerr; + memcpy(model->material, m, (model->nummaterial - 1) * sizeof(m3dm_t)); + if(model->texture) { + tx = model->texture; + model->texture = (m3dtx_t*)M3D_MALLOC(model->numtexture * sizeof(m3dtx_t)); + if(!model->texture) goto memerr; + memcpy(model->texture, tx, model->numtexture * sizeof(m3dm_t)); + } + model->flags &= ~M3D_FLG_MTLLIB; + } else { + model->material = (m3dm_t*)M3D_REALLOC(model->material, model->nummaterial * sizeof(m3dm_t)); + if(!model->material) goto memerr; + } + m = &model->material[i]; + m->numprop = 0; + m->name = name; + m->prop = (m3dp_t*)M3D_MALLOC((len / 2) * sizeof(m3dp_t)); + if(!m->prop) goto memerr; + while(data < chunk) { + i = m->numprop++; + m->prop[i].type = *data++; + m->prop[i].value.num = 0; + if(m->prop[i].type >= 128) + k = m3dpf_map; + else { + for(k = 256, j = 0; j < sizeof(m3d_propertytypes)/sizeof(m3d_propertytypes[0]); j++) + if(m->prop[i].type == m3d_propertytypes[j].id) { k = m3d_propertytypes[j].format; break; } + } + switch(k) { + case m3dpf_color: + switch(model->ci_s) { + case 1: m->prop[i].value.color = model->cmap ? model->cmap[data[0]] : 0; data++; break; + case 2: m->prop[i].value.color = model->cmap ? model->cmap[*((uint16_t*)data)] : 0; data += 2; break; + case 4: m->prop[i].value.color = *((uint32_t*)data); data += 4; break; + } + break; + + case m3dpf_uint8: m->prop[i].value.num = *data++; break; + case m3dpf_uint16:m->prop[i].value.num = *((uint16_t*)data); data += 2; break; + case m3dpf_uint32:m->prop[i].value.num = *((uint32_t*)data); data += 4; break; + case m3dpf_float: m->prop[i].value.fnum = *((float*)data); data += 4; break; + + case m3dpf_map: + M3D_GETSTR(name); + m->prop[i].value.textureid = _m3d_gettx(model, readfilecb, freecb, name); + if(model->errcode == M3D_ERR_ALLOC) goto memerr; + if(m->prop[i].value.textureid == (M3D_INDEX)-1U) { + M3D_LOG("Texture not found"); + M3D_LOG(m->name); + m->numprop--; + } + break; + + default: + M3D_LOG("Unknown material property in"); + M3D_LOG(m->name); + model->errcode = M3D_ERR_UNKPROP; + data = chunk; + break; + } + } + m->prop = (m3dp_t*)M3D_REALLOC(m->prop, m->numprop * sizeof(m3dp_t)); + if(!m->prop) goto memerr; + } + } else + /* face */ + if(M3D_CHUNKMAGIC(data, 'P','R','O','C')) { + /* procedural surface */ + M3D_GETSTR(name); + M3D_LOG("Procedural surface"); + M3D_LOG(name); + _m3d_getpr(model, readfilecb, freecb, name); + } else + if(M3D_CHUNKMAGIC(data, 'M','E','S','H')) { + M3D_LOG("Mesh data"); + /* mesh */ + data += sizeof(m3dchunk_t); + mi = (M3D_INDEX)-1U; + am = model->numface; + while(data < chunk) { + k = *data++; + n = k >> 4; + k &= 15; + if(!n) { + /* use material */ + mi = (M3D_INDEX)-1U; + M3D_GETSTR(name); + if(name) { + for(j = 0; j < model->nummaterial; j++) + if(!strcmp(name, model->material[j].name)) { + mi = (M3D_INDEX)j; + break; + } + if(mi == (M3D_INDEX)-1U) model->errcode = M3D_ERR_MTRL; + } + continue; + } + if(n != 3) { M3D_LOG("Only triangle mesh supported for now"); model->errcode = M3D_ERR_UNKMESH; return model; } + i = model->numface++; + if(model->numface > am) { + am = model->numface + 4095; + model->face = (m3df_t*)M3D_REALLOC(model->face, am * sizeof(m3df_t)); + if(!model->face) goto memerr; + } + memset(&model->face[i], 255, sizeof(m3df_t)); /* set all index to -1 by default */ + model->face[i].materialid = mi; + for(j = 0; j < n; j++) { + /* vertex */ + data = _m3d_getidx(data, model->vi_s, &model->face[i].vertex[j]); + /* texcoord */ + if(k & 1) + data = _m3d_getidx(data, model->ti_s, &model->face[i].texcoord[j]); + /* normal */ + if(k & 2) + data = _m3d_getidx(data, model->vi_s, &model->face[i].normal[j]); + } + } + model->face = (m3df_t*)M3D_REALLOC(model->face, model->numface * sizeof(m3df_t)); + } else + if(M3D_CHUNKMAGIC(data, 'S','H','P','E')) { + /* mathematical shape */ + data += sizeof(m3dchunk_t); + M3D_GETSTR(name); + M3D_LOG("Mathematical Shape"); + M3D_LOG(name); + i = model->numshape++; + model->shape = (m3dh_t*)M3D_REALLOC(model->shape, model->numshape * sizeof(m3dh_t)); + if(!model->shape) goto memerr; + h = &model->shape[i]; + h->numcmd = 0; + h->cmd = NULL; + h->name = name; + h->group = (M3D_INDEX)-1U; + data = _m3d_getidx(data, model->bi_s, &h->group); + if(h->group != (M3D_INDEX)-1U && h->group >= model->numbone) { + M3D_LOG("Unknown bone id as shape group in shape"); + M3D_LOG(name); + h->group = (M3D_INDEX)-1U; + model->errcode = M3D_ERR_SHPE; + } + while(data < chunk) { + i = h->numcmd++; + h->cmd = (m3dc_t*)M3D_REALLOC(h->cmd, h->numcmd * sizeof(m3dc_t)); + if(!h->cmd) goto memerr; + h->cmd[i].type = *data++; + if(h->cmd[i].type & 0x80) { + h->cmd[i].type &= 0x7F; + h->cmd[i].type |= (*data++ << 7); + } + if(h->cmd[i].type >= (unsigned int)(sizeof(m3d_commandtypes)/sizeof(m3d_commandtypes[0]))) { + M3D_LOG("Unknown shape command in"); + M3D_LOG(h->name); + model->errcode = M3D_ERR_UNKCMD; + break; + } + cd = &m3d_commandtypes[h->cmd[i].type]; + h->cmd[i].arg = (uint32_t*)M3D_MALLOC(cd->p * sizeof(uint32_t)); + if(!h->cmd[i].arg) goto memerr; + memset(h->cmd[i].arg, 0, cd->p * sizeof(uint32_t)); + for(k = n = 0, l = cd->p; k < l; k++) + switch(cd->a[((k - n) % (cd->p - n)) + n]) { + case m3dcp_mi_t: + h->cmd[i].arg[k] = -1U; + M3D_GETSTR(name); + if(name) { + for(n = 0; n < model->nummaterial; n++) + if(!strcmp(name, model->material[n].name)) { + h->cmd[i].arg[k] = n; + break; + } + if(h->cmd[i].arg[k] == -1U) model->errcode = M3D_ERR_MTRL; + } + break; + case m3dcp_vc_t: + f = 0.0f; + switch(model->vc_s) { + case 1: f = (float)((int8_t)data[0]) / 127; break; + case 2: f = (float)(*((int16_t*)(data+0))) / 32767; break; + case 4: f = (float)(*((float*)(data+0))); break; + case 8: f = (float)(*((double*)(data+0))); break; + } + h->cmd[i].arg[k] = *((uint32_t*)&f); + data += model->vc_s; + break; + case m3dcp_hi_t: data = _m3d_getidx(data, model->hi_s, &h->cmd[i].arg[k]); break; + case m3dcp_fi_t: data = _m3d_getidx(data, model->fi_s, &h->cmd[i].arg[k]); break; + case m3dcp_ti_t: data = _m3d_getidx(data, model->ti_s, &h->cmd[i].arg[k]); break; + case m3dcp_qi_t: + case m3dcp_vi_t: data = _m3d_getidx(data, model->vi_s, &h->cmd[i].arg[k]); break; + case m3dcp_i1_t: data = _m3d_getidx(data, 1, &h->cmd[i].arg[k]); break; + case m3dcp_i2_t: data = _m3d_getidx(data, 2, &h->cmd[i].arg[k]); break; + case m3dcp_i4_t: data = _m3d_getidx(data, 4, &h->cmd[i].arg[k]); break; + case m3dcp_va_t: data = _m3d_getidx(data, 4, &h->cmd[i].arg[k]); + n = k + 1; l += (h->cmd[i].arg[k] - 1) * (cd->p - k - 1); + h->cmd[i].arg = (uint32_t*)M3D_REALLOC(h->cmd[i].arg, l * sizeof(uint32_t)); + if(!h->cmd[i].arg) goto memerr; + memset(&h->cmd[i].arg[k + 1], 0, (l - k - 1) * sizeof(uint32_t)); + break; + } + } + } else + /* annotation label list */ + if(M3D_CHUNKMAGIC(data, 'L','B','L','S')) { + data += sizeof(m3dchunk_t); + M3D_GETSTR(name); + M3D_GETSTR(lang); + M3D_LOG("Label list"); + if(name) { M3D_LOG(name); } + if(lang) { M3D_LOG(lang); } + if(model->ci_s && model->ci_s < 4 && !model->cmap) model->errcode = M3D_ERR_CMAP; + k = 0; + switch(model->ci_s) { + case 1: k = model->cmap ? model->cmap[data[0]] : 0; data++; break; + case 2: k = model->cmap ? model->cmap[*((uint16_t*)data)] : 0; data += 2; break; + case 4: k = *((uint32_t*)data); data += 4; break; + /* case 8: break; */ + } + reclen = model->vi_s + model->si_s; + i = model->numlabel; model->numlabel += len / reclen; + model->label = (m3dl_t*)M3D_REALLOC(model->label, model->numlabel * sizeof(m3dl_t)); + if(!model->label) goto memerr; + memset(&model->label[i], 0, (model->numlabel - i) * sizeof(m3dl_t)); + for(; data < chunk && i < model->numlabel; i++) { + model->label[i].name = name; + model->label[i].lang = lang; + model->label[i].color = k; + data = _m3d_getidx(data, model->vi_s, &model->label[i].vertexid); + M3D_GETSTR(model->label[i].text); + } + } else + /* action */ + if(M3D_CHUNKMAGIC(data, 'A','C','T','N')) { + M3D_LOG("Action"); + i = model->numaction++; + model->action = (m3da_t*)M3D_REALLOC(model->action, model->numaction * sizeof(m3da_t)); + if(!model->action) goto memerr; + a = &model->action[i]; + data += sizeof(m3dchunk_t); + M3D_GETSTR(a->name); + M3D_LOG(a->name); + a->numframe = *((uint16_t*)data); data += 2; + if(a->numframe < 1) { + model->numaction--; + } else { + a->durationmsec = *((uint32_t*)data); data += 4; + a->frame = (m3dfr_t*)M3D_MALLOC(a->numframe * sizeof(m3dfr_t)); + if(!a->frame) goto memerr; + for(i = 0; data < chunk && i < a->numframe; i++) { + a->frame[i].msec = *((uint32_t*)data); data += 4; + a->frame[i].numtransform = 0; a->frame[i].transform = NULL; + data = _m3d_getidx(data, model->fc_s, &a->frame[i].numtransform); + if(a->frame[i].numtransform > 0) { + a->frame[i].transform = (m3dtr_t*)M3D_MALLOC(a->frame[i].numtransform * sizeof(m3dtr_t)); + for(j = 0; j < a->frame[i].numtransform; j++) { + data = _m3d_getidx(data, model->bi_s, &a->frame[i].transform[j].boneid); + data = _m3d_getidx(data, model->vi_s, &a->frame[i].transform[j].pos); + data = _m3d_getidx(data, model->vi_s, &a->frame[i].transform[j].ori); + } + } + } + } + } else { + i = model->numextra++; + model->extra = (m3dchunk_t**)M3D_REALLOC(model->extra, model->numextra * sizeof(m3dchunk_t*)); + if(!model->extra) goto memerr; + model->extra[i] = (m3dchunk_t*)data; + } + } + /* calculate normals, normalize skin weights, create bone/vertex cross-references and calculate transform matrices */ +#ifdef M3D_ASCII +postprocess: +#endif + if(model) { + M3D_LOG("Post-process"); +#ifndef M3D_NONORMALS + if(model->numface && model->face) { + /* if they are missing, calculate triangle normals into a temporary buffer */ + for(i = 0, n = model->numvertex; i < model->numface; i++) + if(model->face[i].normal[0] == -1U) { + v0 = &model->vertex[model->face[i].vertex[0]]; + v1 = &model->vertex[model->face[i].vertex[1]]; + v2 = &model->vertex[model->face[i].vertex[2]]; + va.x = v1->x - v0->x; va.y = v1->y - v0->y; va.z = v1->z - v0->z; + vb.x = v2->x - v0->x; vb.y = v2->y - v0->y; vb.z = v2->z - v0->z; + if(!norm) { + norm = (m3dv_t*)M3D_MALLOC(model->numface * sizeof(m3dv_t)); + if(!norm) goto memerr; + } + v0 = &norm[i]; + v0->x = (va.y * vb.z) - (va.z * vb.y); + v0->y = (va.z * vb.x) - (va.x * vb.z); + v0->z = (va.x * vb.y) - (va.y * vb.x); + w = _m3d_rsq((v0->x * v0->x) + (v0->y * v0->y) + (v0->z * v0->z)); + v0->x *= w; v0->y *= w; v0->z *= w; + model->face[i].normal[0] = model->face[i].vertex[0] + n; + model->face[i].normal[1] = model->face[i].vertex[1] + n; + model->face[i].normal[2] = model->face[i].vertex[2] + n; + } + /* this is the fast way, we don't care if a normal is repeated in model->vertex */ + if(norm) { + M3D_LOG("Generating normals"); + model->flags |= M3D_FLG_GENNORM; + model->numvertex <<= 1; + model->vertex = (m3dv_t*)M3D_REALLOC(model->vertex, model->numvertex * sizeof(m3dv_t)); + if(!model->vertex) goto memerr; + memset(&model->vertex[n], 0, n * sizeof(m3dv_t)); + for(i = 0; i < model->numface; i++) + for(j = 0; j < 3; j++) { + v0 = &model->vertex[model->face[i].vertex[j] + n]; + v0->x += norm[i].x; + v0->y += norm[i].y; + v0->z += norm[i].z; + } + /* for each vertex, take the average of the temporary normals and use that */ + for(i = 0, v0 = &model->vertex[n]; i < n; i++, v0++) { + w = _m3d_rsq((v0->x * v0->x) + (v0->y * v0->y) + (v0->z * v0->z)); + v0->x *= w; v0->y *= w; v0->z *= w; + v0->skinid = -1U; + } + M3D_FREE(norm); + } + } +#endif + if(model->numbone && model->bone && model->numskin && model->skin && model->numvertex && model->vertex) { +#ifndef M3D_NOWEIGHTS + M3D_LOG("Generating weight cross-reference"); + for(i = 0; i < model->numvertex; i++) { + if(model->vertex[i].skinid < model->numskin) { + sk = &model->skin[model->vertex[i].skinid]; + w = (M3D_FLOAT)0.0; + for(j = 0; j < M3D_NUMBONE && sk->boneid[j] != (M3D_INDEX)-1U && sk->weight[j] > (M3D_FLOAT)0.0; j++) + w += sk->weight[j]; + for(j = 0; j < M3D_NUMBONE && sk->boneid[j] != (M3D_INDEX)-1U && sk->weight[j] > (M3D_FLOAT)0.0; j++) { + sk->weight[j] /= w; + b = &model->bone[sk->boneid[j]]; + k = b->numweight++; + b->weight = (m3dw_t*)M3D_REALLOC(b->weight, b->numweight * sizeof(m3da_t)); + if(!b->weight) goto memerr; + b->weight[k].vertexid = i; + b->weight[k].weight = sk->weight[j]; + } + } + } +#endif +#ifndef M3D_NOANIMATION + M3D_LOG("Calculating bone transformation matrices"); + for(i = 0; i < model->numbone; i++) { + b = &model->bone[i]; + if(model->bone[i].parent == (M3D_INDEX)-1U) { + _m3d_mat((M3D_FLOAT*)&b->mat4, &model->vertex[b->pos], &model->vertex[b->ori]); + } else { + _m3d_mat((M3D_FLOAT*)&r, &model->vertex[b->pos], &model->vertex[b->ori]); + _m3d_mul((M3D_FLOAT*)&b->mat4, (M3D_FLOAT*)&model->bone[b->parent].mat4, (M3D_FLOAT*)&r); + } + } + for(i = 0; i < model->numbone; i++) + _m3d_inv((M3D_FLOAT*)&model->bone[i].mat4); +#endif + } + } + return model; +} + +/** + * Calculates skeletons for animation frames, returns a working copy (should be freed after use) + */ +m3dtr_t *m3d_frame(m3d_t *model, M3D_INDEX actionid, M3D_INDEX frameid, m3dtr_t *skeleton) +{ + unsigned int i; + M3D_INDEX s = frameid; + m3dfr_t *fr; + + if(!model || !model->numbone || !model->bone || (actionid != (M3D_INDEX)-1U && (!model->action || + actionid >= model->numaction || frameid >= model->action[actionid].numframe))) { + model->errcode = M3D_ERR_UNKFRAME; + return skeleton; + } + model->errcode = M3D_SUCCESS; + if(!skeleton) { + skeleton = (m3dtr_t*)M3D_MALLOC(model->numbone * sizeof(m3dtr_t)); + if(!skeleton) { + model->errcode = M3D_ERR_ALLOC; + return NULL; + } + goto gen; + } + if(actionid == (M3D_INDEX)-1U || !frameid) { +gen: s = 0; + for(i = 0; i < model->numbone; i++) { + skeleton[i].boneid = i; + skeleton[i].pos = model->bone[i].pos; + skeleton[i].ori = model->bone[i].ori; + } + } + if(actionid < model->numaction && (frameid || !model->action[actionid].frame[0].msec)) { + for(; s <= frameid; s++) { + fr = &model->action[actionid].frame[s]; + for(i = 0; i < fr->numtransform; i++) { + skeleton[fr->transform[i].boneid].pos = fr->transform[i].pos; + skeleton[fr->transform[i].boneid].ori = fr->transform[i].ori; + } + } + } + return skeleton; +} + +#ifndef M3D_NOANIMATION +/** + * Returns interpolated animation-pose, a working copy (should be freed after use) + */ +m3db_t *m3d_pose(m3d_t *model, M3D_INDEX actionid, uint32_t msec) +{ + unsigned int i, j, l; + M3D_FLOAT r[16], t, c, d, s; + m3db_t *ret; + m3dv_t *v, *p, *f; + m3dtr_t *tmp; + m3dfr_t *fr; + + if(!model || !model->numbone || !model->bone) { + model->errcode = M3D_ERR_UNKFRAME; + return NULL; + } + ret = (m3db_t*)M3D_MALLOC(model->numbone * sizeof(m3db_t)); + if(!ret) { + model->errcode = M3D_ERR_ALLOC; + return NULL; + } + memcpy(ret, model->bone, model->numbone * sizeof(m3db_t)); + for(i = 0; i < model->numbone; i++) + _m3d_inv((M3D_FLOAT*)&ret[i].mat4); + if(!model->action || actionid >= model->numaction) { + model->errcode = M3D_ERR_UNKFRAME; + return ret; + } + msec %= model->action[actionid].durationmsec; + model->errcode = M3D_SUCCESS; + fr = &model->action[actionid].frame[0]; + for(j = l = 0; j < model->action[actionid].numframe && model->action[actionid].frame[j].msec <= msec; j++) { + fr = &model->action[actionid].frame[j]; + l = fr->msec; + for(i = 0; i < fr->numtransform; i++) { + ret[fr->transform[i].boneid].pos = fr->transform[i].pos; + ret[fr->transform[i].boneid].ori = fr->transform[i].ori; + } + } + if(l != msec) { + model->vertex = (m3dv_t*)M3D_REALLOC(model->vertex, (model->numvertex + 2 * model->numbone) * sizeof(m3dv_t)); + if(!model->vertex) { + free(ret); + model->errcode = M3D_ERR_ALLOC; + return NULL; + } + tmp = (m3dtr_t*)M3D_MALLOC(model->numbone * sizeof(m3dtr_t)); + if(tmp) { + for(i = 0; i < model->numbone; i++) { + tmp[i].pos = ret[i].pos; + tmp[i].ori = ret[i].ori; + } + fr = &model->action[actionid].frame[j % model->action[actionid].numframe]; + t = l >= fr->msec ? (M3D_FLOAT)1.0 : (M3D_FLOAT)(msec - l) / (M3D_FLOAT)(fr->msec - l); + for(i = 0; i < fr->numtransform; i++) { + tmp[fr->transform[i].boneid].pos = fr->transform[i].pos; + tmp[fr->transform[i].boneid].ori = fr->transform[i].ori; + } + for(i = 0, j = model->numvertex; i < model->numbone; i++) { + /* interpolation of position */ + if(ret[i].pos != tmp[i].pos) { + p = &model->vertex[ret[i].pos]; + f = &model->vertex[tmp[i].pos]; + v = &model->vertex[j]; + v->x = p->x + t * (f->x - p->x); + v->y = p->y + t * (f->y - p->y); + v->z = p->z + t * (f->z - p->z); + ret[i].pos = j++; + } + /* interpolation of orientation */ + if(ret[i].ori != tmp[i].ori) { + p = &model->vertex[ret[i].ori]; + f = &model->vertex[tmp[i].ori]; + v = &model->vertex[j]; + d = p->w * f->w + p->x * f->x + p->y * f->y + p->z * f->z; + if(d < 0) { d = -d; s = (M3D_FLOAT)-1.0; } else s = (M3D_FLOAT)1.0; +#if 0 + /* don't use SLERP, requires two more variables, libm linkage and it is slow (but nice) */ + a = (M3D_FLOAT)1.0 - t; b = t; + if(d < (M3D_FLOAT)0.999999) { c = acosf(d); b = 1 / sinf(c); a = sinf(a * c) * b; b *= sinf(t * c) * s; } + v->x = p->x * a + f->x * b; + v->y = p->y * a + f->y * b; + v->z = p->z * a + f->z * b; + v->w = p->w * a + f->w * b; +#else + /* approximated NLERP, original approximation by Arseny Kapoulkine, heavily optimized by me */ + c = t - (M3D_FLOAT)0.5; t += t * c * (t - (M3D_FLOAT)1.0) * (((M3D_FLOAT)1.0904 + d * ((M3D_FLOAT)-3.2452 + + d * ((M3D_FLOAT)3.55645 - d * (M3D_FLOAT)1.43519))) * c * c + ((M3D_FLOAT)0.848013 + d * + ((M3D_FLOAT)-1.06021 + d * (M3D_FLOAT)0.215638))); + v->x = p->x + t * (s * f->x - p->x); + v->y = p->y + t * (s * f->y - p->y); + v->z = p->z + t * (s * f->z - p->z); + v->w = p->w + t * (s * f->w - p->w); + d = _m3d_rsq(v->w * v->w + v->x * v->x + v->y * v->y + v->z * v->z); + v->x *= d; v->y *= d; v->z *= d; v->w *= d; +#endif + ret[i].ori = j++; + } + } + M3D_FREE(tmp); + } + } + for(i = 0; i < model->numbone; i++) { + if(ret[i].parent == (M3D_INDEX)-1U) { + _m3d_mat((M3D_FLOAT*)&ret[i].mat4, &model->vertex[ret[i].pos], &model->vertex[ret[i].ori]); + } else { + _m3d_mat((M3D_FLOAT*)&r, &model->vertex[ret[i].pos], &model->vertex[ret[i].ori]); + _m3d_mul((M3D_FLOAT*)&ret[i].mat4, (M3D_FLOAT*)&ret[ret[i].parent].mat4, (M3D_FLOAT*)&r); + } + } + return ret; +} + +#endif /* M3D_NOANIMATION */ + +#endif /* M3D_IMPLEMENTATION */ + +#if !defined(M3D_NODUP) && (!defined(M3D_NOIMPORTER) || defined(M3D_EXPORTER)) +/** + * Free the in-memory model + */ +void m3d_free(m3d_t *model) +{ + unsigned int i, j; + + if(!model) return; +#ifdef M3D_ASCII + /* if model imported from ASCII, we have to free all strings as well */ + if(model->flags & M3D_FLG_FREESTR) { + if(model->name) M3D_FREE(model->name); + if(model->license) M3D_FREE(model->license); + if(model->author) M3D_FREE(model->author); + if(model->desc) M3D_FREE(model->desc); + if(model->bone) + for(i = 0; i < model->numbone; i++) + if(model->bone[i].name) + M3D_FREE(model->bone[i].name); + if(model->shape) + for(i = 0; i < model->numshape; i++) + if(model->shape[i].name) + M3D_FREE(model->shape[i].name); + if(model->material) + for(i = 0; i < model->nummaterial; i++) + if(model->material[i].name) + M3D_FREE(model->material[i].name); + if(model->action) + for(i = 0; i < model->numaction; i++) + if(model->action[i].name) + M3D_FREE(model->action[i].name); + if(model->texture) + for(i = 0; i < model->numtexture; i++) + if(model->texture[i].name) + M3D_FREE(model->texture[i].name); + if(model->inlined) + for(i = 0; i < model->numinlined; i++) { + if(model->inlined[i].name) + M3D_FREE(model->inlined[i].name); + if(model->inlined[i].data) + M3D_FREE(model->inlined[i].data); + } + if(model->extra) + for(i = 0; i < model->numextra; i++) + if(model->extra[i]) + M3D_FREE(model->extra[i]); + if(model->label) + for(i = 0; i < model->numlabel; i++) { + if(model->label[i].name) { + for(j = i + 1; j < model->numlabel; j++) + if(model->label[j].name == model->label[i].name) + model->label[j].name = NULL; + M3D_FREE(model->label[i].name); + } + if(model->label[i].lang) { + for(j = i + 1; j < model->numlabel; j++) + if(model->label[j].lang == model->label[i].lang) + model->label[j].lang = NULL; + M3D_FREE(model->label[i].lang); + } + if(model->label[i].text) + M3D_FREE(model->label[i].text); + } + if(model->preview.data) + M3D_FREE(model->preview.data); + } +#endif + if(model->flags & M3D_FLG_FREERAW) M3D_FREE(model->raw); + + if(model->tmap) M3D_FREE(model->tmap); + if(model->bone) { + for(i = 0; i < model->numbone; i++) + if(model->bone[i].weight) + M3D_FREE(model->bone[i].weight); + M3D_FREE(model->bone); + } + if(model->skin) M3D_FREE(model->skin); + if(model->vertex) M3D_FREE(model->vertex); + if(model->face) M3D_FREE(model->face); + if(model->shape) { + for(i = 0; i < model->numshape; i++) { + if(model->shape[i].cmd) { + for(j = 0; j < model->shape[i].numcmd; j++) + if(model->shape[i].cmd[j].arg) M3D_FREE(model->shape[i].cmd[j].arg); + M3D_FREE(model->shape[i].cmd); + } + } + M3D_FREE(model->shape); + } + if(model->material && !(model->flags & M3D_FLG_MTLLIB)) { + for(i = 0; i < model->nummaterial; i++) + if(model->material[i].prop) M3D_FREE(model->material[i].prop); + M3D_FREE(model->material); + } + if(model->texture) { + for(i = 0; i < model->numtexture; i++) + if(model->texture[i].d) M3D_FREE(model->texture[i].d); + M3D_FREE(model->texture); + } + if(model->action) { + for(i = 0; i < model->numaction; i++) { + if(model->action[i].frame) { + for(j = 0; j < model->action[i].numframe; j++) + if(model->action[i].frame[j].transform) M3D_FREE(model->action[i].frame[j].transform); + M3D_FREE(model->action[i].frame); + } + } + M3D_FREE(model->action); + } + if(model->label) M3D_FREE(model->label); + if(model->inlined) M3D_FREE(model->inlined); + if(model->extra) M3D_FREE(model->extra); + free(model); +} +#endif + +#ifdef M3D_EXPORTER +typedef struct { + char *str; + uint32_t offs; +} m3dstr_t; + +typedef struct { + m3dti_t data; + M3D_INDEX oldidx; + M3D_INDEX newidx; +} m3dtisave_t; + +typedef struct { + m3dv_t data; + M3D_INDEX oldidx; + M3D_INDEX newidx; + unsigned char norm; +} m3dvsave_t; + +typedef struct { + m3ds_t data; + M3D_INDEX oldidx; + M3D_INDEX newidx; +} m3dssave_t; + +typedef struct { + m3df_t data; + int group; + uint8_t opacity; +} m3dfsave_t; + +/* create unique list of strings */ +static m3dstr_t *_m3d_addstr(m3dstr_t *str, uint32_t *numstr, char *s) +{ + uint32_t i; + if(!s || !*s) return str; + if(str) { + for(i = 0; i < *numstr; i++) + if(str[i].str == s || !strcmp(str[i].str, s)) return str; + } + str = (m3dstr_t*)M3D_REALLOC(str, ((*numstr) + 1) * sizeof(m3dstr_t)); + str[*numstr].str = s; + str[*numstr].offs = 0; + (*numstr)++; + return str; +} + +/* add strings to header */ +m3dhdr_t *_m3d_addhdr(m3dhdr_t *h, m3dstr_t *s) +{ + int i; + char *safe = _m3d_safestr(s->str, 0); + i = strlen(safe); + h = (m3dhdr_t*)M3D_REALLOC(h, h->length + i+1); + if(!h) { M3D_FREE(safe); return NULL; } + memcpy((uint8_t*)h + h->length, safe, i+1); + s->offs = h->length - 16; + h->length += i+1; + M3D_FREE(safe); + return h; +} + +/* return offset of string */ +static uint32_t _m3d_stridx(m3dstr_t *str, uint32_t numstr, char *s) +{ + uint32_t i; + char *safe; + if(!s || !*s) return 0; + if(str) { + safe = _m3d_safestr(s, 0); + if(!safe) return 0; + if(!*safe) { + free(safe); + return 0; + } + for(i = 0; i < numstr; i++) + if(!strcmp(str[i].str, s)) { + free(safe); + return str[i].offs; + } + free(safe); + } + return 0; +} + +/* compare to faces by their material */ +static int _m3d_facecmp(const void *a, const void *b) { + const m3dfsave_t *A = (const m3dfsave_t*)a, *B = (const m3dfsave_t*)b; + return A->group != B->group ? A->group - B->group : (A->opacity != B->opacity ? (int)B->opacity - (int)A->opacity : + (int)A->data.materialid - (int)B->data.materialid); +} +/* compare face groups */ +static int _m3d_grpcmp(const void *a, const void *b) { return *((uint32_t*)a) - *((uint32_t*)b); } +/* compare UVs */ +static int _m3d_ticmp(const void *a, const void *b) { return memcmp(a, b, sizeof(m3dti_t)); } +/* compare skin groups */ +static int _m3d_skincmp(const void *a, const void *b) { return memcmp(a, b, sizeof(m3ds_t)); } +/* compare vertices */ +static int _m3d_vrtxcmp(const void *a, const void *b) { + int c = memcmp(a, b, 3 * sizeof(M3D_FLOAT)); + if(c) return c; + c = ((m3dvsave_t*)a)->norm - ((m3dvsave_t*)b)->norm; + if(c) return c; + return memcmp(a, b, sizeof(m3dv_t)); +} +/* compare labels */ +static _inline int _m3d_strcmp(char *a, char *b) +{ + if(a == NULL && b != NULL) return -1; + if(a != NULL && b == NULL) return 1; + if(a == NULL && b == NULL) return 0; + return strcmp(a, b); +} +static int _m3d_lblcmp(const void *a, const void *b) { + const m3dl_t *A = (const m3dl_t*)a, *B = (const m3dl_t*)b; + int c = _m3d_strcmp(A->lang, B->lang); + if(!c) c = _m3d_strcmp(A->name, B->name); + if(!c) c = _m3d_strcmp(A->text, B->text); + return c; +} +/* compare two colors by HSV value */ +_inline static int _m3d_cmapcmp(const void *a, const void *b) +{ + uint8_t *A = (uint8_t*)a, *B = (uint8_t*)b; + _register int m, vA, vB; + /* get HSV value for A */ + m = A[2] < A[1]? A[2] : A[1]; if(A[0] < m) m = A[0]; + vA = A[2] > A[1]? A[2] : A[1]; if(A[0] > vA) vA = A[0]; + /* get HSV value for B */ + m = B[2] < B[1]? B[2] : B[1]; if(B[0] < m) m = B[0]; + vB = B[2] > B[1]? B[2] : B[1]; if(B[0] > vB) vB = B[0]; + return vA - vB; +} + +/* create sorted list of colors */ +static uint32_t *_m3d_addcmap(uint32_t *cmap, uint32_t *numcmap, uint32_t color) +{ + uint32_t i; + if(cmap) { + for(i = 0; i < *numcmap; i++) + if(cmap[i] == color) return cmap; + } + cmap = (uint32_t*)M3D_REALLOC(cmap, ((*numcmap) + 1) * sizeof(uint32_t)); + for(i = 0; i < *numcmap && _m3d_cmapcmp(&color, &cmap[i]) > 0; i++); + if(i < *numcmap) memmove(&cmap[i+1], &cmap[i], ((*numcmap) - i)*sizeof(uint32_t)); + cmap[i] = color; + (*numcmap)++; + return cmap; +} + +/* look up a color and return its index */ +static uint32_t _m3d_cmapidx(uint32_t *cmap, uint32_t numcmap, uint32_t color) +{ + uint32_t i; + if(numcmap >= 65536) + return color; + for(i = 0; i < numcmap; i++) + if(cmap[i] == color) return i; + return 0; +} + +/* add index to output */ +static unsigned char *_m3d_addidx(unsigned char *out, char type, uint32_t idx) { + switch(type) { + case 1: *out++ = (uint8_t)(idx); break; + case 2: *((uint16_t*)out) = (uint16_t)(idx); out += 2; break; + case 4: *((uint32_t*)out) = (uint32_t)(idx); out += 4; break; + /* case 0: case 8: break; */ + } + return out; +} + +/* round a vertex position */ +static void _m3d_round(int quality, m3dv_t *src, m3dv_t *dst) +{ + _register int t; + /* copy additional attributes */ + if(src != dst) memcpy(dst, src, sizeof(m3dv_t)); + /* round according to quality */ + switch(quality) { + case M3D_EXP_INT8: + t = src->x * 127 + (src->x >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5); dst->x = (M3D_FLOAT)t / 127; + t = src->y * 127 + (src->y >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5); dst->y = (M3D_FLOAT)t / 127; + t = src->z * 127 + (src->z >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5); dst->z = (M3D_FLOAT)t / 127; + t = src->w * 127 + (src->w >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5); dst->w = (M3D_FLOAT)t / 127; + break; + case M3D_EXP_INT16: + t = src->x * 32767 + (src->x >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5); dst->x = (M3D_FLOAT)t / 32767; + t = src->y * 32767 + (src->y >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5); dst->y = (M3D_FLOAT)t / 32767; + t = src->z * 32767 + (src->z >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5); dst->z = (M3D_FLOAT)t / 32767; + t = src->w * 32767 + (src->w >= 0 ? (M3D_FLOAT)0.5 : (M3D_FLOAT)-0.5); dst->w = (M3D_FLOAT)t / 32767; + break; + } + if(dst->x == (M3D_FLOAT)-0.0) dst->x = (M3D_FLOAT)0.0; + if(dst->y == (M3D_FLOAT)-0.0) dst->y = (M3D_FLOAT)0.0; + if(dst->z == (M3D_FLOAT)-0.0) dst->z = (M3D_FLOAT)0.0; + if(dst->w == (M3D_FLOAT)-0.0) dst->w = (M3D_FLOAT)0.0; +} + +#ifdef M3D_ASCII +/* add a bone to ascii output */ +static char *_m3d_prtbone(char *ptr, m3db_t *bone, M3D_INDEX numbone, M3D_INDEX parent, uint32_t level, M3D_INDEX *vrtxidx) +{ + uint32_t i, j; + char *sn; + + if(level > M3D_BONEMAXLEVEL || !bone) return ptr; + for(i = 0; i < numbone; i++) { + if(bone[i].parent == parent) { + for(j = 0; j < level; j++) *ptr++ = '/'; + sn = _m3d_safestr(bone[i].name, 0); + ptr += sprintf(ptr, "%d %d %s\r\n", vrtxidx[bone[i].pos], vrtxidx[bone[i].ori], sn); + M3D_FREE(sn); + ptr = _m3d_prtbone(ptr, bone, numbone, i, level + 1, vrtxidx); + } + } + return ptr; +} +#endif + +/** + * Function to encode an in-memory model into on storage Model 3D format + */ +unsigned char *m3d_save(m3d_t *model, int quality, int flags, unsigned int *size) +{ +#ifdef M3D_ASCII + const char *ol; + char *ptr; +#endif + char vc_s, vi_s, si_s, ci_s, ti_s, bi_s, nb_s, sk_s, fc_s, hi_s, fi_s; + char *sn = NULL, *sl = NULL, *sa = NULL, *sd = NULL; + unsigned char *out = NULL, *z = NULL, weights[M3D_NUMBONE], *norm = NULL; + unsigned int i, j, k, l, n, len, chunklen, *length; + M3D_FLOAT scale = (M3D_FLOAT)0.0, min_x, max_x, min_y, max_y, min_z, max_z; + M3D_INDEX last, *vrtxidx = NULL, *mtrlidx = NULL, *tmapidx = NULL, *skinidx = NULL; + uint32_t idx, numcmap = 0, *cmap = NULL, numvrtx = 0, maxvrtx = 0, numtmap = 0, maxtmap = 0, numproc = 0; + uint32_t numskin = 0, maxskin = 0, numstr = 0, maxt = 0, maxbone = 0, numgrp = 0, maxgrp = 0, *grpidx = NULL; + uint8_t *opa; + m3dcd_t *cd; + m3dc_t *cmd; + m3dstr_t *str = NULL; + m3dvsave_t *vrtx = NULL, vertex; + m3dtisave_t *tmap = NULL, tcoord; + m3dssave_t *skin = NULL, sk; + m3dfsave_t *face = NULL; + m3dhdr_t *h = NULL; + m3dm_t *m; + m3da_t *a; + + if(!model) { + if(size) *size = 0; + return NULL; + } + model->errcode = M3D_SUCCESS; +#ifdef M3D_ASCII + if(flags & M3D_EXP_ASCII) quality = M3D_EXP_DOUBLE; +#endif + vrtxidx = (M3D_INDEX*)M3D_MALLOC(model->numvertex * sizeof(M3D_INDEX)); + if(!vrtxidx) goto memerr; + memset(vrtxidx, 255, model->numvertex * sizeof(M3D_INDEX)); + if(model->numvertex && !(flags & M3D_EXP_NONORMAL)){ + norm = (unsigned char*)M3D_MALLOC(model->numvertex * sizeof(unsigned char)); + if(!norm) goto memerr; + memset(norm, 0, model->numvertex * sizeof(unsigned char)); + } + if(model->nummaterial && !(flags & M3D_EXP_NOMATERIAL)) { + mtrlidx = (M3D_INDEX*)M3D_MALLOC(model->nummaterial * sizeof(M3D_INDEX)); + if(!mtrlidx) goto memerr; + memset(mtrlidx, 255, model->nummaterial * sizeof(M3D_INDEX)); + opa = (uint8_t*)M3D_MALLOC(model->nummaterial * 2 * sizeof(M3D_INDEX)); + if(!opa) goto memerr; + memset(opa, 255, model->nummaterial * 2 * sizeof(M3D_INDEX)); + } + if(model->numtmap && !(flags & M3D_EXP_NOTXTCRD)) { + tmapidx = (M3D_INDEX*)M3D_MALLOC(model->numtmap * sizeof(M3D_INDEX)); + if(!tmapidx) goto memerr; + memset(tmapidx, 255, model->numtmap * sizeof(M3D_INDEX)); + } + /** collect array elements that are actually referenced **/ + if(!(flags & M3D_EXP_NOFACE)) { + /* face */ + if(model->numface && model->face) { + M3D_LOG("Processing mesh face"); + face = (m3dfsave_t*)M3D_MALLOC(model->numface * sizeof(m3dfsave_t)); + if(!face) goto memerr; + for(i = 0; i < model->numface; i++) { + memcpy(&face[i].data, &model->face[i], sizeof(m3df_t)); + face[i].group = 0; + face[i].opacity = 255; + if(!(flags & M3D_EXP_NOMATERIAL) && model->face[i].materialid < model->nummaterial) { + if(model->material[model->face[i].materialid].numprop) { + mtrlidx[model->face[i].materialid] = 0; + if(opa[model->face[i].materialid * 2]) { + m = &model->material[model->face[i].materialid]; + for(j = 0; j < m->numprop; j++) + if(m->prop[j].type == m3dp_Kd) { + opa[model->face[i].materialid * 2 + 1] = ((uint8_t*)&m->prop[j].value.color)[3]; + break; + } + for(j = 0; j < m->numprop; j++) + if(m->prop[j].type == m3dp_d) { + opa[model->face[i].materialid * 2 + 1] = (uint8_t)(m->prop[j].value.fnum * 255); + break; + } + opa[model->face[i].materialid * 2] = 0; + } + face[i].opacity = opa[model->face[i].materialid * 2 + 1]; + } else + face[i].data.materialid = (M3D_INDEX)-1U; + } + for(j = 0; j < 3; j++) { + k = model->face[i].vertex[j]; + if(k < model->numvertex) + vrtxidx[k] = 0; + if(!(flags & M3D_EXP_NOCMAP)) { + cmap = _m3d_addcmap(cmap, &numcmap, model->vertex[k].color); + if(!cmap) goto memerr; + } + k = model->face[i].normal[j]; + if(k < model->numvertex && !(flags & M3D_EXP_NONORMAL)) { + vrtxidx[k] = 0; + norm[k] = 1; + } + k = model->face[i].texcoord[j]; + if(k < model->numtmap && !(flags & M3D_EXP_NOTXTCRD)) + tmapidx[k] = 0; + } + /* convert from CW to CCW */ + if(flags & M3D_EXP_IDOSUCK) { + j = face[i].data.vertex[1]; + face[i].data.vertex[1] = face[i].data.vertex[2]; + face[i].data.vertex[2] = face[i].data.vertex[1]; + j = face[i].data.normal[1]; + face[i].data.normal[1] = face[i].data.normal[2]; + face[i].data.normal[2] = face[i].data.normal[1]; + j = face[i].data.texcoord[1]; + face[i].data.texcoord[1] = face[i].data.texcoord[2]; + face[i].data.texcoord[2] = face[i].data.texcoord[1]; + } + } + } + if(model->numshape && model->shape) { + M3D_LOG("Processing shape face"); + for(i = 0; i < model->numshape; i++) { + if(!model->shape[i].numcmd) continue; + str = _m3d_addstr(str, &numstr, model->shape[i].name); + if(!str) goto memerr; + for(j = 0; j < model->shape[i].numcmd; j++) { + cmd = &model->shape[i].cmd[j]; + if(cmd->type >= (unsigned int)(sizeof(m3d_commandtypes)/sizeof(m3d_commandtypes[0])) || !cmd->arg) + continue; + if(cmd->type == m3dc_mesh) { + if(numgrp + 2 < maxgrp) { + maxgrp += 1024; + grpidx = (uint32_t*)realloc(grpidx, maxgrp * sizeof(uint32_t)); + if(!grpidx) goto memerr; + if(!numgrp) { + grpidx[0] = 0; + grpidx[1] = model->numface; + numgrp += 2; + } + } + grpidx[numgrp + 0] = cmd->arg[0]; + grpidx[numgrp + 1] = cmd->arg[0] + cmd->arg[1]; + numgrp += 2; + } + cd = &m3d_commandtypes[cmd->type]; + for(k = n = 0, l = cd->p; k < l; k++) + switch(cd->a[((k - n) % (cd->p - n)) + n]) { + case m3dcp_mi_t: + if(!(flags & M3D_EXP_NOMATERIAL) && cmd->arg[k] < model->nummaterial) + mtrlidx[cmd->arg[k]] = 0; + break; + case m3dcp_ti_t: + if(!(flags & M3D_EXP_NOTXTCRD) && cmd->arg[k] < model->numtmap) + tmapidx[cmd->arg[k]] = 0; + break; + case m3dcp_qi_t: + case m3dcp_vi_t: + if(cmd->arg[k] < model->numvertex) + vrtxidx[cmd->arg[k]] = 0; + break; + case m3dcp_va_t: + n = k + 1; l += (cmd->arg[k] - 1) * (cd->p - k - 1); + break; + } + } + } + } + if(model->numface && face) { + if(numgrp && grpidx) { + qsort(grpidx, numgrp, sizeof(uint32_t), _m3d_grpcmp); + for(i = j = 0; i < model->numface && j < numgrp; i++) { + while(j < numgrp && grpidx[j] < i) j++; + face[i].group = j; + } + } + qsort(face, model->numface, sizeof(m3dfsave_t), _m3d_facecmp); + } + if(grpidx) { M3D_FREE(grpidx); grpidx = NULL; } + if(model->numlabel && model->label) { + M3D_LOG("Processing annotation labels"); + for(i = 0; i < model->numlabel; i++) { + str = _m3d_addstr(str, &numstr, model->label[i].name); + str = _m3d_addstr(str, &numstr, model->label[i].lang); + str = _m3d_addstr(str, &numstr, model->label[i].text); + if(!(flags & M3D_EXP_NOCMAP)) { + cmap = _m3d_addcmap(cmap, &numcmap, model->label[i].color); + if(!cmap) goto memerr; + } + if(model->label[i].vertexid < model->numvertex) + vrtxidx[model->label[i].vertexid] = 0; + } + qsort(model->label, model->numlabel, sizeof(m3dl_t), _m3d_lblcmp); + } + } else if(!(flags & M3D_EXP_NOMATERIAL)) { + /* without a face, simply add all materials, because it can be an mtllib */ + for(i = 0; i < model->nummaterial; i++) + mtrlidx[i] = i; + } + /* bind-pose skeleton */ + if(model->numbone && model->bone && !(flags & M3D_EXP_NOBONE)) { + M3D_LOG("Processing bones"); + for(i = 0; i < model->numbone; i++) { + str = _m3d_addstr(str, &numstr, model->bone[i].name); + if(!str) goto memerr; + k = model->bone[i].pos; + if(k < model->numvertex) + vrtxidx[k] = 0; + k = model->bone[i].ori; + if(k < model->numvertex) + vrtxidx[k] = 0; + } + } + /* actions, animated skeleton poses */ + if(model->numaction && model->action && !(flags & M3D_EXP_NOACTION)) { + M3D_LOG("Processing action list"); + for(j = 0; j < model->numaction; j++) { + a = &model->action[j]; + str = _m3d_addstr(str, &numstr, a->name); + if(!str) goto memerr; + if(a->numframe > 65535) a->numframe = 65535; + for(i = 0; i < a->numframe; i++) { + for(l = 0; l < a->frame[i].numtransform; l++) { + k = a->frame[i].transform[l].pos; + if(k < model->numvertex) + vrtxidx[k] = 0; + k = a->frame[i].transform[l].ori; + if(k < model->numvertex) + vrtxidx[k] = 0; + } + if(l > maxt) maxt = l; + } + } + } + /* add colors to color map and texture names to string table */ + if(!(flags & M3D_EXP_NOMATERIAL)) { + M3D_LOG("Processing materials"); + for(i = k = 0; i < model->nummaterial; i++) { + if(mtrlidx[i] == (M3D_INDEX)-1U || !model->material[i].numprop) continue; + mtrlidx[i] = k++; + m = &model->material[i]; + str = _m3d_addstr(str, &numstr, m->name); + if(!str) goto memerr; + if(m->prop) + for(j = 0; j < m->numprop; j++) { + if(!(flags & M3D_EXP_NOCMAP) && m->prop[j].type < 128) { + for(l = 0; l < sizeof(m3d_propertytypes)/sizeof(m3d_propertytypes[0]); l++) { + if(m->prop[j].type == m3d_propertytypes[l].id && m3d_propertytypes[l].format == m3dpf_color) { + ((uint8_t*)&m->prop[j].value.color)[3] = opa[i * 2 + 1]; + cmap = _m3d_addcmap(cmap, &numcmap, m->prop[j].value.color); + if(!cmap) goto memerr; + break; + } + } + } + if(m->prop[j].type >= 128 && m->prop[j].value.textureid < model->numtexture && + model->texture[m->prop[j].value.textureid].name) { + str = _m3d_addstr(str, &numstr, model->texture[m->prop[j].value.textureid].name); + if(!str) goto memerr; + } + } + } + } + /* if there's only one black color, don't store it */ + if(numcmap == 1 && cmap && !cmap[0]) numcmap = 0; + + /** compress lists **/ + if(model->numtmap && !(flags & M3D_EXP_NOTXTCRD)) { + M3D_LOG("Compressing tmap"); + tmap = (m3dtisave_t*)M3D_MALLOC(model->numtmap * sizeof(m3dtisave_t)); + if(!tmap) goto memerr; + for(i = 0; i < model->numtmap; i++) { + if(tmapidx[i] == (M3D_INDEX)-1U) continue; + switch(quality) { + case M3D_EXP_INT8: + l = model->tmap[i].u * 255; tcoord.data.u = (M3D_FLOAT)l / 255; + l = model->tmap[i].v * 255; tcoord.data.v = (M3D_FLOAT)l / 255; + break; + case M3D_EXP_INT16: + l = model->tmap[i].u * 65535; tcoord.data.u = (M3D_FLOAT)l / 65535; + l = model->tmap[i].v * 65535; tcoord.data.v = (M3D_FLOAT)l / 65535; + break; + default: + tcoord.data.u = model->tmap[i].u; + tcoord.data.v = model->tmap[i].v; + break; + } + if(flags & M3D_EXP_FLIPTXTCRD) + tcoord.data.v = (M3D_FLOAT)1.0 - tcoord.data.v; + tcoord.oldidx = i; + memcpy(&tmap[numtmap++], &tcoord, sizeof(m3dtisave_t)); + } + if(numtmap) { + qsort(tmap, numtmap, sizeof(m3dtisave_t), _m3d_ticmp); + memcpy(&tcoord.data, &tmap[0], sizeof(m3dti_t)); + for(i = 0; i < numtmap; i++) { + if(memcmp(&tcoord.data, &tmap[i].data, sizeof(m3dti_t))) { + memcpy(&tcoord.data, &tmap[i].data, sizeof(m3dti_t)); + maxtmap++; + } + tmap[i].newidx = maxtmap; + tmapidx[tmap[i].oldidx] = maxtmap; + } + maxtmap++; + } + } + if(model->numskin && model->skin && !(flags & M3D_EXP_NOBONE)) { + M3D_LOG("Compressing skin"); + skinidx = (M3D_INDEX*)M3D_MALLOC(model->numskin * sizeof(M3D_INDEX)); + if(!skinidx) goto memerr; + skin = (m3dssave_t*)M3D_MALLOC(model->numskin * sizeof(m3dssave_t)); + if(!skin) goto memerr; + memset(skinidx, 255, model->numskin * sizeof(M3D_INDEX)); + for(i = 0; i < model->numvertex; i++) { + if(vrtxidx[i] != (M3D_INDEX)-1U && model->vertex[i].skinid < model->numskin) + skinidx[model->vertex[i].skinid] = 0; + } + for(i = 0; i < model->numskin; i++) { + if(skinidx[i] == (M3D_INDEX)-1U) continue; + memset(&sk, 0, sizeof(m3dssave_t)); + for(j = 0, min_x = (M3D_FLOAT)0.0; j < M3D_NUMBONE && model->skin[i].boneid[j] != (M3D_INDEX)-1U && + model->skin[i].weight[j] > (M3D_FLOAT)0.0; j++) { + sk.data.boneid[j] = model->skin[i].boneid[j]; + sk.data.weight[j] = model->skin[i].weight[j]; + min_x += sk.data.weight[j]; + } + if(j > maxbone) maxbone = j; + if(min_x != (M3D_FLOAT)1.0 && min_x != (M3D_FLOAT)0.0) + for(j = 0; j < M3D_NUMBONE && sk.data.weight[j] > (M3D_FLOAT)0.0; j++) + sk.data.weight[j] /= min_x; + sk.oldidx = i; + memcpy(&skin[numskin++], &sk, sizeof(m3dssave_t)); + } + if(numskin) { + qsort(skin, numskin, sizeof(m3dssave_t), _m3d_skincmp); + memcpy(&sk.data, &skin[0].data, sizeof(m3ds_t)); + for(i = 0; i < numskin; i++) { + if(memcmp(&sk.data, &skin[i].data, sizeof(m3ds_t))) { + memcpy(&sk.data, &skin[i].data, sizeof(m3ds_t)); + maxskin++; + } + skin[i].newidx = maxskin; + skinidx[skin[i].oldidx] = maxskin; + } + maxskin++; + } + } + + M3D_LOG("Compressing vertex list"); + min_x = min_y = min_z = (M3D_FLOAT)1e10; + max_x = max_y = max_z = (M3D_FLOAT)-1e10; + if(vrtxidx) { + vrtx = (m3dvsave_t*)M3D_MALLOC(model->numvertex * sizeof(m3dvsave_t)); + if(!vrtx) goto memerr; + for(i = numvrtx = 0; i < model->numvertex; i++) { + if(vrtxidx[i] == (M3D_INDEX)-1U) continue; + _m3d_round(quality, &model->vertex[i], &vertex.data); + vertex.norm = norm ? norm[i] : 0; + if(vertex.data.skinid != (M3D_INDEX)-2U && !vertex.norm) { + vertex.data.skinid = vertex.data.skinid != (M3D_INDEX)-1U && skinidx ? skinidx[vertex.data.skinid] : (M3D_INDEX)-1U; + if(vertex.data.x > max_x) max_x = vertex.data.x; + if(vertex.data.x < min_x) min_x = vertex.data.x; + if(vertex.data.y > max_y) max_y = vertex.data.y; + if(vertex.data.y < min_y) min_y = vertex.data.y; + if(vertex.data.z > max_z) max_z = vertex.data.z; + if(vertex.data.z < min_z) min_z = vertex.data.z; + } +#ifdef M3D_VERTEXTYPE + vertex.data.type = 0; +#endif + vertex.oldidx = i; + memcpy(&vrtx[numvrtx++], &vertex, sizeof(m3dvsave_t)); + } + if(numvrtx) { + qsort(vrtx, numvrtx, sizeof(m3dvsave_t), _m3d_vrtxcmp); + memcpy(&vertex.data, &vrtx[0].data, sizeof(m3dv_t)); + for(i = 0; i < numvrtx; i++) { + if(memcmp(&vertex.data, &vrtx[i].data, vrtx[i].norm ? 3 * sizeof(M3D_FLOAT) : sizeof(m3dv_t))) { + memcpy(&vertex.data, &vrtx[i].data, sizeof(m3dv_t)); + maxvrtx++; + } + vrtx[i].newidx = maxvrtx; + vrtxidx[vrtx[i].oldidx] = maxvrtx; + } + maxvrtx++; + } + } + if(skinidx) { M3D_FREE(skinidx); skinidx = NULL; } + if(norm) { M3D_FREE(norm); norm = NULL; } + + /* normalize to bounding cube */ + if(numvrtx && !(flags & M3D_EXP_NORECALC)) { + M3D_LOG("Normalizing coordinates"); + if(min_x < (M3D_FLOAT)0.0) min_x = -min_x; + if(max_x < (M3D_FLOAT)0.0) max_x = -max_x; + if(min_y < (M3D_FLOAT)0.0) min_y = -min_y; + if(max_y < (M3D_FLOAT)0.0) max_y = -max_y; + if(min_z < (M3D_FLOAT)0.0) min_z = -min_z; + if(max_z < (M3D_FLOAT)0.0) max_z = -max_z; + scale = min_x; + if(max_x > scale) scale = max_x; + if(min_y > scale) scale = min_y; + if(max_y > scale) scale = max_y; + if(min_z > scale) scale = min_z; + if(max_z > scale) scale = max_z; + if(scale == (M3D_FLOAT)0.0) scale = (M3D_FLOAT)1.0; + if(scale != (M3D_FLOAT)1.0) { + for(i = 0; i < numvrtx; i++) { + if(vrtx[i].data.skinid == (M3D_INDEX)-2U) continue; + vrtx[i].data.x /= scale; + vrtx[i].data.y /= scale; + vrtx[i].data.z /= scale; + } + } + } + if(model->scale > (M3D_FLOAT)0.0) scale = model->scale; + if(scale <= (M3D_FLOAT)0.0) scale = (M3D_FLOAT)1.0; + + /* meta info */ + sn = _m3d_safestr(model->name && *model->name ? model->name : (char*)"(noname)", 2); + sl = _m3d_safestr(model->license ? model->license : (char*)"MIT", 2); + sa = _m3d_safestr(model->author ? model->author : getenv("LOGNAME"), 2); + if(!sn || !sl || !sa) { +memerr: if(vrtxidx) M3D_FREE(vrtxidx); + if(mtrlidx) M3D_FREE(mtrlidx); + if(tmapidx) M3D_FREE(tmapidx); + if(skinidx) M3D_FREE(skinidx); + if(grpidx) M3D_FREE(grpidx); + if(norm) M3D_FREE(norm); + if(face) M3D_FREE(face); + if(cmap) M3D_FREE(cmap); + if(tmap) M3D_FREE(tmap); + if(skin) M3D_FREE(skin); + if(str) M3D_FREE(str); + if(vrtx) M3D_FREE(vrtx); + if(sn) M3D_FREE(sn); + if(sl) M3D_FREE(sl); + if(sa) M3D_FREE(sa); + if(sd) M3D_FREE(sd); + if(out) M3D_FREE(out); + if(h) M3D_FREE(h); + M3D_LOG("Out of memory"); + model->errcode = M3D_ERR_ALLOC; + return NULL; + } + + M3D_LOG("Serializing model"); +#ifdef M3D_ASCII + if(flags & M3D_EXP_ASCII) { + /* use CRLF to make model creators on Win happy... */ + sd = _m3d_safestr(model->desc, 1); + if(!sd) goto memerr; + ol = setlocale(LC_NUMERIC, NULL); + setlocale(LC_NUMERIC, "C"); + /* header */ + len = 64 + strlen(sn) + strlen(sl) + strlen(sa) + strlen(sd); + out = (unsigned char*)M3D_MALLOC(len); + if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; } + ptr = (char*)out; + ptr += sprintf(ptr, "3dmodel %g\r\n%s\r\n%s\r\n%s\r\n%s\r\n\r\n", scale, + sn, sl, sa, sd); + M3D_FREE(sl); M3D_FREE(sa); M3D_FREE(sd); + sl = sa = sd = NULL; + /* preview chunk */ + if(model->preview.data && model->preview.length) { + sl = _m3d_safestr(sn, 0); + if(sl) { + ptr -= (uint64_t)out; len = (uint64_t)ptr + 20; + out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uint64_t)out; + if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; } + ptr += sprintf(ptr, "Preview\r\n%s.png\r\n\r\n", sl); + M3D_FREE(sl); sl = NULL; + } + } + M3D_FREE(sn); sn = NULL; + /* texture map */ + if(numtmap && tmap && !(flags & M3D_EXP_NOTXTCRD) && !(flags & M3D_EXP_NOFACE)) { + ptr -= (uint64_t)out; len = (uint64_t)ptr + maxtmap * 32 + 12; + out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uint64_t)out; + if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; } + ptr += sprintf(ptr, "Textmap\r\n"); + last = (M3D_INDEX)-1U; + for(i = 0; i < numtmap; i++) { + if(tmap[i].newidx == last) continue; + last = tmap[i].newidx; + ptr += sprintf(ptr, "%g %g\r\n", tmap[i].data.u, tmap[i].data.v); + } + ptr += sprintf(ptr, "\r\n"); + } + /* vertex chunk */ + if(numvrtx && vrtx && !(flags & M3D_EXP_NOFACE)) { + ptr -= (uint64_t)out; len = (uint64_t)ptr + maxvrtx * 128 + 10; + out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uint64_t)out; + if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; } + ptr += sprintf(ptr, "Vertex\r\n"); + last = (M3D_INDEX)-1U; + for(i = 0; i < numvrtx; i++) { + if(vrtx[i].newidx == last) continue; + last = vrtx[i].newidx; + ptr += sprintf(ptr, "%g %g %g %g", vrtx[i].data.x, vrtx[i].data.y, vrtx[i].data.z, vrtx[i].data.w); + if(!(flags & M3D_EXP_NOCMAP) && vrtx[i].data.color) + ptr += sprintf(ptr, " #%08x", vrtx[i].data.color); + if(!(flags & M3D_EXP_NOBONE) && model->numbone && maxskin && vrtx[i].data.skinid < M3D_INDEXMAX) { + if(skin[vrtx[i].data.skinid].data.weight[0] == (M3D_FLOAT)1.0) + ptr += sprintf(ptr, " %d", skin[vrtx[i].data.skinid].data.boneid[0]); + else + for(j = 0; j < M3D_NUMBONE && skin[vrtx[i].data.skinid].data.boneid[j] != (M3D_INDEX)-1U && + skin[vrtx[i].data.skinid].data.weight[j] > (M3D_FLOAT)0.0; j++) + ptr += sprintf(ptr, " %d:%g", skin[vrtx[i].data.skinid].data.boneid[j], + skin[vrtx[i].data.skinid].data.weight[j]); + } + ptr += sprintf(ptr, "\r\n"); + } + ptr += sprintf(ptr, "\r\n"); + } + /* bones chunk */ + if(model->numbone && model->bone && !(flags & M3D_EXP_NOBONE)) { + ptr -= (uint64_t)out; len = (uint64_t)ptr + 9; + for(i = 0; i < model->numbone; i++) { + len += strlen(model->bone[i].name) + 128; + } + out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uint64_t)out; + if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; } + ptr += sprintf(ptr, "Bones\r\n"); + ptr = _m3d_prtbone(ptr, model->bone, model->numbone, (M3D_INDEX)-1U, 0, vrtxidx); + ptr += sprintf(ptr, "\r\n"); + } + /* materials */ + if(model->nummaterial && !(flags & M3D_EXP_NOMATERIAL)) { + for(j = 0; j < model->nummaterial; j++) { + if(mtrlidx[j] == (M3D_INDEX)-1U || !model->material[j].numprop || !model->material[j].prop) continue; + m = &model->material[j]; + sn = _m3d_safestr(m->name, 0); + if(!sn) { setlocale(LC_NUMERIC, ol); goto memerr; } + ptr -= (uint64_t)out; len = (uint64_t)ptr + strlen(sn) + 12; + for(i = 0; i < m->numprop; i++) { + if(m->prop[i].type < 128) + len += 32; + else if(m->prop[i].value.textureid < model->numtexture && model->texture[m->prop[i].value.textureid].name) + len += strlen(model->texture[m->prop[i].value.textureid].name) + 16; + } + out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uint64_t)out; + if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; } + ptr += sprintf(ptr, "Material %s\r\n", sn); + M3D_FREE(sn); sn = NULL; + for(i = 0; i < m->numprop; i++) { + k = 256; + if(m->prop[i].type >= 128) { + for(l = 0; l < sizeof(m3d_propertytypes)/sizeof(m3d_propertytypes[0]); l++) + if(m->prop[i].type == m3d_propertytypes[l].id) { + sn = m3d_propertytypes[l].key; + break; + } + if(!sn) + for(l = 0; l < sizeof(m3d_propertytypes)/sizeof(m3d_propertytypes[0]); l++) + if(m->prop[i].type - 128 == m3d_propertytypes[l].id) { + sn = m3d_propertytypes[l].key; + break; + } + k = sn ? m3dpf_map : 256; + } else { + for(l = 0; l < sizeof(m3d_propertytypes)/sizeof(m3d_propertytypes[0]); l++) + if(m->prop[i].type == m3d_propertytypes[l].id) { + sn = m3d_propertytypes[l].key; + k = m3d_propertytypes[l].format; + break; + } + } + switch(k) { + case m3dpf_color: ptr += sprintf(ptr, "%s #%08x\r\n", sn, m->prop[i].value.color); break; + case m3dpf_uint8: + case m3dpf_uint16: + case m3dpf_uint32: ptr += sprintf(ptr, "%s %d\r\n", sn, m->prop[i].value.num); break; + case m3dpf_float: ptr += sprintf(ptr, "%s %g\r\n", sn, m->prop[i].value.fnum); break; + case m3dpf_map: + if(m->prop[i].value.textureid < model->numtexture && + model->texture[m->prop[i].value.textureid].name) { + sl = _m3d_safestr(model->texture[m->prop[i].value.textureid].name, 0); + if(!sl) { setlocale(LC_NUMERIC, ol); goto memerr; } + if(*sl) + ptr += sprintf(ptr, "map_%s %s\r\n", sn, sl); + M3D_FREE(sn); M3D_FREE(sl); sl = NULL; + } + break; + } + sn = NULL; + } + ptr += sprintf(ptr, "\r\n"); + } + } + /* procedural face */ + if(model->numinlined && model->inlined && !(flags & M3D_EXP_NOFACE)) { + /* all inlined assets which are not textures should be procedural surfaces */ + for(j = 0; j < model->numinlined; j++) { + if(!model->inlined[j].name || !*model->inlined[j].name || !model->inlined[j].length || !model->inlined[j].data || + (model->inlined[j].data[1] == 'P' && model->inlined[j].data[2] == 'N' && model->inlined[j].data[3] == 'G')) + continue; + for(i = k = 0; i < model->numtexture; i++) { + if(!strcmp(model->inlined[j].name, model->texture[i].name)) { k = 1; break; } + } + if(k) continue; + sn = _m3d_safestr(model->inlined[j].name, 0); + if(!sn) { setlocale(LC_NUMERIC, ol); goto memerr; } + ptr -= (uint64_t)out; len = (uint64_t)ptr + strlen(sn) + 18; + out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uint64_t)out; + if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; } + ptr += sprintf(ptr, "Procedural\r\n%s\r\n\r\n", sn); + M3D_FREE(sn); sn = NULL; + } + } + /* mesh face */ + if(model->numface && face && !(flags & M3D_EXP_NOFACE)) { + ptr -= (uint64_t)out; len = (uint64_t)ptr + model->numface * 128 + 6; + last = (M3D_INDEX)-1U; + if(!(flags & M3D_EXP_NOMATERIAL)) + for(i = 0; i < model->numface; i++) { + j = face[i].data.materialid < model->nummaterial ? face[i].data.materialid : (M3D_INDEX)-1U; + if(j != last) { + last = j; + if(last < model->nummaterial) + len += strlen(model->material[last].name); + len += 6; + } + } + out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uint64_t)out; + if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; } + ptr += sprintf(ptr, "Mesh\r\n"); + last = (M3D_INDEX)-1U; + for(i = 0; i < model->numface; i++) { + j = face[i].data.materialid < model->nummaterial ? face[i].data.materialid : (M3D_INDEX)-1U; + if(!(flags & M3D_EXP_NOMATERIAL) && j != last) { + last = j; + if(last < model->nummaterial) { + sn = _m3d_safestr(model->material[last].name, 0); + if(!sn) { setlocale(LC_NUMERIC, ol); goto memerr; } + ptr += sprintf(ptr, "use %s\r\n", sn); + M3D_FREE(sn); sn = NULL; + } else + ptr += sprintf(ptr, "use\r\n"); + } + /* hardcoded triangles. Should be repeated as many times as the number of edges in polygon */ + for(j = 0; j < 3; j++) { + ptr += sprintf(ptr, "%s%d", j?" ":"", vrtxidx[face[i].data.vertex[j]]); + k = -1U; + if(!(flags & M3D_EXP_NOTXTCRD) && (face[i].data.texcoord[j] != (M3D_INDEX)-1U) && + (tmapidx[face[i].data.texcoord[j]] != (M3D_INDEX)-1U)) { + k = tmapidx[face[i].data.texcoord[j]]; + ptr += sprintf(ptr, "/%d", k); + } + if(!(flags & M3D_EXP_NONORMAL) && (face[i].data.normal[j] != (M3D_INDEX)-1U)) + ptr += sprintf(ptr, "%s/%d", k == -1U? "/" : "", vrtxidx[face[i].data.normal[j]]); + } + ptr += sprintf(ptr, "\r\n"); + } + ptr += sprintf(ptr, "\r\n"); + } + /* mathematical shapes face */ + if(model->numshape && model->numshape && !(flags & M3D_EXP_NOFACE)) { + for(j = 0; j < model->numshape; j++) { + sn = _m3d_safestr(model->shape[j].name, 0); + if(!sn) { setlocale(LC_NUMERIC, ol); goto memerr; } + ptr -= (uint64_t)out; len = (uint64_t)ptr + strlen(sn) + 33; + out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uint64_t)out; + if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; } + ptr += sprintf(ptr, "Shape %s\r\n", sn); + M3D_FREE(sn); sn = NULL; + if(model->shape[j].group != (M3D_INDEX)-1U && !(flags & M3D_EXP_NOBONE)) + ptr += sprintf(ptr, "group %d\r\n", model->shape[j].group); + for(i = 0; i < model->shape[j].numcmd; i++) { + cmd = &model->shape[j].cmd[i]; + if(cmd->type >= (unsigned int)(sizeof(m3d_commandtypes)/sizeof(m3d_commandtypes[0])) || !cmd->arg) + continue; + cd = &m3d_commandtypes[cmd->type]; + ptr -= (uint64_t)out; len = (uint64_t)ptr + strlen(cd->key) + 3; + for(k = 0; k < cd->p; k++) + switch(cd->a[k]) { + case m3dcp_mi_t: if(cmd->arg[k] != -1U) { len += strlen(model->material[cmd->arg[k]].name) + 1; } break; + case m3dcp_va_t: len += cmd->arg[k] * (cd->p - k - 1) * 16; k = cd->p; break; + default: len += 16; break; + } + out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uint64_t)out; + if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; } + ptr += sprintf(ptr, "%s", cd->key); + for(k = n = 0, l = cd->p; k < l; k++) { + switch(cd->a[((k - n) % (cd->p - n)) + n]) { + case m3dcp_mi_t: + if(cmd->arg[k] != -1U) { + sn = _m3d_safestr(model->material[cmd->arg[k]].name, 0); + if(!sn) { setlocale(LC_NUMERIC, ol); goto memerr; } + ptr += sprintf(ptr, " %s", sn); + M3D_FREE(sn); sn = NULL; + } + break; + case m3dcp_vc_t: ptr += sprintf(ptr, " %g", *((float*)&cmd->arg[k])); break; + case m3dcp_va_t: ptr += sprintf(ptr, " %d[", cmd->arg[k]); + n = k + 1; l += (cmd->arg[k] - 1) * (cd->p - k - 1); + break; + default: ptr += sprintf(ptr, " %d", cmd->arg[k]); break; + } + } + ptr += sprintf(ptr, "%s\r\n", l > cd->p ? " ]" : ""); + } + ptr += sprintf(ptr, "\r\n"); + } + } + /* annotation labels */ + if(model->numlabel && model->label && !(flags & M3D_EXP_NOFACE)) { + for(i = 0, j = 3, length = NULL; i < model->numlabel; i++) { + if(model->label[i].name) j += strlen(model->label[i].name); + if(model->label[i].lang) j += strlen(model->label[i].lang); + if(model->label[i].text) j += strlen(model->label[i].text); + j += 40; + } + ptr -= (uint64_t)out; len = (uint64_t)ptr + j; + out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uint64_t)out; + if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; } + for(i = 0; i < model->numlabel; i++) { + if(!i || _m3d_strcmp(sl, model->label[i].lang) || _m3d_strcmp(sn, model->label[i].name)) { + sl = model->label[i].lang; + sn = model->label[i].name; + sd = _m3d_safestr(sn, 0); + if(!sd) { setlocale(LC_NUMERIC, ol); sn = sl = NULL; goto memerr; } + if(i) ptr += sprintf(ptr, "\r\n"); + ptr += sprintf(ptr, "Labels %s\r\n", sd); + M3D_FREE(sd); sd = NULL; + if(model->label[i].color) + ptr += sprintf(ptr, "color #0x%08x\r\n", model->label[i].color); + if(sl && *sl) { + sd = _m3d_safestr(sl, 0); + if(!sd) { setlocale(LC_NUMERIC, ol); sn = sl = NULL; goto memerr; } + ptr += sprintf(ptr, "lang %s\r\n", sd); + M3D_FREE(sd); sd = NULL; + } + } + sd = _m3d_safestr(model->label[i].text, 2); + if(!sd) { setlocale(LC_NUMERIC, ol); sn = sl = NULL; goto memerr; } + ptr += sprintf(ptr, "%d %s\r\n", model->label[i].vertexid, sd); + M3D_FREE(sd); sd = NULL; + } + ptr += sprintf(ptr, "\r\n"); + sn = sl = NULL; + } + /* actions */ + if(model->numaction && model->action && !(flags & M3D_EXP_NOACTION)) { + for(j = 0; j < model->numaction; j++) { + a = &model->action[j]; + sn = _m3d_safestr(a->name, 0); + if(!sn) { setlocale(LC_NUMERIC, ol); goto memerr; } + ptr -= (uint64_t)out; len = (uint64_t)ptr + strlen(sn) + 48; + for(i = 0; i < a->numframe; i++) + len += a->frame[i].numtransform * 128 + 8; + out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uint64_t)out; + if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; } + ptr += sprintf(ptr, "Action %d %s\r\n", a->durationmsec, sn); + M3D_FREE(sn); sn = NULL; + for(i = 0; i < a->numframe; i++) { + ptr += sprintf(ptr, "frame %d\r\n", a->frame[i].msec); + for(k = 0; k < a->frame[i].numtransform; k++) { + ptr += sprintf(ptr, "%d %d %d\r\n", a->frame[i].transform[k].boneid, + vrtxidx[a->frame[i].transform[k].pos], vrtxidx[a->frame[i].transform[k].ori]); + } + } + ptr += sprintf(ptr, "\r\n"); + } + } + /* inlined assets */ + if(model->numinlined && model->inlined) { + for(i = j = 0; i < model->numinlined; i++) + if(model->inlined[i].name) + j += strlen(model->inlined[i].name) + 6; + if(j > 0) { + ptr -= (uint64_t)out; len = (uint64_t)ptr + j + 16; + out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uint64_t)out; + if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; } + ptr += sprintf(ptr, "Assets\r\n"); + for(i = 0; i < model->numinlined; i++) + if(model->inlined[i].name) + ptr += sprintf(ptr, "%s%s\r\n", model->inlined[i].name, strrchr(model->inlined[i].name, '.') ? "" : ".png"); + ptr += sprintf(ptr, "\r\n"); + } + } + /* extra info */ + if(model->numextra && (flags & M3D_EXP_EXTRA)) { + for(i = 0; i < model->numextra; i++) { + if(model->extra[i]->length < 9) continue; + ptr -= (uint64_t)out; len = (uint64_t)ptr + 17 + model->extra[i]->length * 3; + out = (unsigned char*)M3D_REALLOC(out, len); ptr += (uint64_t)out; + if(!out) { setlocale(LC_NUMERIC, ol); goto memerr; } + ptr += sprintf(ptr, "Extra %c%c%c%c\r\n", + model->extra[i]->magic[0] > ' ' ? model->extra[i]->magic[0] : '_', + model->extra[i]->magic[1] > ' ' ? model->extra[i]->magic[1] : '_', + model->extra[i]->magic[2] > ' ' ? model->extra[i]->magic[2] : '_', + model->extra[i]->magic[3] > ' ' ? model->extra[i]->magic[3] : '_'); + for(j = 0; j < model->extra[i]->length; j++) + ptr += sprintf(ptr, "%02x ", *((unsigned char *)model->extra + sizeof(m3dchunk_t) + j)); + ptr--; + ptr += sprintf(ptr, "\r\n\r\n"); + } + } + setlocale(LC_NUMERIC, ol); + len = (uint64_t)ptr - (uint64_t)out; + out = (unsigned char*)M3D_REALLOC(out, len + 1); + if(!out) goto memerr; + out[len] = 0; + } else +#endif + { + /* stricly only use LF (newline) in binary */ + sd = _m3d_safestr(model->desc, 3); + if(!sd) goto memerr; + /* header */ + h = (m3dhdr_t*)M3D_MALLOC(sizeof(m3dhdr_t) + strlen(sn) + strlen(sl) + strlen(sa) + strlen(sd) + 4); + if(!h) goto memerr; + memcpy((uint8_t*)h, "HEAD", 4); + h->length = sizeof(m3dhdr_t); + h->scale = scale; + i = strlen(sn); memcpy((uint8_t*)h + h->length, sn, i+1); h->length += i+1; M3D_FREE(sn); + i = strlen(sl); memcpy((uint8_t*)h + h->length, sl, i+1); h->length += i+1; M3D_FREE(sl); + i = strlen(sa); memcpy((uint8_t*)h + h->length, sa, i+1); h->length += i+1; M3D_FREE(sa); + i = strlen(sd); memcpy((uint8_t*)h + h->length, sd, i+1); h->length += i+1; M3D_FREE(sd); + sn = sl = sa = sd = NULL; + if(model->inlined) + for(i = 0; i < model->numinlined; i++) { + if(model->inlined[i].name && *model->inlined[i].name && model->inlined[i].length > 0) { + str = _m3d_addstr(str, &numstr, model->inlined[i].name); + if(!str) goto memerr; + } + } + if(str) + for(i = 0; i < numstr; i++) { + h = _m3d_addhdr(h, &str[i]); + if(!h) goto memerr; + } + vc_s = quality == M3D_EXP_INT8? 1 : (quality == M3D_EXP_INT16? 2 : (quality == M3D_EXP_DOUBLE? 8 : 4)); + vi_s = maxvrtx < 254 ? 1 : (maxvrtx < 65534 ? 2 : 4); + si_s = h->length - 16 < 254 ? 1 : (h->length - 16 < 65534 ? 2 : 4); + ci_s = !numcmap || !cmap ? 0 : (numcmap < 254 ? 1 : (numcmap < 65534 ? 2 : 4)); + ti_s = !maxtmap || !tmap ? 0 : (maxtmap < 254 ? 1 : (maxtmap < 65534 ? 2 : 4)); + bi_s = !model->numbone || !model->bone || (flags & M3D_EXP_NOBONE)? 0 : (model->numbone < 254 ? 1 : + (model->numbone < 65534 ? 2 : 4)); + nb_s = maxbone < 2 ? 1 : (maxbone == 2 ? 2 : (maxbone <= 4 ? 4 : 8)); + sk_s = !bi_s || !maxskin || !skin ? 0 : (maxskin < 254 ? 1 : (maxskin < 65534 ? 2 : 4)); + fc_s = maxt < 254 ? 1 : (maxt < 65534 ? 2 : 4); + hi_s = !model->numshape || !model->shape || (flags & M3D_EXP_NOFACE)? 0 : (model->numshape < 254 ? 1 : + (model->numshape < 65534 ? 2 : 4)); + fi_s = !model->numface || !model->face || (flags & M3D_EXP_NOFACE)? 0 : (model->numface < 254 ? 1 : + (model->numface < 65534 ? 2 : 4)); + h->types = (vc_s == 8 ? (3<<0) : (vc_s == 2 ? (1<<0) : (vc_s == 1 ? (0<<0) : (2<<0)))) | + (vi_s == 2 ? (1<<2) : (vi_s == 1 ? (0<<2) : (2<<2))) | + (si_s == 2 ? (1<<4) : (si_s == 1 ? (0<<4) : (2<<4))) | + (ci_s == 2 ? (1<<6) : (ci_s == 1 ? (0<<6) : (ci_s == 4 ? (2<<6) : (3<<6)))) | + (ti_s == 2 ? (1<<8) : (ti_s == 1 ? (0<<8) : (ti_s == 4 ? (2<<8) : (3<<8)))) | + (bi_s == 2 ? (1<<10): (bi_s == 1 ? (0<<10): (bi_s == 4 ? (2<<10) : (3<<10)))) | + (nb_s == 2 ? (1<<12): (nb_s == 1 ? (0<<12): (2<<12))) | + (sk_s == 2 ? (1<<14): (sk_s == 1 ? (0<<14): (sk_s == 4 ? (2<<14) : (3<<14)))) | + (fc_s == 2 ? (1<<16): (fc_s == 1 ? (0<<16): (2<<16))) | + (hi_s == 2 ? (1<<18): (hi_s == 1 ? (0<<18): (hi_s == 4 ? (2<<18) : (3<<18)))) | + (fi_s == 2 ? (1<<20): (fi_s == 1 ? (0<<20): (fi_s == 4 ? (2<<20) : (3<<20)))); + len = h->length; + /* preview image chunk, must be the first if exists */ + if(model->preview.data && model->preview.length) { + chunklen = 8 + model->preview.length; + h = (m3dhdr_t*)M3D_REALLOC(h, len + chunklen); + if(!h) goto memerr; + memcpy((uint8_t*)h + len, "PRVW", 4); + *((uint32_t*)((uint8_t*)h + len + 4)) = chunklen; + memcpy((uint8_t*)h + len + 8, model->preview.data, model->preview.length); + len += chunklen; + } + /* color map */ + if(numcmap && cmap && ci_s < 4 && !(flags & M3D_EXP_NOCMAP)) { + chunklen = 8 + numcmap * sizeof(uint32_t); + h = (m3dhdr_t*)M3D_REALLOC(h, len + chunklen); + if(!h) goto memerr; + memcpy((uint8_t*)h + len, "CMAP", 4); + *((uint32_t*)((uint8_t*)h + len + 4)) = chunklen; + memcpy((uint8_t*)h + len + 8, cmap, chunklen - 8); + len += chunklen; + } else numcmap = 0; + /* texture map */ + if(numtmap && tmap && !(flags & M3D_EXP_NOTXTCRD) && !(flags & M3D_EXP_NOFACE)) { + chunklen = 8 + maxtmap * vc_s * 2; + h = (m3dhdr_t*)M3D_REALLOC(h, len + chunklen); + if(!h) goto memerr; + memcpy((uint8_t*)h + len, "TMAP", 4); + length = (uint32_t*)((uint8_t*)h + len + 4); + out = (uint8_t*)h + len + 8; + last = (M3D_INDEX)-1U; + for(i = 0; i < numtmap; i++) { + if(tmap[i].newidx == last) continue; + last = tmap[i].newidx; + switch(vc_s) { + case 1: *out++ = (uint8_t)(tmap[i].data.u * 255); *out++ = (uint8_t)(tmap[i].data.v * 255); break; + case 2: + *((uint16_t*)out) = (uint16_t)(tmap[i].data.u * 65535); out += 2; + *((uint16_t*)out) = (uint16_t)(tmap[i].data.v * 65535); out += 2; + break; + case 4: *((float*)out) = tmap[i].data.u; out += 4; *((float*)out) = tmap[i].data.v; out += 4; break; + case 8: *((double*)out) = tmap[i].data.u; out += 8; *((double*)out) = tmap[i].data.v; out += 8; break; + } + } + *length = (uint64_t)out - (uint64_t)((uint8_t*)h + len); + out = NULL; + len += *length; + } + /* vertex */ + if(numvrtx && vrtx) { + chunklen = 8 + maxvrtx * (ci_s + sk_s + 4 * vc_s); + h = (m3dhdr_t*)M3D_REALLOC(h, len + chunklen); + if(!h) goto memerr; + memcpy((uint8_t*)h + len, "VRTS", 4); + length = (uint32_t*)((uint8_t*)h + len + 4); + out = (uint8_t*)h + len + 8; + last = (M3D_INDEX)-1U; + for(i = 0; i < numvrtx; i++) { + if(vrtx[i].newidx == last) continue; + last = vrtx[i].newidx; + switch(vc_s) { + case 1: + *out++ = (int8_t)(vrtx[i].data.x * 127); + *out++ = (int8_t)(vrtx[i].data.y * 127); + *out++ = (int8_t)(vrtx[i].data.z * 127); + *out++ = (int8_t)(vrtx[i].data.w * 127); + break; + case 2: + *((int16_t*)out) = (int16_t)(vrtx[i].data.x * 32767); out += 2; + *((int16_t*)out) = (int16_t)(vrtx[i].data.y * 32767); out += 2; + *((int16_t*)out) = (int16_t)(vrtx[i].data.z * 32767); out += 2; + *((int16_t*)out) = (int16_t)(vrtx[i].data.w * 32767); out += 2; + break; + case 4: + *((float*)out) = vrtx[i].data.x; out += 4; + *((float*)out) = vrtx[i].data.y; out += 4; + *((float*)out) = vrtx[i].data.z; out += 4; + *((float*)out) = vrtx[i].data.w; out += 4; + break; + case 8: + *((double*)out) = vrtx[i].data.x; out += 8; + *((double*)out) = vrtx[i].data.y; out += 8; + *((double*)out) = vrtx[i].data.z; out += 8; + *((double*)out) = vrtx[i].data.w; out += 8; + break; + } + idx = _m3d_cmapidx(cmap, numcmap, vrtx[i].data.color); + switch(ci_s) { + case 1: *out++ = (uint8_t)(idx); break; + case 2: *((uint16_t*)out) = (uint16_t)(idx); out += 2; break; + case 4: *((uint32_t*)out) = vrtx[i].data.color; out += 4; break; + } + out = _m3d_addidx(out, sk_s, vrtx[i].data.skinid); + } + *length = (uint64_t)out - (uint64_t)((uint8_t*)h + len); + out = NULL; + len += *length; + } + /* bones chunk */ + if(model->numbone && model->bone && !(flags & M3D_EXP_NOBONE)) { + i = 8 + bi_s + sk_s + model->numbone * (bi_s + si_s + 2*vi_s); + chunklen = i + numskin * nb_s * (bi_s + 1); + h = (m3dhdr_t*)M3D_REALLOC(h, len + chunklen); + if(!h) goto memerr; + memcpy((uint8_t*)h + len, "BONE", 4); + length = (uint32_t*)((uint8_t*)h + len + 4); + out = (uint8_t*)h + len + 8; + out = _m3d_addidx(out, bi_s, model->numbone); + out = _m3d_addidx(out, sk_s, maxskin); + for(i = 0; i < model->numbone; i++) { + out = _m3d_addidx(out, bi_s, model->bone[i].parent); + out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, model->bone[i].name)); + out = _m3d_addidx(out, vi_s, vrtxidx[model->bone[i].pos]); + out = _m3d_addidx(out, vi_s, vrtxidx[model->bone[i].ori]); + } + if(numskin && skin && sk_s) { + last = (M3D_INDEX)-1U; + for(i = 0; i < numskin; i++) { + if(skin[i].newidx == last) continue; + last = skin[i].newidx; + memset(&weights, 0, nb_s); + for(j = 0; j < (uint32_t)nb_s && skin[i].data.boneid[j] != (M3D_INDEX)-1U && + skin[i].data.weight[j] > (M3D_FLOAT)0.0; j++) + weights[j] = (uint8_t)(skin[i].data.weight[j] * 255); + switch(nb_s) { + case 1: weights[0] = 255; break; + case 2: *((uint16_t*)out) = *((uint16_t*)&weights[0]); out += 2; break; + case 4: *((uint32_t*)out) = *((uint32_t*)&weights[0]); out += 4; break; + case 8: *((uint64_t*)out) = *((uint64_t*)&weights[0]); out += 8; break; + } + for(j = 0; j < (uint32_t)nb_s && skin[i].data.boneid[j] != (M3D_INDEX)-1U && weights[j]; j++) { + out = _m3d_addidx(out, bi_s, skin[i].data.boneid[j]); + *length += bi_s; + } + } + } + *length = (uint64_t)out - (uint64_t)((uint8_t*)h + len); + out = NULL; + len += *length; + } + /* materials */ + if(model->nummaterial && !(flags & M3D_EXP_NOMATERIAL)) { + for(j = 0; j < model->nummaterial; j++) { + if(mtrlidx[j] == (M3D_INDEX)-1U || !model->material[j].numprop || !model->material[j].prop) continue; + m = &model->material[j]; + chunklen = 12 + si_s + m->numprop * 5; + h = (m3dhdr_t*)M3D_REALLOC(h, len + chunklen); + if(!h) goto memerr; + memcpy((uint8_t*)h + len, "MTRL", 4); + length = (uint32_t*)((uint8_t*)h + len + 4); + out = (uint8_t*)h + len + 8; + out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, m->name)); + for(i = 0; i < m->numprop; i++) { + if(m->prop[i].type >= 128) { + if(m->prop[i].value.textureid >= model->numtexture || + !model->texture[m->prop[i].value.textureid].name) continue; + k = m3dpf_map; + } else { + for(k = 256, l = 0; l < sizeof(m3d_propertytypes)/sizeof(m3d_propertytypes[0]); l++) + if(m->prop[i].type == m3d_propertytypes[l].id) { k = m3d_propertytypes[l].format; break; } + } + if(k == 256) continue; + *out++ = m->prop[i].type; + switch(k) { + case m3dpf_color: + if(!(flags & M3D_EXP_NOCMAP)) { + idx = _m3d_cmapidx(cmap, numcmap, m->prop[i].value.color); + switch(ci_s) { + case 1: *out++ = (uint8_t)(idx); break; + case 2: *((uint16_t*)out) = (uint16_t)(idx); out += 2; break; + case 4: *((uint32_t*)out) = (uint32_t)(m->prop[i].value.color); out += 4; break; + } + } else out--; + break; + case m3dpf_uint8: *out++ = m->prop[i].value.num; break; + case m3dpf_uint16: *((uint16_t*)out) = m->prop[i].value.num; out += 2; break; + case m3dpf_uint32: *((uint32_t*)out) = m->prop[i].value.num; out += 4; break; + case m3dpf_float: *((float*)out) = m->prop[i].value.fnum; out += 4; break; + + case m3dpf_map: + idx = _m3d_stridx(str, numstr, model->texture[m->prop[i].value.textureid].name); + out = _m3d_addidx(out, si_s, idx); + break; + } + } + *length = (uint64_t)out - (uint64_t)((uint8_t*)h + len); + len += *length; + out = NULL; + } + } + /* procedural face */ + if(model->numinlined && model->inlined && !(flags & M3D_EXP_NOFACE)) { + /* all inlined assets which are not textures should be procedural surfaces */ + for(j = 0; j < model->numinlined; j++) { + if(!model->inlined[j].name || !model->inlined[j].name[0] || model->inlined[j].length < 4 || + !model->inlined[j].data || (model->inlined[j].data[1] == 'P' && model->inlined[j].data[2] == 'N' && + model->inlined[j].data[3] == 'G')) + continue; + for(i = k = 0; i < model->numtexture; i++) { + if(!strcmp(model->inlined[j].name, model->texture[i].name)) { k = 1; break; } + } + if(k) continue; + numproc++; + chunklen = 8 + si_s; + h = (m3dhdr_t*)M3D_REALLOC(h, len + chunklen); + if(!h) goto memerr; + memcpy((uint8_t*)h + len, "PROC", 4); + *((uint32_t*)((uint8_t*)h + len + 4)) = chunklen; + out = (uint8_t*)h + len + 8; + out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, model->inlined[j].name)); + out = NULL; + len += chunklen; + } + } + /* mesh face */ + if(model->numface && face && !(flags & M3D_EXP_NOFACE)) { + chunklen = 8 + si_s + model->numface * (6 * vi_s + 3 * ti_s + si_s + 1); + h = (m3dhdr_t*)M3D_REALLOC(h, len + chunklen); + if(!h) goto memerr; + memcpy((uint8_t*)h + len, "MESH", 4); + length = (uint32_t*)((uint8_t*)h + len + 4); + out = (uint8_t*)h + len + 8; + last = (M3D_INDEX)-1U; + for(i = 0; i < model->numface; i++) { + if(!(flags & M3D_EXP_NOMATERIAL) && face[i].data.materialid != last) { + last = face[i].data.materialid; + idx = last < model->nummaterial ? _m3d_stridx(str, numstr, model->material[last].name) : 0; + *out++ = 0; + out = _m3d_addidx(out, si_s, idx); + } + /* hardcoded triangles. */ + k = (3 << 4) | + (((flags & M3D_EXP_NOTXTCRD) || !ti_s || face[i].data.texcoord[0] == (M3D_INDEX)-1U || + face[i].data.texcoord[1] == (M3D_INDEX)-1U || face[i].data.texcoord[2] == (M3D_INDEX)-1U) ? 0 : 1) | + (((flags & M3D_EXP_NONORMAL) || face[i].data.normal[0] == (M3D_INDEX)-1U || + face[i].data.normal[1] == (M3D_INDEX)-1U || face[i].data.normal[2] == (M3D_INDEX)-1U) ? 0 : 2); + *out++ = k; + for(j = 0; j < 3; j++) { + out = _m3d_addidx(out, vi_s, vrtxidx[face[i].data.vertex[j]]); + if(k & 1) + out = _m3d_addidx(out, ti_s, tmapidx[face[i].data.texcoord[j]]); + if(k & 2) + out = _m3d_addidx(out, vi_s, vrtxidx[face[i].data.normal[j]]); + } + } + *length = (uint64_t)out - (uint64_t)((uint8_t*)h + len); + len += *length; + out = NULL; + } + /* mathematical shapes face */ + if(model->numshape && model->shape && !(flags & M3D_EXP_NOFACE)) { + for(j = 0; j < model->numshape; j++) { + chunklen = 12 + si_s + model->shape[j].numcmd * (M3D_CMDMAXARG + 1) * 4; + h = (m3dhdr_t*)M3D_REALLOC(h, len + chunklen); + if(!h) goto memerr; + memcpy((uint8_t*)h + len, "SHPE", 4); + length = (uint32_t*)((uint8_t*)h + len + 4); + out = (uint8_t*)h + len + 8; + out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, model->shape[j].name)); + out = _m3d_addidx(out, bi_s, model->shape[j].group); + for(i = 0; i < model->shape[j].numcmd; i++) { + cmd = &model->shape[j].cmd[i]; + if(cmd->type >= (unsigned int)(sizeof(m3d_commandtypes)/sizeof(m3d_commandtypes[0])) || !cmd->arg) + continue; + cd = &m3d_commandtypes[cmd->type]; + *out++ = (cmd->type & 0x7F) | (cmd->type > 127 ? 0x80 : 0); + if(cmd->type > 127) *out++ = (cmd->type >> 7) & 0xff; + for(k = n = 0, l = cd->p; k < l; k++) { + switch(cd->a[((k - n) % (cd->p - n)) + n]) { + case m3dcp_mi_t: + out = _m3d_addidx(out, si_s, cmd->arg[k] < model->nummaterial ? + _m3d_stridx(str, numstr, model->material[cmd->arg[k]].name) : 0); + break; + case m3dcp_vc_t: + min_x = *((float*)&cmd->arg[k]); + switch(vc_s) { + case 1: *out++ = (int8_t)(min_x * 127); break; + case 2: *((int16_t*)out) = (int16_t)(min_x * 32767); out += 2; break; + case 4: *((float*)out) = min_x; out += 4; break; + case 8: *((double*)out) = min_x; out += 8; break; + } + break; + case m3dcp_hi_t: out = _m3d_addidx(out, hi_s, cmd->arg[k]); break; + case m3dcp_fi_t: out = _m3d_addidx(out, fi_s, cmd->arg[k]); break; + case m3dcp_ti_t: out = _m3d_addidx(out, ti_s, cmd->arg[k]); break; + case m3dcp_qi_t: + case m3dcp_vi_t: out = _m3d_addidx(out, vi_s, cmd->arg[k]); break; + case m3dcp_i1_t: out = _m3d_addidx(out, 1, cmd->arg[k]); break; + case m3dcp_i2_t: out = _m3d_addidx(out, 2, cmd->arg[k]); break; + case m3dcp_i4_t: out = _m3d_addidx(out, 4, cmd->arg[k]); break; + case m3dcp_va_t: out = _m3d_addidx(out, 4, cmd->arg[k]); + n = k + 1; l += (cmd->arg[k] - 1) * (cd->p - k - 1); + break; + } + } + } + *length = (uint64_t)out - (uint64_t)((uint8_t*)h + len); + len += *length; + out = NULL; + } + } + /* annotation labels */ + if(model->numlabel && model->label) { + for(i = 0, length = NULL; i < model->numlabel; i++) { + if(!i || _m3d_strcmp(sl, model->label[i].lang) || _m3d_strcmp(sn, model->label[i].name)) { + sl = model->label[i].lang; + sn = model->label[i].name; + if(length) { + *length = (uint64_t)out - (uint64_t)((uint8_t*)h + len); + len += *length; + } + chunklen = 8 + 2 * si_s + ci_s + model->numlabel * (vi_s + si_s); + h = (m3dhdr_t*)M3D_REALLOC(h, len + chunklen); + if(!h) { sn = NULL; sl = NULL; goto memerr; } + memcpy((uint8_t*)h + len, "LBLS", 4); + length = (uint32_t*)((uint8_t*)h + len + 4); + out = (uint8_t*)h + len + 8; + out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, model->label[l].name)); + out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, model->label[l].lang)); + idx = _m3d_cmapidx(cmap, numcmap, model->label[i].color); + switch(ci_s) { + case 1: *out++ = (uint8_t)(idx); break; + case 2: *((uint16_t*)out) = (uint16_t)(idx); out += 2; break; + case 4: *((uint32_t*)out) = model->label[i].color; out += 4; break; + } + } + out = _m3d_addidx(out, vi_s, vrtxidx[model->label[i].vertexid]); + out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, model->label[l].text)); + } + if(length) { + *length = (uint64_t)out - (uint64_t)((uint8_t*)h + len); + len += *length; + } + out = NULL; + sn = sl = NULL; + } + /* actions */ + if(model->numaction && model->action && model->numbone && model->bone && !(flags & M3D_EXP_NOACTION)) { + for(j = 0; j < model->numaction; j++) { + a = &model->action[j]; + chunklen = 14 + si_s + a->numframe * (4 + fc_s + maxt * (bi_s + 2 * vi_s)); + h = (m3dhdr_t*)M3D_REALLOC(h, len + chunklen); + if(!h) goto memerr; + memcpy((uint8_t*)h + len, "ACTN", 4); + length = (uint32_t*)((uint8_t*)h + len + 4); + out = (uint8_t*)h + len + 8; + out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, a->name)); + *((uint16_t*)out) = (uint16_t)(a->numframe); out += 2; + *((uint32_t*)out) = (uint32_t)(a->durationmsec); out += 4; + for(i = 0; i < a->numframe; i++) { + *((uint32_t*)out) = (uint32_t)(a->frame[i].msec); out += 4; + out = _m3d_addidx(out, fc_s, a->frame[i].numtransform); + for(k = 0; k < a->frame[i].numtransform; k++) { + out = _m3d_addidx(out, bi_s, a->frame[i].transform[k].boneid); + out = _m3d_addidx(out, vi_s, vrtxidx[a->frame[i].transform[k].pos]); + out = _m3d_addidx(out, vi_s, vrtxidx[a->frame[i].transform[k].ori]); + } + } + *length = (uint64_t)out - (uint64_t)((uint8_t*)h + len); + len += *length; + out = NULL; + } + } + /* inlined assets */ + if(model->numinlined && model->inlined && (numproc || (flags & M3D_EXP_INLINE))) { + for(j = 0; j < model->numinlined; j++) { + if(!model->inlined[j].name || !model->inlined[j].name[0] || model->inlined[j].length<4 || !model->inlined[j].data) + continue; + if(!(flags & M3D_EXP_INLINE)) { + if(model->inlined[j].data[1] == 'P' && model->inlined[j].data[2] == 'N' && model->inlined[j].data[3] == 'G') + continue; + for(i = k = 0; i < model->numtexture; i++) { + if(!strcmp(model->inlined[j].name, model->texture[i].name)) { k = 1; break; } + } + if(k) continue; + } + chunklen = 8 + si_s + model->inlined[j].length; + h = (m3dhdr_t*)M3D_REALLOC(h, len + chunklen); + if(!h) goto memerr; + memcpy((uint8_t*)h + len, "ASET", 4); + *((uint32_t*)((uint8_t*)h + len + 4)) = chunklen; + out = (uint8_t*)h + len + 8; + out = _m3d_addidx(out, si_s, _m3d_stridx(str, numstr, model->inlined[j].name)); + memcpy(out, model->inlined[j].data, model->inlined[j].length); + out = NULL; + len += chunklen; + } + } + /* extra chunks */ + if(model->numextra && model->extra && (flags & M3D_EXP_EXTRA)) { + for(j = 0; j < model->numextra; j++) { + if(!model->extra[j] || model->extra[j]->length < 8) + continue; + chunklen = model->extra[j]->length; + h = (m3dhdr_t*)M3D_REALLOC(h, len + chunklen); + if(!h) goto memerr; + memcpy((uint8_t*)h + len, model->extra[j], chunklen); + len += chunklen; + } + } + /* add end chunk */ + h = (m3dhdr_t*)M3D_REALLOC(h, len + 4); + if(!h) goto memerr; + memcpy((uint8_t*)h + len, "OMD3", 4); + len += 4; + /* zlib compress */ + if(!(flags & M3D_EXP_NOZLIB)) { + M3D_LOG("Deflating chunks"); + z = stbi_zlib_compress((unsigned char *)h, len, (int*)&l, 9); + if(z && l > 0 && l < len) { len = l; M3D_FREE(h); h = (m3dhdr_t*)z; } + } + /* add file header at the begining */ + len += 8; + out = (unsigned char*)M3D_MALLOC(len); + if(!out) goto memerr; + memcpy(out, "3DMO", 4); + *((uint32_t*)(out + 4)) = len; + memcpy(out + 8, h, len - 8); + } + if(size) *size = out ? len : 0; + if(vrtxidx) M3D_FREE(vrtxidx); + if(mtrlidx) M3D_FREE(mtrlidx); + if(tmapidx) M3D_FREE(tmapidx); + if(skinidx) M3D_FREE(skinidx); + if(norm) M3D_FREE(norm); + if(face) M3D_FREE(face); + if(cmap) M3D_FREE(cmap); + if(tmap) M3D_FREE(tmap); + if(skin) M3D_FREE(skin); + if(str) M3D_FREE(str); + if(vrtx) M3D_FREE(vrtx); + if(h) M3D_FREE(h); + return out; +} +#endif + +#endif + +#ifdef __cplusplus +} +#ifdef M3D_CPPWRAPPER +#include +#include +#include + +/*** C++ wrapper class ***/ +namespace M3D { +#ifdef M3D_IMPLEMENTATION + + class Model { + public: + m3d_t *model; + + public: + Model() { + this->model = (m3d_t*)malloc(sizeof(m3d_t)); memset(this->model, 0, sizeof(m3d_t)); + } + Model(_unused const std::string &data, _unused m3dread_t ReadFileCB, + _unused m3dfree_t FreeCB, _unused M3D::Model mtllib) { +#ifndef M3D_NOIMPORTER + this->model = m3d_load((unsigned char *)data.data(), ReadFileCB, FreeCB, mtllib.model); +#else + Model(); +#endif + } + Model(_unused const std::vector data, _unused m3dread_t ReadFileCB, + _unused m3dfree_t FreeCB, _unused M3D::Model mtllib) { +#ifndef M3D_NOIMPORTER + this->model = m3d_load((unsigned char *)&data[0], ReadFileCB, FreeCB, mtllib.model); +#else + Model(); +#endif + } + Model(_unused const unsigned char *data, _unused m3dread_t ReadFileCB, + _unused m3dfree_t FreeCB, _unused M3D::Model mtllib) { +#ifndef M3D_NOIMPORTER + this->model = m3d_load((unsigned char*)data, ReadFileCB, FreeCB, mtllib.model); +#else + Model(); +#endif + } + ~Model() { m3d_free(this->model); } + + public: + m3d_t *getCStruct() { return this->model; } + std::string getName() { return std::string(this->model->name); } + void setName(std::string name) { this->model->name = (char*)name.c_str(); } + std::string getLicense() { return std::string(this->model->license); } + void setLicense(std::string license) { this->model->license = (char*)license.c_str(); } + std::string getAuthor() { return std::string(this->model->author); } + void setAuthor(std::string author) { this->model->author = (char*)author.c_str(); } + std::string getDescription() { return std::string(this->model->desc); } + void setDescription(std::string desc) { this->model->desc = (char*)desc.c_str(); } + float getScale() { return this->model->scale; } + void setScale(float scale) { this->model->scale = scale; } + std::vector getPreview() { return this->model->preview.data ? + std::vector(this->model->preview.data, this->model->preview.data + this->model->preview.length) : + std::vector(); } + std::vector getColorMap() { return this->model->cmap ? std::vector(this->model->cmap, + this->model->cmap + this->model->numcmap) : std::vector(); } + std::vector getTextureMap() { return this->model->tmap ? std::vector(this->model->tmap, + this->model->tmap + this->model->numtmap) : std::vector(); } + std::vector getTextures() { return this->model->texture ? std::vector(this->model->texture, + this->model->texture + this->model->numtexture) : std::vector(); } + std::string getTextureName(int idx) { return idx >= 0 && (unsigned int)idx < this->model->numtexture ? + std::string(this->model->texture[idx].name) : nullptr; } + std::vector getBones() { return this->model->bone ? std::vector(this->model->bone, this->model->bone + + this->model->numbone) : std::vector(); } + std::string getBoneName(int idx) { return idx >= 0 && (unsigned int)idx < this->model->numbone ? + std::string(this->model->bone[idx].name) : nullptr; } + std::vector getMaterials() { return this->model->material ? std::vector(this->model->material, + this->model->material + this->model->nummaterial) : std::vector(); } + std::string getMaterialName(int idx) { return idx >= 0 && (unsigned int)idx < this->model->nummaterial ? + std::string(this->model->material[idx].name) : nullptr; } + int getMaterialPropertyInt(int idx, int type) { + if (idx < 0 || (unsigned int)idx >= this->model->nummaterial || type < 0 || type >= 127 || + !this->model->material[idx].prop) return -1; + for (int i = 0; i < this->model->material[idx].numprop; i++) { + if (this->model->material[idx].prop[i].type == type) + return this->model->material[idx].prop[i].value.num; + } + return -1; + } + uint32_t getMaterialPropertyColor(int idx, int type) { return this->getMaterialPropertyInt(idx, type); } + float getMaterialPropertyFloat(int idx, int type) { + if (idx < 0 || (unsigned int)idx >= this->model->nummaterial || type < 0 || type >= 127 || + !this->model->material[idx].prop) return -1.0f; + for (int i = 0; i < this->model->material[idx].numprop; i++) { + if (this->model->material[idx].prop[i].type == type) + return this->model->material[idx].prop[i].value.fnum; + } + return -1.0f; + } + m3dtx_t* getMaterialPropertyMap(int idx, int type) { + if (idx < 0 || (unsigned int)idx >= this->model->nummaterial || type < 128 || type > 255 || + !this->model->material[idx].prop) return nullptr; + for (int i = 0; i < this->model->material[idx].numprop; i++) { + if (this->model->material[idx].prop[i].type == type) + return this->model->material[idx].prop[i].value.textureid < this->model->numtexture ? + &this->model->texture[this->model->material[idx].prop[i].value.textureid] : nullptr; + } + return nullptr; + } + std::vector getVertices() { return this->model->vertex ? std::vector(this->model->vertex, + this->model->vertex + this->model->numvertex) : std::vector(); } + std::vector getFace() { return this->model->face ? std::vector(this->model->face, this->model->face + + this->model->numface) : std::vector(); } + std::vector getShape() { return this->model->shape ? std::vector(this->model->shape, + this->model->shape + this->model->numshape) : std::vector(); } + std::string getShapeName(int idx) { return idx >= 0 && (unsigned int)idx < this->model->numshape && + this->model->shape[idx].name && this->model->shape[idx].name[0] ? + std::string(this->model->shape[idx].name) : nullptr; } + unsigned int getShapeGroup(int idx) { return idx >= 0 && (unsigned int)idx < this->model->numshape ? + this->model->shape[idx].group : 0xFFFFFFFF; } + std::vector getShapeCommands(int idx) { return idx >= 0 && (unsigned int)idx < this->model->numshape && + this->model->shape[idx].cmd ? std::vector(this->model->shape[idx].cmd, this->model->shape[idx].cmd + + this->model->shape[idx].numcmd) : std::vector(); } + std::vector getAnnotationLabels() { return this->model->label ? std::vector(this->model->label, + this->model->label + this->model->numlabel) : std::vector(); } + std::vector getSkin() { return this->model->skin ? std::vector(this->model->skin, this->model->skin + + this->model->numskin) : std::vector(); } + std::vector getActions() { return this->model->action ? std::vector(this->model->action, + this->model->action + this->model->numaction) : std::vector(); } + std::string getActionName(int aidx) { return aidx >= 0 && (unsigned int)aidx < this->model->numaction ? + std::string(this->model->action[aidx].name) : nullptr; } + unsigned int getActionDuration(int aidx) { return aidx >= 0 && (unsigned int)aidx < this->model->numaction ? + this->model->action[aidx].durationmsec : 0; } + std::vector getActionFrames(int aidx) { return aidx >= 0 && (unsigned int)aidx < this->model->numaction ? + std::vector(this->model->action[aidx].frame, this->model->action[aidx].frame + + this->model->action[aidx].numframe) : std::vector(); } + unsigned int getActionFrameTimestamp(int aidx, int fidx) { return aidx >= 0 && (unsigned int)aidx < this->model->numaction? + (fidx >= 0 && (unsigned int)fidx < this->model->action[aidx].numframe ? + this->model->action[aidx].frame[fidx].msec : 0) : 0; } + std::vector getActionFrameTransforms(int aidx, int fidx) { + return aidx >= 0 && (unsigned int)aidx < this->model->numaction ? ( + fidx >= 0 && (unsigned int)fidx < this->model->action[aidx].numframe ? + std::vector(this->model->action[aidx].frame[fidx].transform, + this->model->action[aidx].frame[fidx].transform + this->model->action[aidx].frame[fidx].numtransform) : + std::vector()) : std::vector(); } + std::vector getActionFrame(int aidx, int fidx, std::vector skeleton) { + m3dtr_t *pose = m3d_frame(this->model, (unsigned int)aidx, (unsigned int)fidx, + skeleton.size() ? &skeleton[0] : nullptr); + return std::vector(pose, pose + this->model->numbone); } + std::vector getActionPose(int aidx, unsigned int msec) { + m3db_t *pose = m3d_pose(this->model, (unsigned int)aidx, (unsigned int)msec); + return std::vector(pose, pose + this->model->numbone); } + std::vector getInlinedAssets() { return this->model->inlined ? std::vector(this->model->inlined, + this->model->inlined + this->model->numinlined) : std::vector(); } + std::vector> getExtras() { return this->model->extra ? + std::vector>(this->model->extra, + this->model->extra + this->model->numextra) : std::vector>(); } + std::vector Save(_unused int quality, _unused int flags) { +#ifdef M3D_EXPORTER + unsigned int size; + unsigned char *ptr = m3d_save(this->model, quality, flags, &size); + return ptr && size ? std::vector(ptr, ptr + size) : std::vector(); +#else + return std::vector(); +#endif + } + }; + +#else + class Model { + public: + m3d_t *model; + + public: + Model(const std::string &data, m3dread_t ReadFileCB, m3dfree_t FreeCB); + Model(const std::vector data, m3dread_t ReadFileCB, m3dfree_t FreeCB); + Model(const unsigned char *data, m3dread_t ReadFileCB, m3dfree_t FreeCB); + Model(); + ~Model(); + + public: + m3d_t *getCStruct(); + std::string getName(); + void setName(std::string name); + std::string getLicense(); + void setLicense(std::string license); + std::string getAuthor(); + void setAuthor(std::string author); + std::string getDescription(); + void setDescription(std::string desc); + float getScale(); + void setScale(float scale); + std::vector getPreview(); + std::vector getColorMap(); + std::vector getTextureMap(); + std::vector getTextures(); + std::string getTextureName(int idx); + std::vector getBones(); + std::string getBoneName(int idx); + std::vector getMaterials(); + std::string getMaterialName(int idx); + int getMaterialPropertyInt(int idx, int type); + uint32_t getMaterialPropertyColor(int idx, int type); + float getMaterialPropertyFloat(int idx, int type); + m3dtx_t* getMaterialPropertyMap(int idx, int type); + std::vector getVertices(); + std::vector getFace(); + std::vector getShape(); + std::string getShapeName(int idx); + unsigned int getShapeGroup(int idx); + std::vector getShapeCommands(int idx); + std::vector getAnnotationLabels(); + std::vector getSkin(); + std::vector getActions(); + std::string getActionName(int aidx); + unsigned int getActionDuration(int aidx); + std::vector getActionFrames(int aidx); + unsigned int getActionFrameTimestamp(int aidx, int fidx); + std::vector getActionFrameTransforms(int aidx, int fidx); + std::vector getActionFrame(int aidx, int fidx, std::vector skeleton); + std::vector getActionPose(int aidx, unsigned int msec); + std::vector getInlinedAssets(); + std::vector> getExtras(); + std::vector Save(int quality, int flags); + }; + +#endif /* impl */ +} +#endif + +#endif /* __cplusplus */ + +#endif diff --git a/code/Material/MaterialSystem.cpp b/code/Material/MaterialSystem.cpp index 0be6e9f7b..fabd9415a 100644 --- a/code/Material/MaterialSystem.cpp +++ b/code/Material/MaterialSystem.cpp @@ -471,12 +471,12 @@ aiReturn aiMaterial::AddBinaryProperty (const void* pInput, aiPropertyTypeInfo pType ) { - ai_assert( pInput != NULL ); - ai_assert( pKey != NULL ); + ai_assert( pInput != nullptr ); + ai_assert(pKey != nullptr ); ai_assert( 0 != pSizeInBytes ); if ( 0 == pSizeInBytes ) { - + return AI_FAILURE; } // first search the list whether there is already an entry with this key diff --git a/code/PostProcessing/ArmaturePopulate.cpp b/code/PostProcessing/ArmaturePopulate.cpp new file mode 100644 index 000000000..75daeb6b5 --- /dev/null +++ b/code/PostProcessing/ArmaturePopulate.cpp @@ -0,0 +1,268 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ +#include "ArmaturePopulate.h" + +#include +#include +#include +#include +#include + +namespace Assimp { + +/// The default class constructor. +ArmaturePopulate::ArmaturePopulate() : BaseProcess() +{} + +/// The class destructor. +ArmaturePopulate::~ArmaturePopulate() +{} + +bool ArmaturePopulate::IsActive(unsigned int pFlags) const { + return (pFlags & aiProcess_PopulateArmatureData) != 0; +} + +void ArmaturePopulate::SetupProperties(const Importer *pImp) { + // do nothing +} + +void ArmaturePopulate::Execute(aiScene *out) { + + // Now convert all bone positions to the correct mOffsetMatrix + std::vector bones; + std::vector nodes; + std::map bone_stack; + BuildBoneList(out->mRootNode, out->mRootNode, out, bones); + BuildNodeList(out->mRootNode, nodes); + + BuildBoneStack(out->mRootNode, out->mRootNode, out, bones, bone_stack, nodes); + + ASSIMP_LOG_DEBUG_F("Bone stack size: ", bone_stack.size()); + + for (std::pair kvp : bone_stack) { + aiBone *bone = kvp.first; + aiNode *bone_node = kvp.second; + ASSIMP_LOG_DEBUG_F("active node lookup: ", bone->mName.C_Str()); + // lcl transform grab - done in generate_nodes :) + + // bone->mOffsetMatrix = bone_node->mTransformation; + aiNode *armature = GetArmatureRoot(bone_node, bones); + + ai_assert(armature); + + // set up bone armature id + bone->mArmature = armature; + + // set this bone node to be referenced properly + ai_assert(bone_node); + bone->mNode = bone_node; + } +} + + +/* Reprocess all nodes to calculate bone transforms properly based on the REAL + * mOffsetMatrix not the local. */ +/* Before this would use mesh transforms which is wrong for bone transforms */ +/* Before this would work for simple character skeletons but not complex meshes + * with multiple origins */ +/* Source: sketch fab log cutter fbx */ +void ArmaturePopulate::BuildBoneList(aiNode *current_node, + const aiNode *root_node, + const aiScene *scene, + std::vector &bones) { + ai_assert(scene); + for (unsigned int nodeId = 0; nodeId < current_node->mNumChildren; ++nodeId) { + aiNode *child = current_node->mChildren[nodeId]; + ai_assert(child); + + // check for bones + for (unsigned int meshId = 0; meshId < child->mNumMeshes; ++meshId) { + ai_assert(child->mMeshes); + unsigned int mesh_index = child->mMeshes[meshId]; + aiMesh *mesh = scene->mMeshes[mesh_index]; + ai_assert(mesh); + + for (unsigned int boneId = 0; boneId < mesh->mNumBones; ++boneId) { + aiBone *bone = mesh->mBones[boneId]; + ai_assert(bone); + + // duplicate meshes exist with the same bones sometimes :) + // so this must be detected + if (std::find(bones.begin(), bones.end(), bone) == bones.end()) { + // add the element once + bones.push_back(bone); + } + } + + // find mesh and get bones + // then do recursive lookup for bones in root node hierarchy + } + + BuildBoneList(child, root_node, scene, bones); + } +} + +/* Prepare flat node list which can be used for non recursive lookups later */ +void ArmaturePopulate::BuildNodeList(const aiNode *current_node, + std::vector &nodes) { + ai_assert(current_node); + + for (unsigned int nodeId = 0; nodeId < current_node->mNumChildren; ++nodeId) { + aiNode *child = current_node->mChildren[nodeId]; + ai_assert(child); + + nodes.push_back(child); + + BuildNodeList(child, nodes); + } +} + +/* A bone stack allows us to have multiple armatures, with the same bone names + * A bone stack allows us also to retrieve bones true transform even with + * duplicate names :) + */ +void ArmaturePopulate::BuildBoneStack(aiNode *current_node, + const aiNode *root_node, + const aiScene *scene, + const std::vector &bones, + std::map &bone_stack, + std::vector &node_stack) { + ai_assert(scene); + ai_assert(root_node); + ai_assert(!node_stack.empty()); + + for (aiBone *bone : bones) { + ai_assert(bone); + aiNode *node = GetNodeFromStack(bone->mName, node_stack); + if (node == nullptr) { + node_stack.clear(); + BuildNodeList(root_node, node_stack); + ASSIMP_LOG_DEBUG_F("Resetting bone stack: nullptr element ", bone->mName.C_Str()); + + node = GetNodeFromStack(bone->mName, node_stack); + + if (!node) { + ASSIMP_LOG_ERROR("serious import issue node for bone was not detected"); + continue; + } + } + + ASSIMP_LOG_DEBUG_F("Successfully added bone[", bone->mName.C_Str(), "] to stack and bone node is: ", node->mName.C_Str()); + + bone_stack.insert(std::pair(bone, node)); + } +} + + +/* Returns the armature root node */ +/* This is required to be detected for a bone initially, it will recurse up + * until it cannot find another bone and return the node No known failure + * points. (yet) + */ +aiNode *ArmaturePopulate::GetArmatureRoot(aiNode *bone_node, + std::vector &bone_list) { + while (bone_node) { + if (!IsBoneNode(bone_node->mName, bone_list)) { + ASSIMP_LOG_DEBUG_F("GetArmatureRoot() Found valid armature: ", bone_node->mName.C_Str()); + return bone_node; + } + + bone_node = bone_node->mParent; + } + + ASSIMP_LOG_ERROR("GetArmatureRoot() can't find armature!"); + + return nullptr; +} + + + +/* Simple IsBoneNode check if this could be a bone */ +bool ArmaturePopulate::IsBoneNode(const aiString &bone_name, + std::vector &bones) { + for (aiBone *bone : bones) { + if (bone->mName == bone_name) { + return true; + } + } + + return false; +} + +/* Pop this node by name from the stack if found */ +/* Used in multiple armature situations with duplicate node / bone names */ +/* Known flaw: cannot have nodes with bone names, will be fixed in later release + */ +/* (serious to be fixed) Known flaw: nodes which have more than one bone could + * be prematurely dropped from stack */ +aiNode *ArmaturePopulate::GetNodeFromStack(const aiString &node_name, + std::vector &nodes) { + std::vector::iterator iter; + aiNode *found = nullptr; + for (iter = nodes.begin(); iter < nodes.end(); ++iter) { + aiNode *element = *iter; + ai_assert(element); + // node valid and node name matches + if (element->mName == node_name) { + found = element; + break; + } + } + + if (found != nullptr) { + ASSIMP_LOG_INFO_F("Removed node from stack: ", found->mName.C_Str()); + // now pop the element from the node list + nodes.erase(iter); + + return found; + } + + // unique names can cause this problem + ASSIMP_LOG_ERROR("[Serious] GetNodeFromStack() can't find node from stack!"); + + return nullptr; +} + + + + +} // Namespace Assimp diff --git a/code/PostProcessing/ArmaturePopulate.h b/code/PostProcessing/ArmaturePopulate.h new file mode 100644 index 000000000..aa1ad7c80 --- /dev/null +++ b/code/PostProcessing/ArmaturePopulate.h @@ -0,0 +1,112 @@ +/* +Open Asset Import Library (assimp) +---------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the +following conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +---------------------------------------------------------------------- +*/ +#ifndef ARMATURE_POPULATE_H_ +#define ARMATURE_POPULATE_H_ + +#include "Common/BaseProcess.h" +#include +#include +#include + + +struct aiNode; +struct aiBone; + +namespace Assimp { + +// --------------------------------------------------------------------------- +/** Armature Populate: This is a post process designed + * To save you time when importing models into your game engines + * This was originally designed only for fbx but will work with other formats + * it is intended to auto populate aiBone data with armature and the aiNode + * This is very useful when dealing with skinned meshes + * or when dealing with many different skeletons + * It's off by default but recommend that you try it and use it + * It should reduce down any glue code you have in your + * importers + * You can contact RevoluPowered + * For more info about this +*/ +class ASSIMP_API ArmaturePopulate : public BaseProcess { +public: + /// The default class constructor. + ArmaturePopulate(); + + /// The class destructor. + virtual ~ArmaturePopulate(); + + /// Overwritten, @see BaseProcess + virtual bool IsActive( unsigned int pFlags ) const; + + /// Overwritten, @see BaseProcess + virtual void SetupProperties( const Importer* pImp ); + + /// Overwritten, @see BaseProcess + virtual void Execute( aiScene* pScene ); + + static aiNode *GetArmatureRoot(aiNode *bone_node, + std::vector &bone_list); + + static bool IsBoneNode(const aiString &bone_name, + std::vector &bones); + + static aiNode *GetNodeFromStack(const aiString &node_name, + std::vector &nodes); + + static void BuildNodeList(const aiNode *current_node, + std::vector &nodes); + + static void BuildBoneList(aiNode *current_node, const aiNode *root_node, + const aiScene *scene, + std::vector &bones); + + static void BuildBoneStack(aiNode *current_node, const aiNode *root_node, + const aiScene *scene, + const std::vector &bones, + std::map &bone_stack, + std::vector &node_stack); +}; + +} // Namespace Assimp + + +#endif // SCALE_PROCESS_H_ \ No newline at end of file diff --git a/code/PostProcessing/ValidateDataStructure.cpp b/code/PostProcessing/ValidateDataStructure.cpp index 75d1b6ef7..1dc217663 100644 --- a/code/PostProcessing/ValidateDataStructure.cpp +++ b/code/PostProcessing/ValidateDataStructure.cpp @@ -603,15 +603,18 @@ void ValidateDSProcess::SearchForInvalidTextures(const aiMaterial* pMaterial, ReportError("%s #%i is set, but there are only %i %s textures", szType,iIndex,iNumIndices,szType); } - if (!iNumIndices)return; + if (!iNumIndices) { + return; + } std::vector mappings(iNumIndices); // Now check whether all UV indices are valid ... bool bNoSpecified = true; - for (unsigned int i = 0; i < pMaterial->mNumProperties;++i) - { + for (unsigned int i = 0; i < pMaterial->mNumProperties;++i) { aiMaterialProperty* prop = pMaterial->mProperties[i]; - if (prop->mSemantic != type)continue; + if (prop->mSemantic != type) { + continue; + } if ((int)prop->mIndex >= iNumIndices) { @@ -634,7 +637,7 @@ void ValidateDSProcess::SearchForInvalidTextures(const aiMaterial* pMaterial, ReportError("Material property %s%i is expected to be 5 floats large (size is %i)", prop->mKey.data,prop->mIndex, prop->mDataLength); } - mappings[prop->mIndex] = *((aiTextureMapping*)prop->mData); + //mappings[prop->mIndex] = ((aiUVTransform*)prop->mData); } else if (!::strcmp(prop->mKey.data,"$tex.uvwsrc")) { if (aiPTI_Integer != prop->mType || sizeof(int) > prop->mDataLength) diff --git a/code/glTF/glTFAsset.inl b/code/glTF/glTFAsset.inl index f31781a3f..25cf1873c 100644 --- a/code/glTF/glTFAsset.inl +++ b/code/glTF/glTFAsset.inl @@ -1427,9 +1427,6 @@ inline void Asset::ReadExtensionsUsed(Document& doc) } } - #define CHECK_EXT(EXT) \ - if (exts.find(#EXT) != exts.end()) extensionsUsed.EXT = true; - CHECK_EXT(KHR_binary_glTF); CHECK_EXT(KHR_materials_common); diff --git a/code/glTF/glTFCommon.h b/code/glTF/glTFCommon.h index d9edee75e..b2e28d580 100644 --- a/code/glTF/glTFCommon.h +++ b/code/glTF/glTFCommon.h @@ -188,7 +188,7 @@ namespace glTFCommon { size_t DecodeBase64(const char* in, size_t inLength, uint8_t*& out); inline - size_t DecodeBase64(const char* in, uint8_t*& out) { + size_t DecodeBase64(const char* in, uint8_t*& out) { return DecodeBase64(in, strlen(in), out); } @@ -221,25 +221,22 @@ namespace glTFCommon { }; inline - char EncodeCharBase64(uint8_t b) { + char EncodeCharBase64(uint8_t b) { return "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="[size_t(b)]; } inline - uint8_t DecodeCharBase64(char c) { + uint8_t DecodeCharBase64(char c) { return DATA::tableDecodeBase64[size_t(c)]; // TODO faster with lookup table or ifs? - /*if (c >= 'A' && c <= 'Z') return c - 'A'; - if (c >= 'a' && c <= 'z') return c - 'a' + 26; - if (c >= '0' && c <= '9') return c - '0' + 52; - if (c == '+') return 62; - if (c == '/') return 63; - return 64; // '-' */ } size_t DecodeBase64(const char* in, size_t inLength, uint8_t*& out); void EncodeBase64(const uint8_t* in, size_t inLength, std::string& out); - } + } // namespace Util + +#define CHECK_EXT(EXT) \ + if (exts.find(#EXT) != exts.end()) extensionsUsed.EXT = true; } diff --git a/code/glTF2/glTF2Asset.h b/code/glTF2/glTF2Asset.h index 15c4c44fa..831a763cd 100644 --- a/code/glTF2/glTF2Asset.h +++ b/code/glTF2/glTF2Asset.h @@ -685,6 +685,13 @@ namespace glTF2 Ref texture; unsigned int index; unsigned int texCoord = 0; + + bool textureTransformSupported = false; + struct TextureTransformExt { + float offset[2]; + float rotation; + float scale[2]; + } TextureTransformExt_t; }; struct NormalTextureInfo : TextureInfo @@ -1024,7 +1031,7 @@ namespace glTF2 bool KHR_materials_pbrSpecularGlossiness; bool KHR_materials_unlit; bool KHR_lights_punctual; - + bool KHR_texture_transform; } extensionsUsed; AssetMetadata asset; diff --git a/code/glTF2/glTF2Asset.inl b/code/glTF2/glTF2Asset.inl index 6b47b1607..4259022e9 100644 --- a/code/glTF2/glTF2Asset.inl +++ b/code/glTF2/glTF2Asset.inl @@ -800,8 +800,34 @@ inline void Texture::Read(Value& obj, Asset& r) } namespace { - inline void SetTextureProperties(Asset& r, Value* prop, TextureInfo& out) - { + inline void SetTextureProperties(Asset& r, Value* prop, TextureInfo& out) { + if (r.extensionsUsed.KHR_texture_transform) { + if (Value *extensions = FindObject(*prop, "extensions")) { + out.textureTransformSupported = true; + if (Value *pKHR_texture_transform = FindObject(*extensions, "KHR_texture_transform")) { + if (Value *array = FindArray(*pKHR_texture_transform, "offset")) { + out.TextureTransformExt_t.offset[0] = (*array)[0].GetFloat(); + out.TextureTransformExt_t.offset[1] = (*array)[1].GetFloat(); + } else { + out.TextureTransformExt_t.offset[0] = 0; + out.TextureTransformExt_t.offset[1] = 0; + } + + if (!ReadMember(*pKHR_texture_transform, "rotation", out.TextureTransformExt_t.rotation)) { + out.TextureTransformExt_t.rotation = 0; + } + + if (Value *array = FindArray(*pKHR_texture_transform, "scale")) { + out.TextureTransformExt_t.scale[0] = (*array)[0].GetFloat(); + out.TextureTransformExt_t.scale[1] = (*array)[1].GetFloat(); + } else { + out.TextureTransformExt_t.scale[0] = 1; + out.TextureTransformExt_t.scale[1] = 1; + } + } + } + } + if (Value* index = FindUInt(*prop, "index")) { out.texture = r.textures.Retrieve(index->GetUint()); } @@ -877,6 +903,9 @@ inline void Material::Read(Value& material, Asset& r) } } + if (r.extensionsUsed.KHR_texture_transform) { + } + unlit = nullptr != FindObject(*extensions, "KHR_materials_unlit"); } } @@ -1463,12 +1492,10 @@ inline void Asset::ReadExtensionsUsed(Document& doc) } } - #define CHECK_EXT(EXT) \ - if (exts.find(#EXT) != exts.end()) extensionsUsed.EXT = true; - CHECK_EXT(KHR_materials_pbrSpecularGlossiness); CHECK_EXT(KHR_materials_unlit); CHECK_EXT(KHR_lights_punctual); + CHECK_EXT(KHR_texture_transform); #undef CHECK_EXT } diff --git a/code/glTF2/glTF2AssetWriter.inl b/code/glTF2/glTF2AssetWriter.inl index 5d1b22064..44a1d3e73 100644 --- a/code/glTF2/glTF2AssetWriter.inl +++ b/code/glTF2/glTF2AssetWriter.inl @@ -358,7 +358,7 @@ namespace glTF2 { WriteVec(pbrSpecularGlossiness, pbrSG.specularFactor, "specularFactor", defaultSpecularFactor, w.mAl); if (pbrSG.glossinessFactor != 1) { - WriteFloat(obj, pbrSG.glossinessFactor, "glossinessFactor", w.mAl); + WriteFloat(pbrSpecularGlossiness, pbrSG.glossinessFactor, "glossinessFactor", w.mAl); } WriteTex(pbrSpecularGlossiness, pbrSG.diffuseTexture, "diffuseTexture", w.mAl); diff --git a/code/glTF2/glTF2Exporter.cpp b/code/glTF2/glTF2Exporter.cpp index 4724c2ef4..dc0b76443 100644 --- a/code/glTF2/glTF2Exporter.cpp +++ b/code/glTF2/glTF2Exporter.cpp @@ -320,7 +320,9 @@ void glTF2Exporter::GetMatTex(const aiMaterial* mat, Ref& texture, aiTe if (path[0] == '*') { // embedded aiTexture* tex = mScene->mTextures[atoi(&path[1])]; - uint8_t* data = reinterpret_cast(tex->pcData); + // copy data since lifetime control is handed over to the asset + uint8_t* data = new uint8_t[tex->mWidth]; + memcpy(data, tex->pcData, tex->mWidth); texture->source->SetData(data, tex->mWidth, *mAsset); if (tex->achFormatHint[0]) { diff --git a/code/glTF2/glTF2Exporter.h b/code/glTF2/glTF2Exporter.h index 2dc083709..b527c4bc9 100644 --- a/code/glTF2/glTF2Exporter.h +++ b/code/glTF2/glTF2Exporter.h @@ -74,6 +74,7 @@ namespace glTF2 struct Texture; // Vec/matrix types, as raw float arrays + typedef float (vec2)[2]; typedef float (vec3)[3]; typedef float (vec4)[4]; } diff --git a/code/glTF2/glTF2Importer.cpp b/code/glTF2/glTF2Importer.cpp index b3141fd96..43eabdab7 100644 --- a/code/glTF2/glTF2Importer.cpp +++ b/code/glTF2/glTF2Importer.cpp @@ -43,18 +43,18 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #ifndef ASSIMP_BUILD_NO_GLTF_IMPORTER #include "glTF2/glTF2Importer.h" +#include "PostProcessing/MakeVerboseFormat.h" #include "glTF2/glTF2Asset.h" #include "glTF2/glTF2AssetWriter.h" -#include "PostProcessing/MakeVerboseFormat.h" +#include #include #include -#include -#include #include -#include #include -#include +#include +#include +#include #include #include @@ -67,11 +67,11 @@ using namespace glTF2; using namespace glTFCommon; namespace { - // generate bi-tangents from normals and tangents according to spec - struct Tangent { - aiVector3D xyz; - ai_real w; - }; +// generate bi-tangents from normals and tangents according to spec +struct Tangent { + aiVector3D xyz; + ai_real w; +}; } // namespace // @@ -79,66 +79,63 @@ namespace { // static const aiImporterDesc desc = { - "glTF2 Importer", - "", - "", - "", - aiImporterFlags_SupportTextFlavour | aiImporterFlags_SupportBinaryFlavour | aiImporterFlags_LimitedSupport | aiImporterFlags_Experimental, - 0, - 0, - 0, - 0, - "gltf glb" + "glTF2 Importer", + "", + "", + "", + aiImporterFlags_SupportTextFlavour | aiImporterFlags_SupportBinaryFlavour | aiImporterFlags_LimitedSupport | aiImporterFlags_Experimental, + 0, + 0, + 0, + 0, + "gltf glb" }; -glTF2Importer::glTF2Importer() -: BaseImporter() -, meshOffsets() -, embeddedTexIdxs() -, mScene( NULL ) { - // empty +glTF2Importer::glTF2Importer() : + BaseImporter(), + meshOffsets(), + embeddedTexIdxs(), + mScene(NULL) { + // empty } glTF2Importer::~glTF2Importer() { - // empty + // empty } -const aiImporterDesc* glTF2Importer::GetInfo() const -{ - return &desc; +const aiImporterDesc *glTF2Importer::GetInfo() const { + return &desc; } -bool glTF2Importer::CanRead(const std::string& pFile, IOSystem* pIOHandler, bool /* checkSig */) const -{ - const std::string &extension = GetExtension(pFile); +bool glTF2Importer::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool /* checkSig */) const { + const std::string &extension = GetExtension(pFile); - if (extension != "gltf" && extension != "glb") - return false; + if (extension != "gltf" && extension != "glb") + return false; - if (pIOHandler) { - glTF2::Asset asset(pIOHandler); - asset.Load(pFile, extension == "glb"); - std::string version = asset.asset.version; - return !version.empty() && version[0] == '2'; - } + if (pIOHandler) { + glTF2::Asset asset(pIOHandler); + asset.Load(pFile, extension == "glb"); + std::string version = asset.asset.version; + return !version.empty() && version[0] == '2'; + } - return false; + return false; } -static aiTextureMapMode ConvertWrappingMode(SamplerWrap gltfWrapMode) -{ - switch (gltfWrapMode) { - case SamplerWrap::Mirrored_Repeat: - return aiTextureMapMode_Mirror; +static aiTextureMapMode ConvertWrappingMode(SamplerWrap gltfWrapMode) { + switch (gltfWrapMode) { + case SamplerWrap::Mirrored_Repeat: + return aiTextureMapMode_Mirror; - case SamplerWrap::Clamp_To_Edge: - return aiTextureMapMode_Clamp; + case SamplerWrap::Clamp_To_Edge: + return aiTextureMapMode_Clamp; - case SamplerWrap::UNSET: - case SamplerWrap::Repeat: - default: - return aiTextureMapMode_Wrap; - } + case SamplerWrap::UNSET: + case SamplerWrap::Repeat: + default: + return aiTextureMapMode_Wrap; + } } /*static void CopyValue(const glTF2::vec3& v, aiColor3D& out) @@ -180,1182 +177,1150 @@ static void CopyValue(const glTF2::vec4& v, aiQuaternion& out) o.a4 = v[12]; o.b4 = v[13]; o.c4 = v[14]; o.d4 = v[15]; }*/ -inline void SetMaterialColorProperty(Asset& /*r*/, vec4& prop, aiMaterial* mat, const char* pKey, unsigned int type, unsigned int idx) -{ - aiColor4D col; - CopyValue(prop, col); - mat->AddProperty(&col, 1, pKey, type, idx); +inline void SetMaterialColorProperty(Asset & /*r*/, vec4 &prop, aiMaterial *mat, const char *pKey, unsigned int type, unsigned int idx) { + aiColor4D col; + CopyValue(prop, col); + mat->AddProperty(&col, 1, pKey, type, idx); } -inline void SetMaterialColorProperty(Asset& /*r*/, vec3& prop, aiMaterial* mat, const char* pKey, unsigned int type, unsigned int idx) -{ - aiColor4D col; - glTFCommon::CopyValue(prop, col); - mat->AddProperty(&col, 1, pKey, type, idx); +inline void SetMaterialColorProperty(Asset & /*r*/, vec3 &prop, aiMaterial *mat, const char *pKey, unsigned int type, unsigned int idx) { + aiColor4D col; + glTFCommon::CopyValue(prop, col); + mat->AddProperty(&col, 1, pKey, type, idx); } -inline void SetMaterialTextureProperty(std::vector& embeddedTexIdxs, Asset& /*r*/, glTF2::TextureInfo prop, aiMaterial* mat, aiTextureType texType, unsigned int texSlot = 0) -{ - if (prop.texture && prop.texture->source) { - aiString uri(prop.texture->source->uri); +inline void SetMaterialTextureProperty(std::vector &embeddedTexIdxs, Asset & /*r*/, glTF2::TextureInfo prop, aiMaterial *mat, aiTextureType texType, unsigned int texSlot = 0) { + if (prop.texture && prop.texture->source) { + aiString uri(prop.texture->source->uri); - int texIdx = embeddedTexIdxs[prop.texture->source.GetIndex()]; - if (texIdx != -1) { // embedded - // setup texture reference string (copied from ColladaLoader::FindFilenameForEffectTexture) - uri.data[0] = '*'; - uri.length = 1 + ASSIMP_itoa10(uri.data + 1, MAXLEN - 1, texIdx); - } + int texIdx = embeddedTexIdxs[prop.texture->source.GetIndex()]; + if (texIdx != -1) { // embedded + // setup texture reference string (copied from ColladaLoader::FindFilenameForEffectTexture) + uri.data[0] = '*'; + uri.length = 1 + ASSIMP_itoa10(uri.data + 1, MAXLEN - 1, texIdx); + } mat->AddProperty(&uri, AI_MATKEY_TEXTURE(texType, texSlot)); - mat->AddProperty(&prop.texCoord, 1, _AI_MATKEY_GLTF_TEXTURE_TEXCOORD_BASE, texType, texSlot); - if (prop.texture->sampler) { - Ref sampler = prop.texture->sampler; + if (prop.textureTransformSupported) { + aiUVTransform transform; + transform.mTranslation.x = prop.TextureTransformExt_t.offset[0]; + transform.mTranslation.y = prop.TextureTransformExt_t.offset[0]; + transform.mRotation = prop.TextureTransformExt_t.rotation; + transform.mScaling.x = prop.TextureTransformExt_t.scale[0]; + transform.mScaling.y = prop.TextureTransformExt_t.scale[1]; + mat->AddProperty(&transform, 1, _AI_MATKEY_UVTRANSFORM_BASE, texType, texSlot); + } - aiString name(sampler->name); - aiString id(sampler->id); + if (prop.texture->sampler) { + Ref sampler = prop.texture->sampler; - mat->AddProperty(&name, AI_MATKEY_GLTF_MAPPINGNAME(texType, texSlot)); - mat->AddProperty(&id, AI_MATKEY_GLTF_MAPPINGID(texType, texSlot)); + aiString name(sampler->name); + aiString id(sampler->id); - aiTextureMapMode wrapS = ConvertWrappingMode(sampler->wrapS); - aiTextureMapMode wrapT = ConvertWrappingMode(sampler->wrapT); - mat->AddProperty(&wrapS, 1, AI_MATKEY_MAPPINGMODE_U(texType, texSlot)); - mat->AddProperty(&wrapT, 1, AI_MATKEY_MAPPINGMODE_V(texType, texSlot)); + mat->AddProperty(&name, AI_MATKEY_GLTF_MAPPINGNAME(texType, texSlot)); + mat->AddProperty(&id, AI_MATKEY_GLTF_MAPPINGID(texType, texSlot)); - if (sampler->magFilter != SamplerMagFilter::UNSET) { - mat->AddProperty(&sampler->magFilter, 1, AI_MATKEY_GLTF_MAPPINGFILTER_MAG(texType, texSlot)); - } + aiTextureMapMode wrapS = ConvertWrappingMode(sampler->wrapS); + aiTextureMapMode wrapT = ConvertWrappingMode(sampler->wrapT); + mat->AddProperty(&wrapS, 1, AI_MATKEY_MAPPINGMODE_U(texType, texSlot)); + mat->AddProperty(&wrapT, 1, AI_MATKEY_MAPPINGMODE_V(texType, texSlot)); - if (sampler->minFilter != SamplerMinFilter::UNSET) { - mat->AddProperty(&sampler->minFilter, 1, AI_MATKEY_GLTF_MAPPINGFILTER_MIN(texType, texSlot)); - } - } - } + if (sampler->magFilter != SamplerMagFilter::UNSET) { + mat->AddProperty(&sampler->magFilter, 1, AI_MATKEY_GLTF_MAPPINGFILTER_MAG(texType, texSlot)); + } + + if (sampler->minFilter != SamplerMinFilter::UNSET) { + mat->AddProperty(&sampler->minFilter, 1, AI_MATKEY_GLTF_MAPPINGFILTER_MIN(texType, texSlot)); + } + } + } } -inline void SetMaterialTextureProperty(std::vector& embeddedTexIdxs, Asset& r, glTF2::NormalTextureInfo& prop, aiMaterial* mat, aiTextureType texType, unsigned int texSlot = 0) -{ - SetMaterialTextureProperty( embeddedTexIdxs, r, (glTF2::TextureInfo) prop, mat, texType, texSlot ); +inline void SetMaterialTextureProperty(std::vector &embeddedTexIdxs, Asset &r, glTF2::NormalTextureInfo &prop, aiMaterial *mat, aiTextureType texType, unsigned int texSlot = 0) { + SetMaterialTextureProperty(embeddedTexIdxs, r, (glTF2::TextureInfo)prop, mat, texType, texSlot); - if (prop.texture && prop.texture->source) { - mat->AddProperty(&prop.scale, 1, AI_MATKEY_GLTF_TEXTURE_SCALE(texType, texSlot)); - } + if (prop.texture && prop.texture->source) { + mat->AddProperty(&prop.scale, 1, AI_MATKEY_GLTF_TEXTURE_SCALE(texType, texSlot)); + } } -inline void SetMaterialTextureProperty(std::vector& embeddedTexIdxs, Asset& r, glTF2::OcclusionTextureInfo& prop, aiMaterial* mat, aiTextureType texType, unsigned int texSlot = 0) -{ - SetMaterialTextureProperty( embeddedTexIdxs, r, (glTF2::TextureInfo) prop, mat, texType, texSlot ); +inline void SetMaterialTextureProperty(std::vector &embeddedTexIdxs, Asset &r, glTF2::OcclusionTextureInfo &prop, aiMaterial *mat, aiTextureType texType, unsigned int texSlot = 0) { + SetMaterialTextureProperty(embeddedTexIdxs, r, (glTF2::TextureInfo)prop, mat, texType, texSlot); - if (prop.texture && prop.texture->source) { - mat->AddProperty(&prop.strength, 1, AI_MATKEY_GLTF_TEXTURE_STRENGTH(texType, texSlot)); - } + if (prop.texture && prop.texture->source) { + mat->AddProperty(&prop.strength, 1, AI_MATKEY_GLTF_TEXTURE_STRENGTH(texType, texSlot)); + } } -static aiMaterial* ImportMaterial(std::vector& embeddedTexIdxs, Asset& r, Material& mat) -{ - aiMaterial* aimat = new aiMaterial(); +static aiMaterial *ImportMaterial(std::vector &embeddedTexIdxs, Asset &r, Material &mat) { + aiMaterial *aimat = new aiMaterial(); - if (!mat.name.empty()) { - aiString str(mat.name); + if (!mat.name.empty()) { + aiString str(mat.name); - aimat->AddProperty(&str, AI_MATKEY_NAME); - } + aimat->AddProperty(&str, AI_MATKEY_NAME); + } - SetMaterialColorProperty(r, mat.pbrMetallicRoughness.baseColorFactor, aimat, AI_MATKEY_COLOR_DIFFUSE); - SetMaterialColorProperty(r, mat.pbrMetallicRoughness.baseColorFactor, aimat, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_FACTOR); + SetMaterialColorProperty(r, mat.pbrMetallicRoughness.baseColorFactor, aimat, AI_MATKEY_COLOR_DIFFUSE); + SetMaterialColorProperty(r, mat.pbrMetallicRoughness.baseColorFactor, aimat, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_FACTOR); - SetMaterialTextureProperty(embeddedTexIdxs, r, mat.pbrMetallicRoughness.baseColorTexture, aimat, aiTextureType_DIFFUSE); - SetMaterialTextureProperty(embeddedTexIdxs, r, mat.pbrMetallicRoughness.baseColorTexture, aimat, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_TEXTURE); + SetMaterialTextureProperty(embeddedTexIdxs, r, mat.pbrMetallicRoughness.baseColorTexture, aimat, aiTextureType_DIFFUSE); + SetMaterialTextureProperty(embeddedTexIdxs, r, mat.pbrMetallicRoughness.baseColorTexture, aimat, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_TEXTURE); - SetMaterialTextureProperty(embeddedTexIdxs, r, mat.pbrMetallicRoughness.metallicRoughnessTexture, aimat, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE); + SetMaterialTextureProperty(embeddedTexIdxs, r, mat.pbrMetallicRoughness.metallicRoughnessTexture, aimat, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE); - aimat->AddProperty(&mat.pbrMetallicRoughness.metallicFactor, 1, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR); - aimat->AddProperty(&mat.pbrMetallicRoughness.roughnessFactor, 1, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR); + aimat->AddProperty(&mat.pbrMetallicRoughness.metallicFactor, 1, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR); + aimat->AddProperty(&mat.pbrMetallicRoughness.roughnessFactor, 1, AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR); - float roughnessAsShininess = 1 - mat.pbrMetallicRoughness.roughnessFactor; - roughnessAsShininess *= roughnessAsShininess * 1000; - aimat->AddProperty(&roughnessAsShininess, 1, AI_MATKEY_SHININESS); + float roughnessAsShininess = 1 - mat.pbrMetallicRoughness.roughnessFactor; + roughnessAsShininess *= roughnessAsShininess * 1000; + aimat->AddProperty(&roughnessAsShininess, 1, AI_MATKEY_SHININESS); - SetMaterialTextureProperty(embeddedTexIdxs, r, mat.normalTexture, aimat, aiTextureType_NORMALS); - SetMaterialTextureProperty(embeddedTexIdxs, r, mat.occlusionTexture, aimat, aiTextureType_LIGHTMAP); - SetMaterialTextureProperty(embeddedTexIdxs, r, mat.emissiveTexture, aimat, aiTextureType_EMISSIVE); - SetMaterialColorProperty(r, mat.emissiveFactor, aimat, AI_MATKEY_COLOR_EMISSIVE); + SetMaterialTextureProperty(embeddedTexIdxs, r, mat.normalTexture, aimat, aiTextureType_NORMALS); + SetMaterialTextureProperty(embeddedTexIdxs, r, mat.occlusionTexture, aimat, aiTextureType_LIGHTMAP); + SetMaterialTextureProperty(embeddedTexIdxs, r, mat.emissiveTexture, aimat, aiTextureType_EMISSIVE); + SetMaterialColorProperty(r, mat.emissiveFactor, aimat, AI_MATKEY_COLOR_EMISSIVE); - aimat->AddProperty(&mat.doubleSided, 1, AI_MATKEY_TWOSIDED); + aimat->AddProperty(&mat.doubleSided, 1, AI_MATKEY_TWOSIDED); - aiString alphaMode(mat.alphaMode); - aimat->AddProperty(&alphaMode, AI_MATKEY_GLTF_ALPHAMODE); - aimat->AddProperty(&mat.alphaCutoff, 1, AI_MATKEY_GLTF_ALPHACUTOFF); + aiString alphaMode(mat.alphaMode); + aimat->AddProperty(&alphaMode, AI_MATKEY_GLTF_ALPHAMODE); + aimat->AddProperty(&mat.alphaCutoff, 1, AI_MATKEY_GLTF_ALPHACUTOFF); - //pbrSpecularGlossiness - if (mat.pbrSpecularGlossiness.isPresent) { - PbrSpecularGlossiness &pbrSG = mat.pbrSpecularGlossiness.value; + //pbrSpecularGlossiness + if (mat.pbrSpecularGlossiness.isPresent) { + PbrSpecularGlossiness &pbrSG = mat.pbrSpecularGlossiness.value; - aimat->AddProperty(&mat.pbrSpecularGlossiness.isPresent, 1, AI_MATKEY_GLTF_PBRSPECULARGLOSSINESS); - SetMaterialColorProperty(r, pbrSG.diffuseFactor, aimat, AI_MATKEY_COLOR_DIFFUSE); - SetMaterialColorProperty(r, pbrSG.specularFactor, aimat, AI_MATKEY_COLOR_SPECULAR); + aimat->AddProperty(&mat.pbrSpecularGlossiness.isPresent, 1, AI_MATKEY_GLTF_PBRSPECULARGLOSSINESS); + SetMaterialColorProperty(r, pbrSG.diffuseFactor, aimat, AI_MATKEY_COLOR_DIFFUSE); + SetMaterialColorProperty(r, pbrSG.specularFactor, aimat, AI_MATKEY_COLOR_SPECULAR); - float glossinessAsShininess = pbrSG.glossinessFactor * 1000.0f; - aimat->AddProperty(&glossinessAsShininess, 1, AI_MATKEY_SHININESS); - aimat->AddProperty(&pbrSG.glossinessFactor, 1, AI_MATKEY_GLTF_PBRSPECULARGLOSSINESS_GLOSSINESS_FACTOR); + float glossinessAsShininess = pbrSG.glossinessFactor * 1000.0f; + aimat->AddProperty(&glossinessAsShininess, 1, AI_MATKEY_SHININESS); + aimat->AddProperty(&pbrSG.glossinessFactor, 1, AI_MATKEY_GLTF_PBRSPECULARGLOSSINESS_GLOSSINESS_FACTOR); - SetMaterialTextureProperty(embeddedTexIdxs, r, pbrSG.diffuseTexture, aimat, aiTextureType_DIFFUSE); + SetMaterialTextureProperty(embeddedTexIdxs, r, pbrSG.diffuseTexture, aimat, aiTextureType_DIFFUSE); - SetMaterialTextureProperty(embeddedTexIdxs, r, pbrSG.specularGlossinessTexture, aimat, aiTextureType_SPECULAR); - } - if (mat.unlit) { - aimat->AddProperty(&mat.unlit, 1, AI_MATKEY_GLTF_UNLIT); - } + SetMaterialTextureProperty(embeddedTexIdxs, r, pbrSG.specularGlossinessTexture, aimat, aiTextureType_SPECULAR); + } + if (mat.unlit) { + aimat->AddProperty(&mat.unlit, 1, AI_MATKEY_GLTF_UNLIT); + } - return aimat; + return aimat; } -void glTF2Importer::ImportMaterials(glTF2::Asset& r) -{ - const unsigned int numImportedMaterials = unsigned(r.materials.Size()); - Material defaultMaterial; +void glTF2Importer::ImportMaterials(glTF2::Asset &r) { + const unsigned int numImportedMaterials = unsigned(r.materials.Size()); + Material defaultMaterial; - mScene->mNumMaterials = numImportedMaterials + 1; - mScene->mMaterials = new aiMaterial*[mScene->mNumMaterials]; - mScene->mMaterials[numImportedMaterials] = ImportMaterial(embeddedTexIdxs, r, defaultMaterial); + mScene->mNumMaterials = numImportedMaterials + 1; + mScene->mMaterials = new aiMaterial *[mScene->mNumMaterials]; + mScene->mMaterials[numImportedMaterials] = ImportMaterial(embeddedTexIdxs, r, defaultMaterial); - for (unsigned int i = 0; i < numImportedMaterials; ++i) { - mScene->mMaterials[i] = ImportMaterial(embeddedTexIdxs, r, r.materials[i]); - } + for (unsigned int i = 0; i < numImportedMaterials; ++i) { + mScene->mMaterials[i] = ImportMaterial(embeddedTexIdxs, r, r.materials[i]); + } } - -static inline void SetFace(aiFace& face, int a) -{ - face.mNumIndices = 1; - face.mIndices = new unsigned int[1]; - face.mIndices[0] = a; +static inline void SetFace(aiFace &face, int a) { + face.mNumIndices = 1; + face.mIndices = new unsigned int[1]; + face.mIndices[0] = a; } -static inline void SetFace(aiFace& face, int a, int b) -{ - face.mNumIndices = 2; - face.mIndices = new unsigned int[2]; - face.mIndices[0] = a; - face.mIndices[1] = b; +static inline void SetFace(aiFace &face, int a, int b) { + face.mNumIndices = 2; + face.mIndices = new unsigned int[2]; + face.mIndices[0] = a; + face.mIndices[1] = b; } -static inline void SetFace(aiFace& face, int a, int b, int c) -{ - face.mNumIndices = 3; - face.mIndices = new unsigned int[3]; - face.mIndices[0] = a; - face.mIndices[1] = b; - face.mIndices[2] = c; +static inline void SetFace(aiFace &face, int a, int b, int c) { + face.mNumIndices = 3; + face.mIndices = new unsigned int[3]; + face.mIndices[0] = a; + face.mIndices[1] = b; + face.mIndices[2] = c; } #ifdef ASSIMP_BUILD_DEBUG -static inline bool CheckValidFacesIndices(aiFace* faces, unsigned nFaces, unsigned nVerts) -{ - for (unsigned i = 0; i < nFaces; ++i) { - for (unsigned j = 0; j < faces[i].mNumIndices; ++j) { - unsigned idx = faces[i].mIndices[j]; - if (idx >= nVerts) - return false; - } - } - return true; +static inline bool CheckValidFacesIndices(aiFace *faces, unsigned nFaces, unsigned nVerts) { + for (unsigned i = 0; i < nFaces; ++i) { + for (unsigned j = 0; j < faces[i].mNumIndices; ++j) { + unsigned idx = faces[i].mIndices[j]; + if (idx >= nVerts) + return false; + } + } + return true; } #endif // ASSIMP_BUILD_DEBUG -void glTF2Importer::ImportMeshes(glTF2::Asset& r) -{ - std::vector meshes; +void glTF2Importer::ImportMeshes(glTF2::Asset &r) { + std::vector meshes; - unsigned int k = 0; + unsigned int k = 0; - for (unsigned int m = 0; m < r.meshes.Size(); ++m) { - Mesh& mesh = r.meshes[m]; + for (unsigned int m = 0; m < r.meshes.Size(); ++m) { + Mesh &mesh = r.meshes[m]; - meshOffsets.push_back(k); - k += unsigned(mesh.primitives.size()); + meshOffsets.push_back(k); + k += unsigned(mesh.primitives.size()); - for (unsigned int p = 0; p < mesh.primitives.size(); ++p) { - Mesh::Primitive& prim = mesh.primitives[p]; + for (unsigned int p = 0; p < mesh.primitives.size(); ++p) { + Mesh::Primitive &prim = mesh.primitives[p]; - aiMesh* aim = new aiMesh(); - meshes.push_back(aim); + aiMesh *aim = new aiMesh(); + meshes.push_back(aim); - aim->mName = mesh.name.empty() ? mesh.id : mesh.name; + aim->mName = mesh.name.empty() ? mesh.id : mesh.name; - if (mesh.primitives.size() > 1) { - ai_uint32& len = aim->mName.length; - aim->mName.data[len] = '-'; - len += 1 + ASSIMP_itoa10(aim->mName.data + len + 1, unsigned(MAXLEN - len - 1), p); - } + if (mesh.primitives.size() > 1) { + ai_uint32 &len = aim->mName.length; + aim->mName.data[len] = '-'; + len += 1 + ASSIMP_itoa10(aim->mName.data + len + 1, unsigned(MAXLEN - len - 1), p); + } - switch (prim.mode) { - case PrimitiveMode_POINTS: - aim->mPrimitiveTypes |= aiPrimitiveType_POINT; - break; + switch (prim.mode) { + case PrimitiveMode_POINTS: + aim->mPrimitiveTypes |= aiPrimitiveType_POINT; + break; - case PrimitiveMode_LINES: - case PrimitiveMode_LINE_LOOP: - case PrimitiveMode_LINE_STRIP: - aim->mPrimitiveTypes |= aiPrimitiveType_LINE; - break; + case PrimitiveMode_LINES: + case PrimitiveMode_LINE_LOOP: + case PrimitiveMode_LINE_STRIP: + aim->mPrimitiveTypes |= aiPrimitiveType_LINE; + break; - case PrimitiveMode_TRIANGLES: - case PrimitiveMode_TRIANGLE_STRIP: - case PrimitiveMode_TRIANGLE_FAN: - aim->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE; - break; + case PrimitiveMode_TRIANGLES: + case PrimitiveMode_TRIANGLE_STRIP: + case PrimitiveMode_TRIANGLE_FAN: + aim->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE; + break; + } - } + Mesh::Primitive::Attributes &attr = prim.attributes; - Mesh::Primitive::Attributes& attr = prim.attributes; + if (attr.position.size() > 0 && attr.position[0]) { + aim->mNumVertices = static_cast(attr.position[0]->count); + attr.position[0]->ExtractData(aim->mVertices); + } - if (attr.position.size() > 0 && attr.position[0]) { - aim->mNumVertices = static_cast(attr.position[0]->count); - attr.position[0]->ExtractData(aim->mVertices); - } + if (attr.normal.size() > 0 && attr.normal[0]) { + attr.normal[0]->ExtractData(aim->mNormals); - if (attr.normal.size() > 0 && attr.normal[0]) { - attr.normal[0]->ExtractData(aim->mNormals); + // only extract tangents if normals are present + if (attr.tangent.size() > 0 && attr.tangent[0]) { + // generate bitangents from normals and tangents according to spec + Tangent *tangents = nullptr; - // only extract tangents if normals are present - if (attr.tangent.size() > 0 && attr.tangent[0]) { - // generate bitangents from normals and tangents according to spec - Tangent *tangents = nullptr; + attr.tangent[0]->ExtractData(tangents); - attr.tangent[0]->ExtractData(tangents); + aim->mTangents = new aiVector3D[aim->mNumVertices]; + aim->mBitangents = new aiVector3D[aim->mNumVertices]; - aim->mTangents = new aiVector3D[aim->mNumVertices]; - aim->mBitangents = new aiVector3D[aim->mNumVertices]; + for (unsigned int i = 0; i < aim->mNumVertices; ++i) { + aim->mTangents[i] = tangents[i].xyz; + aim->mBitangents[i] = (aim->mNormals[i] ^ tangents[i].xyz) * tangents[i].w; + } - for (unsigned int i = 0; i < aim->mNumVertices; ++i) { - aim->mTangents[i] = tangents[i].xyz; - aim->mBitangents[i] = (aim->mNormals[i] ^ tangents[i].xyz) * tangents[i].w; - } + delete[] tangents; + } + } - delete [] tangents; - } - } + for (size_t c = 0; c < attr.color.size() && c < AI_MAX_NUMBER_OF_COLOR_SETS; ++c) { + if (attr.color[c]->count != aim->mNumVertices) { + DefaultLogger::get()->warn("Color stream size in mesh \"" + mesh.name + + "\" does not match the vertex count"); + continue; + } + attr.color[c]->ExtractData(aim->mColors[c]); + } + for (size_t tc = 0; tc < attr.texcoord.size() && tc < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++tc) { + if (attr.texcoord[tc]->count != aim->mNumVertices) { + DefaultLogger::get()->warn("Texcoord stream size in mesh \"" + mesh.name + + "\" does not match the vertex count"); + continue; + } - for (size_t c = 0; c < attr.color.size() && c < AI_MAX_NUMBER_OF_COLOR_SETS; ++c) { - if (attr.color[c]->count != aim->mNumVertices) { - DefaultLogger::get()->warn("Color stream size in mesh \"" + mesh.name + - "\" does not match the vertex count"); - continue; - } - attr.color[c]->ExtractData(aim->mColors[c]); - } - for (size_t tc = 0; tc < attr.texcoord.size() && tc < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++tc) { - if (attr.texcoord[tc]->count != aim->mNumVertices) { - DefaultLogger::get()->warn("Texcoord stream size in mesh \"" + mesh.name + - "\" does not match the vertex count"); - continue; - } + attr.texcoord[tc]->ExtractData(aim->mTextureCoords[tc]); + aim->mNumUVComponents[tc] = attr.texcoord[tc]->GetNumComponents(); - attr.texcoord[tc]->ExtractData(aim->mTextureCoords[tc]); - aim->mNumUVComponents[tc] = attr.texcoord[tc]->GetNumComponents(); + aiVector3D *values = aim->mTextureCoords[tc]; + for (unsigned int i = 0; i < aim->mNumVertices; ++i) { + values[i].y = 1 - values[i].y; // Flip Y coords + } + } - aiVector3D* values = aim->mTextureCoords[tc]; - for (unsigned int i = 0; i < aim->mNumVertices; ++i) { - values[i].y = 1 - values[i].y; // Flip Y coords - } - } + std::vector &targets = prim.targets; + if (targets.size() > 0) { + aim->mNumAnimMeshes = (unsigned int)targets.size(); + aim->mAnimMeshes = new aiAnimMesh *[aim->mNumAnimMeshes]; + for (size_t i = 0; i < targets.size(); i++) { + aim->mAnimMeshes[i] = aiCreateAnimMesh(aim); + aiAnimMesh &aiAnimMesh = *(aim->mAnimMeshes[i]); + Mesh::Primitive::Target &target = targets[i]; - std::vector& targets = prim.targets; - if (targets.size() > 0) { - aim->mNumAnimMeshes = (unsigned int)targets.size(); - aim->mAnimMeshes = new aiAnimMesh*[aim->mNumAnimMeshes]; - for (size_t i = 0; i < targets.size(); i++) { - aim->mAnimMeshes[i] = aiCreateAnimMesh(aim); - aiAnimMesh& aiAnimMesh = *(aim->mAnimMeshes[i]); - Mesh::Primitive::Target& target = targets[i]; + if (target.position.size() > 0) { + aiVector3D *positionDiff = nullptr; + target.position[0]->ExtractData(positionDiff); + for (unsigned int vertexId = 0; vertexId < aim->mNumVertices; vertexId++) { + aiAnimMesh.mVertices[vertexId] += positionDiff[vertexId]; + } + delete[] positionDiff; + } + if (target.normal.size() > 0) { + aiVector3D *normalDiff = nullptr; + target.normal[0]->ExtractData(normalDiff); + for (unsigned int vertexId = 0; vertexId < aim->mNumVertices; vertexId++) { + aiAnimMesh.mNormals[vertexId] += normalDiff[vertexId]; + } + delete[] normalDiff; + } + if (target.tangent.size() > 0) { + Tangent *tangent = nullptr; + attr.tangent[0]->ExtractData(tangent); - if (target.position.size() > 0) { - aiVector3D *positionDiff = nullptr; - target.position[0]->ExtractData(positionDiff); - for(unsigned int vertexId = 0; vertexId < aim->mNumVertices; vertexId++) { - aiAnimMesh.mVertices[vertexId] += positionDiff[vertexId]; - } - delete [] positionDiff; - } - if (target.normal.size() > 0) { - aiVector3D *normalDiff = nullptr; - target.normal[0]->ExtractData(normalDiff); - for(unsigned int vertexId = 0; vertexId < aim->mNumVertices; vertexId++) { - aiAnimMesh.mNormals[vertexId] += normalDiff[vertexId]; - } - delete [] normalDiff; - } - if (target.tangent.size() > 0) { - Tangent *tangent = nullptr; - attr.tangent[0]->ExtractData(tangent); + aiVector3D *tangentDiff = nullptr; + target.tangent[0]->ExtractData(tangentDiff); - aiVector3D *tangentDiff = nullptr; - target.tangent[0]->ExtractData(tangentDiff); + for (unsigned int vertexId = 0; vertexId < aim->mNumVertices; ++vertexId) { + tangent[vertexId].xyz += tangentDiff[vertexId]; + aiAnimMesh.mTangents[vertexId] = tangent[vertexId].xyz; + aiAnimMesh.mBitangents[vertexId] = (aiAnimMesh.mNormals[vertexId] ^ tangent[vertexId].xyz) * tangent[vertexId].w; + } + delete[] tangent; + delete[] tangentDiff; + } + if (mesh.weights.size() > i) { + aiAnimMesh.mWeight = mesh.weights[i]; + } + } + } - for (unsigned int vertexId = 0; vertexId < aim->mNumVertices; ++vertexId) { - tangent[vertexId].xyz += tangentDiff[vertexId]; - aiAnimMesh.mTangents[vertexId] = tangent[vertexId].xyz; - aiAnimMesh.mBitangents[vertexId] = (aiAnimMesh.mNormals[vertexId] ^ tangent[vertexId].xyz) * tangent[vertexId].w; - } - delete [] tangent; - delete [] tangentDiff; - } - if (mesh.weights.size() > i) { - aiAnimMesh.mWeight = mesh.weights[i]; - } - } - } + aiFace *faces = 0; + size_t nFaces = 0; + if (prim.indices) { + size_t count = prim.indices->count; - aiFace* faces = 0; - size_t nFaces = 0; + Accessor::Indexer data = prim.indices->GetIndexer(); + ai_assert(data.IsValid()); - if (prim.indices) { - size_t count = prim.indices->count; + switch (prim.mode) { + case PrimitiveMode_POINTS: { + nFaces = count; + faces = new aiFace[nFaces]; + for (unsigned int i = 0; i < count; ++i) { + SetFace(faces[i], data.GetUInt(i)); + } + break; + } - Accessor::Indexer data = prim.indices->GetIndexer(); - ai_assert(data.IsValid()); + case PrimitiveMode_LINES: { + nFaces = count / 2; + if (nFaces * 2 != count) { + ASSIMP_LOG_WARN("The number of vertices was not compatible with the LINES mode. Some vertices were dropped."); + count = nFaces * 2; + } + faces = new aiFace[nFaces]; + for (unsigned int i = 0; i < count; i += 2) { + SetFace(faces[i / 2], data.GetUInt(i), data.GetUInt(i + 1)); + } + break; + } - switch (prim.mode) { - case PrimitiveMode_POINTS: { - nFaces = count; - faces = new aiFace[nFaces]; - for (unsigned int i = 0; i < count; ++i) { - SetFace(faces[i], data.GetUInt(i)); - } - break; - } + case PrimitiveMode_LINE_LOOP: + case PrimitiveMode_LINE_STRIP: { + nFaces = count - ((prim.mode == PrimitiveMode_LINE_STRIP) ? 1 : 0); + faces = new aiFace[nFaces]; + SetFace(faces[0], data.GetUInt(0), data.GetUInt(1)); + for (unsigned int i = 2; i < count; ++i) { + SetFace(faces[i - 1], faces[i - 2].mIndices[1], data.GetUInt(i)); + } + if (prim.mode == PrimitiveMode_LINE_LOOP) { // close the loop + SetFace(faces[count - 1], faces[count - 2].mIndices[1], faces[0].mIndices[0]); + } + break; + } - case PrimitiveMode_LINES: { - nFaces = count / 2; - if (nFaces * 2 != count) { - ASSIMP_LOG_WARN("The number of vertices was not compatible with the LINES mode. Some vertices were dropped."); - count = nFaces * 2; - } - faces = new aiFace[nFaces]; - for (unsigned int i = 0; i < count; i += 2) { - SetFace(faces[i / 2], data.GetUInt(i), data.GetUInt(i + 1)); - } - break; - } + case PrimitiveMode_TRIANGLES: { + nFaces = count / 3; + if (nFaces * 3 != count) { + ASSIMP_LOG_WARN("The number of vertices was not compatible with the TRIANGLES mode. Some vertices were dropped."); + count = nFaces * 3; + } + faces = new aiFace[nFaces]; + for (unsigned int i = 0; i < count; i += 3) { + SetFace(faces[i / 3], data.GetUInt(i), data.GetUInt(i + 1), data.GetUInt(i + 2)); + } + break; + } + case PrimitiveMode_TRIANGLE_STRIP: { + nFaces = count - 2; + faces = new aiFace[nFaces]; + for (unsigned int i = 0; i < nFaces; ++i) { + //The ordering is to ensure that the triangles are all drawn with the same orientation + if ((i + 1) % 2 == 0) { + //For even n, vertices n + 1, n, and n + 2 define triangle n + SetFace(faces[i], data.GetUInt(i + 1), data.GetUInt(i), data.GetUInt(i + 2)); + } else { + //For odd n, vertices n, n+1, and n+2 define triangle n + SetFace(faces[i], data.GetUInt(i), data.GetUInt(i + 1), data.GetUInt(i + 2)); + } + } + break; + } + case PrimitiveMode_TRIANGLE_FAN: + nFaces = count - 2; + faces = new aiFace[nFaces]; + SetFace(faces[0], data.GetUInt(0), data.GetUInt(1), data.GetUInt(2)); + for (unsigned int i = 1; i < nFaces; ++i) { + SetFace(faces[i], faces[0].mIndices[0], faces[i - 1].mIndices[2], data.GetUInt(i + 2)); + } + break; + } + } else { // no indices provided so directly generate from counts - case PrimitiveMode_LINE_LOOP: - case PrimitiveMode_LINE_STRIP: { - nFaces = count - ((prim.mode == PrimitiveMode_LINE_STRIP) ? 1 : 0); - faces = new aiFace[nFaces]; - SetFace(faces[0], data.GetUInt(0), data.GetUInt(1)); - for (unsigned int i = 2; i < count; ++i) { - SetFace(faces[i - 1], faces[i - 2].mIndices[1], data.GetUInt(i)); - } - if (prim.mode == PrimitiveMode_LINE_LOOP) { // close the loop - SetFace(faces[count - 1], faces[count - 2].mIndices[1], faces[0].mIndices[0]); - } - break; - } + // use the already determined count as it includes checks + unsigned int count = aim->mNumVertices; - case PrimitiveMode_TRIANGLES: { - nFaces = count / 3; - if (nFaces * 3 != count) { - ASSIMP_LOG_WARN("The number of vertices was not compatible with the TRIANGLES mode. Some vertices were dropped."); - count = nFaces * 3; - } - faces = new aiFace[nFaces]; - for (unsigned int i = 0; i < count; i += 3) { - SetFace(faces[i / 3], data.GetUInt(i), data.GetUInt(i + 1), data.GetUInt(i + 2)); - } - break; - } - case PrimitiveMode_TRIANGLE_STRIP: { - nFaces = count - 2; - faces = new aiFace[nFaces]; - for (unsigned int i = 0; i < nFaces; ++i) { - //The ordering is to ensure that the triangles are all drawn with the same orientation - if ((i + 1) % 2 == 0) - { - //For even n, vertices n + 1, n, and n + 2 define triangle n - SetFace(faces[i], data.GetUInt(i + 1), data.GetUInt(i), data.GetUInt(i + 2)); - } - else - { - //For odd n, vertices n, n+1, and n+2 define triangle n - SetFace(faces[i], data.GetUInt(i), data.GetUInt(i + 1), data.GetUInt(i + 2)); - } - } - break; - } - case PrimitiveMode_TRIANGLE_FAN: - nFaces = count - 2; - faces = new aiFace[nFaces]; - SetFace(faces[0], data.GetUInt(0), data.GetUInt(1), data.GetUInt(2)); - for (unsigned int i = 1; i < nFaces; ++i) { - SetFace(faces[i], faces[0].mIndices[0], faces[i - 1].mIndices[2], data.GetUInt(i + 2)); - } - break; - } - } - else { // no indices provided so directly generate from counts + switch (prim.mode) { + case PrimitiveMode_POINTS: { + nFaces = count; + faces = new aiFace[nFaces]; + for (unsigned int i = 0; i < count; ++i) { + SetFace(faces[i], i); + } + break; + } - // use the already determined count as it includes checks - unsigned int count = aim->mNumVertices; + case PrimitiveMode_LINES: { + nFaces = count / 2; + if (nFaces * 2 != count) { + ASSIMP_LOG_WARN("The number of vertices was not compatible with the LINES mode. Some vertices were dropped."); + count = (unsigned int)nFaces * 2; + } + faces = new aiFace[nFaces]; + for (unsigned int i = 0; i < count; i += 2) { + SetFace(faces[i / 2], i, i + 1); + } + break; + } - switch (prim.mode) { - case PrimitiveMode_POINTS: { - nFaces = count; - faces = new aiFace[nFaces]; - for (unsigned int i = 0; i < count; ++i) { - SetFace(faces[i], i); - } - break; - } + case PrimitiveMode_LINE_LOOP: + case PrimitiveMode_LINE_STRIP: { + nFaces = count - ((prim.mode == PrimitiveMode_LINE_STRIP) ? 1 : 0); + faces = new aiFace[nFaces]; + SetFace(faces[0], 0, 1); + for (unsigned int i = 2; i < count; ++i) { + SetFace(faces[i - 1], faces[i - 2].mIndices[1], i); + } + if (prim.mode == PrimitiveMode_LINE_LOOP) { // close the loop + SetFace(faces[count - 1], faces[count - 2].mIndices[1], faces[0].mIndices[0]); + } + break; + } - case PrimitiveMode_LINES: { - nFaces = count / 2; - if (nFaces * 2 != count) { - ASSIMP_LOG_WARN("The number of vertices was not compatible with the LINES mode. Some vertices were dropped."); - count = nFaces * 2; - } - faces = new aiFace[nFaces]; - for (unsigned int i = 0; i < count; i += 2) { - SetFace(faces[i / 2], i, i + 1); - } - break; - } + case PrimitiveMode_TRIANGLES: { + nFaces = count / 3; + if (nFaces * 3 != count) { + ASSIMP_LOG_WARN("The number of vertices was not compatible with the TRIANGLES mode. Some vertices were dropped."); + count = (unsigned int)nFaces * 3; + } + faces = new aiFace[nFaces]; + for (unsigned int i = 0; i < count; i += 3) { + SetFace(faces[i / 3], i, i + 1, i + 2); + } + break; + } + case PrimitiveMode_TRIANGLE_STRIP: { + nFaces = count - 2; + faces = new aiFace[nFaces]; + for (unsigned int i = 0; i < nFaces; ++i) { + //The ordering is to ensure that the triangles are all drawn with the same orientation + if ((i + 1) % 2 == 0) { + //For even n, vertices n + 1, n, and n + 2 define triangle n + SetFace(faces[i], i + 1, i, i + 2); + } else { + //For odd n, vertices n, n+1, and n+2 define triangle n + SetFace(faces[i], i, i + 1, i + 2); + } + } + break; + } + case PrimitiveMode_TRIANGLE_FAN: + nFaces = count - 2; + faces = new aiFace[nFaces]; + SetFace(faces[0], 0, 1, 2); + for (unsigned int i = 1; i < nFaces; ++i) { + SetFace(faces[i], faces[0].mIndices[0], faces[i - 1].mIndices[2], i + 2); + } + break; + } + } - case PrimitiveMode_LINE_LOOP: - case PrimitiveMode_LINE_STRIP: { - nFaces = count - ((prim.mode == PrimitiveMode_LINE_STRIP) ? 1 : 0); - faces = new aiFace[nFaces]; - SetFace(faces[0], 0, 1); - for (unsigned int i = 2; i < count; ++i) { - SetFace(faces[i - 1], faces[i - 2].mIndices[1], i); - } - if (prim.mode == PrimitiveMode_LINE_LOOP) { // close the loop - SetFace(faces[count - 1], faces[count - 2].mIndices[1], faces[0].mIndices[0]); - } - break; - } + if (faces) { + aim->mFaces = faces; + aim->mNumFaces = static_cast(nFaces); + ai_assert(CheckValidFacesIndices(faces, static_cast(nFaces), aim->mNumVertices)); + } - case PrimitiveMode_TRIANGLES: { - nFaces = count / 3; - if (nFaces * 3 != count) { - ASSIMP_LOG_WARN("The number of vertices was not compatible with the TRIANGLES mode. Some vertices were dropped."); - count = nFaces * 3; - } - faces = new aiFace[nFaces]; - for (unsigned int i = 0; i < count; i += 3) { - SetFace(faces[i / 3], i, i + 1, i + 2); - } - break; - } - case PrimitiveMode_TRIANGLE_STRIP: { - nFaces = count - 2; - faces = new aiFace[nFaces]; - for (unsigned int i = 0; i < nFaces; ++i) { - //The ordering is to ensure that the triangles are all drawn with the same orientation - if ((i+1) % 2 == 0) - { - //For even n, vertices n + 1, n, and n + 2 define triangle n - SetFace(faces[i], i+1, i, i+2); - } - else - { - //For odd n, vertices n, n+1, and n+2 define triangle n - SetFace(faces[i], i, i+1, i+2); - } - } - break; - } - case PrimitiveMode_TRIANGLE_FAN: - nFaces = count - 2; - faces = new aiFace[nFaces]; - SetFace(faces[0], 0, 1, 2); - for (unsigned int i = 1; i < nFaces; ++i) { - SetFace(faces[i], faces[0].mIndices[0], faces[i - 1].mIndices[2], i + 2); - } - break; - } - } + if (prim.material) { + aim->mMaterialIndex = prim.material.GetIndex(); + } else { + aim->mMaterialIndex = mScene->mNumMaterials - 1; + } + } + } - if (faces) { - aim->mFaces = faces; - aim->mNumFaces = static_cast(nFaces); - ai_assert(CheckValidFacesIndices(faces, static_cast(nFaces), aim->mNumVertices)); - } + meshOffsets.push_back(k); - if (prim.material) { - aim->mMaterialIndex = prim.material.GetIndex(); - } - else { - aim->mMaterialIndex = mScene->mNumMaterials - 1; - } - - } - } - - meshOffsets.push_back(k); - - CopyVector(meshes, mScene->mMeshes, mScene->mNumMeshes); + CopyVector(meshes, mScene->mMeshes, mScene->mNumMeshes); } -void glTF2Importer::ImportCameras(glTF2::Asset& r) -{ - if (!r.cameras.Size()) return; +void glTF2Importer::ImportCameras(glTF2::Asset &r) { + if (!r.cameras.Size()) return; - mScene->mNumCameras = r.cameras.Size(); - mScene->mCameras = new aiCamera*[r.cameras.Size()]; + mScene->mNumCameras = r.cameras.Size(); + mScene->mCameras = new aiCamera *[r.cameras.Size()]; - for (size_t i = 0; i < r.cameras.Size(); ++i) { - Camera& cam = r.cameras[i]; + for (size_t i = 0; i < r.cameras.Size(); ++i) { + Camera &cam = r.cameras[i]; - aiCamera* aicam = mScene->mCameras[i] = new aiCamera(); + aiCamera *aicam = mScene->mCameras[i] = new aiCamera(); - // cameras point in -Z by default, rest is specified in node transform - aicam->mLookAt = aiVector3D(0.f,0.f,-1.f); + // cameras point in -Z by default, rest is specified in node transform + aicam->mLookAt = aiVector3D(0.f, 0.f, -1.f); - if (cam.type == Camera::Perspective) { + if (cam.type == Camera::Perspective) { - aicam->mAspect = cam.cameraProperties.perspective.aspectRatio; - aicam->mHorizontalFOV = cam.cameraProperties.perspective.yfov * ((aicam->mAspect == 0.f) ? 1.f : aicam->mAspect); - aicam->mClipPlaneFar = cam.cameraProperties.perspective.zfar; - aicam->mClipPlaneNear = cam.cameraProperties.perspective.znear; - } else { - aicam->mClipPlaneFar = cam.cameraProperties.ortographic.zfar; - aicam->mClipPlaneNear = cam.cameraProperties.ortographic.znear; - aicam->mHorizontalFOV = 0.0; - aicam->mAspect = 1.0f; - if (0.f != cam.cameraProperties.ortographic.ymag ) { - aicam->mAspect = cam.cameraProperties.ortographic.xmag / cam.cameraProperties.ortographic.ymag; - } - } - } + aicam->mAspect = cam.cameraProperties.perspective.aspectRatio; + aicam->mHorizontalFOV = cam.cameraProperties.perspective.yfov * ((aicam->mAspect == 0.f) ? 1.f : aicam->mAspect); + aicam->mClipPlaneFar = cam.cameraProperties.perspective.zfar; + aicam->mClipPlaneNear = cam.cameraProperties.perspective.znear; + } else { + aicam->mClipPlaneFar = cam.cameraProperties.ortographic.zfar; + aicam->mClipPlaneNear = cam.cameraProperties.ortographic.znear; + aicam->mHorizontalFOV = 0.0; + aicam->mAspect = 1.0f; + if (0.f != cam.cameraProperties.ortographic.ymag) { + aicam->mAspect = cam.cameraProperties.ortographic.xmag / cam.cameraProperties.ortographic.ymag; + } + } + } } -void glTF2Importer::ImportLights(glTF2::Asset& r) -{ - if (!r.lights.Size()) - return; +void glTF2Importer::ImportLights(glTF2::Asset &r) { + if (!r.lights.Size()) + return; - mScene->mNumLights = r.lights.Size(); - mScene->mLights = new aiLight*[r.lights.Size()]; + mScene->mNumLights = r.lights.Size(); + mScene->mLights = new aiLight *[r.lights.Size()]; - for (size_t i = 0; i < r.lights.Size(); ++i) { - Light& light = r.lights[i]; + for (size_t i = 0; i < r.lights.Size(); ++i) { + Light &light = r.lights[i]; - aiLight* ail = mScene->mLights[i] = new aiLight(); + aiLight *ail = mScene->mLights[i] = new aiLight(); - switch (light.type) - { - case Light::Directional: - ail->mType = aiLightSource_DIRECTIONAL; break; - case Light::Point: - ail->mType = aiLightSource_POINT; break; - case Light::Spot: - ail->mType = aiLightSource_SPOT; break; - } + switch (light.type) { + case Light::Directional: + ail->mType = aiLightSource_DIRECTIONAL; + break; + case Light::Point: + ail->mType = aiLightSource_POINT; + break; + case Light::Spot: + ail->mType = aiLightSource_SPOT; + break; + } - if (ail->mType != aiLightSource_POINT) - { - ail->mDirection = aiVector3D(0.0f, 0.0f, -1.0f); - ail->mUp = aiVector3D(0.0f, 1.0f, 0.0f); - } + if (ail->mType != aiLightSource_POINT) { + ail->mDirection = aiVector3D(0.0f, 0.0f, -1.0f); + ail->mUp = aiVector3D(0.0f, 1.0f, 0.0f); + } - vec3 colorWithIntensity = { light.color[0] * light.intensity, light.color[1] * light.intensity, light.color[2] * light.intensity }; - CopyValue(colorWithIntensity, ail->mColorAmbient); - CopyValue(colorWithIntensity, ail->mColorDiffuse); - CopyValue(colorWithIntensity, ail->mColorSpecular); + vec3 colorWithIntensity = { light.color[0] * light.intensity, light.color[1] * light.intensity, light.color[2] * light.intensity }; + CopyValue(colorWithIntensity, ail->mColorAmbient); + CopyValue(colorWithIntensity, ail->mColorDiffuse); + CopyValue(colorWithIntensity, ail->mColorSpecular); - if (ail->mType == aiLightSource_DIRECTIONAL) - { - ail->mAttenuationConstant = 1.0; - ail->mAttenuationLinear = 0.0; - ail->mAttenuationQuadratic = 0.0; - } - else - { - //in PBR attenuation is calculated using inverse square law which can be expressed - //using assimps equation: 1/(att0 + att1 * d + att2 * d*d) with the following parameters - //this is correct equation for the case when range (see - //https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual) - //is not present. When range is not present it is assumed that it is infinite and so numerator is 1. - //When range is present then numerator might be any value in range [0,1] and then assimps equation - //will not suffice. In this case range is added into metadata in ImportNode function - //and its up to implementation to read it when it wants to - ail->mAttenuationConstant = 0.0; - ail->mAttenuationLinear = 0.0; - ail->mAttenuationQuadratic = 1.0; - } + if (ail->mType == aiLightSource_DIRECTIONAL) { + ail->mAttenuationConstant = 1.0; + ail->mAttenuationLinear = 0.0; + ail->mAttenuationQuadratic = 0.0; + } else { + //in PBR attenuation is calculated using inverse square law which can be expressed + //using assimps equation: 1/(att0 + att1 * d + att2 * d*d) with the following parameters + //this is correct equation for the case when range (see + //https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual) + //is not present. When range is not present it is assumed that it is infinite and so numerator is 1. + //When range is present then numerator might be any value in range [0,1] and then assimps equation + //will not suffice. In this case range is added into metadata in ImportNode function + //and its up to implementation to read it when it wants to + ail->mAttenuationConstant = 0.0; + ail->mAttenuationLinear = 0.0; + ail->mAttenuationQuadratic = 1.0; + } - if (ail->mType == aiLightSource_SPOT) - { - ail->mAngleInnerCone = light.innerConeAngle; - ail->mAngleOuterCone = light.outerConeAngle; - } - } + if (ail->mType == aiLightSource_SPOT) { + ail->mAngleInnerCone = light.innerConeAngle; + ail->mAngleOuterCone = light.outerConeAngle; + } + } } -static void GetNodeTransform(aiMatrix4x4& matrix, const glTF2::Node& node) { - if (node.matrix.isPresent) { - CopyValue(node.matrix.value, matrix); - } - else { - if (node.translation.isPresent) { - aiVector3D trans; - CopyValue(node.translation.value, trans); - aiMatrix4x4 t; - aiMatrix4x4::Translation(trans, t); - matrix = matrix * t; - } +static void GetNodeTransform(aiMatrix4x4 &matrix, const glTF2::Node &node) { + if (node.matrix.isPresent) { + CopyValue(node.matrix.value, matrix); + } else { + if (node.translation.isPresent) { + aiVector3D trans; + CopyValue(node.translation.value, trans); + aiMatrix4x4 t; + aiMatrix4x4::Translation(trans, t); + matrix = matrix * t; + } - if (node.rotation.isPresent) { - aiQuaternion rot; - CopyValue(node.rotation.value, rot); - matrix = matrix * aiMatrix4x4(rot.GetMatrix()); - } + if (node.rotation.isPresent) { + aiQuaternion rot; + CopyValue(node.rotation.value, rot); + matrix = matrix * aiMatrix4x4(rot.GetMatrix()); + } - if (node.scale.isPresent) { - aiVector3D scal(1.f); - CopyValue(node.scale.value, scal); - aiMatrix4x4 s; - aiMatrix4x4::Scaling(scal, s); - matrix = matrix * s; - } - } + if (node.scale.isPresent) { + aiVector3D scal(1.f); + CopyValue(node.scale.value, scal); + aiMatrix4x4 s; + aiMatrix4x4::Scaling(scal, s); + matrix = matrix * s; + } + } } -static void BuildVertexWeightMapping(Mesh::Primitive& primitive, std::vector>& map) -{ - Mesh::Primitive::Attributes& attr = primitive.attributes; - if (attr.weight.empty() || attr.joint.empty()) { - return; - } - if (attr.weight[0]->count != attr.joint[0]->count) { - return; - } +static void BuildVertexWeightMapping(Mesh::Primitive &primitive, std::vector> &map) { + Mesh::Primitive::Attributes &attr = primitive.attributes; + if (attr.weight.empty() || attr.joint.empty()) { + return; + } + if (attr.weight[0]->count != attr.joint[0]->count) { + return; + } - size_t num_vertices = attr.weight[0]->count; + size_t num_vertices = attr.weight[0]->count; - struct Weights { float values[4]; }; - Weights* weights = nullptr; - attr.weight[0]->ExtractData(weights); + struct Weights { + float values[4]; + }; + Weights *weights = nullptr; + attr.weight[0]->ExtractData(weights); - struct Indices8 { uint8_t values[4]; }; - struct Indices16 { uint16_t values[4]; }; - Indices8* indices8 = nullptr; - Indices16* indices16 = nullptr; - if (attr.joint[0]->GetElementSize() == 4) { - attr.joint[0]->ExtractData(indices8); - }else { - attr.joint[0]->ExtractData(indices16); - } - // - if (nullptr == indices8 && nullptr == indices16) { - // Something went completely wrong! - ai_assert(false); - return; - } + struct Indices8 { + uint8_t values[4]; + }; + struct Indices16 { + uint16_t values[4]; + }; + Indices8 *indices8 = nullptr; + Indices16 *indices16 = nullptr; + if (attr.joint[0]->GetElementSize() == 4) { + attr.joint[0]->ExtractData(indices8); + } else { + attr.joint[0]->ExtractData(indices16); + } + // + if (nullptr == indices8 && nullptr == indices16) { + // Something went completely wrong! + ai_assert(false); + return; + } - for (size_t i = 0; i < num_vertices; ++i) { - for (int j = 0; j < 4; ++j) { - const unsigned int bone = (indices8!=nullptr) ? indices8[i].values[j] : indices16[i].values[j]; - const float weight = weights[i].values[j]; - if (weight > 0 && bone < map.size()) { - map[bone].reserve(8); - map[bone].emplace_back(static_cast(i), weight); - } - } - } + for (size_t i = 0; i < num_vertices; ++i) { + for (int j = 0; j < 4; ++j) { + const unsigned int bone = (indices8 != nullptr) ? indices8[i].values[j] : indices16[i].values[j]; + const float weight = weights[i].values[j]; + if (weight > 0 && bone < map.size()) { + map[bone].reserve(8); + map[bone].emplace_back(static_cast(i), weight); + } + } + } - delete[] weights; - delete[] indices8; - delete[] indices16; + delete[] weights; + delete[] indices8; + delete[] indices16; } -static std::string GetNodeName(const Node& node) -{ - return node.name.empty() ? node.id : node.name; +static std::string GetNodeName(const Node &node) { + return node.name.empty() ? node.id : node.name; } -aiNode* ImportNode(aiScene* pScene, glTF2::Asset& r, std::vector& meshOffsets, glTF2::Ref& ptr) -{ - Node& node = *ptr; +aiNode *ImportNode(aiScene *pScene, glTF2::Asset &r, std::vector &meshOffsets, glTF2::Ref &ptr) { + Node &node = *ptr; - aiNode* ainode = new aiNode(GetNodeName(node)); + aiNode *ainode = new aiNode(GetNodeName(node)); - if (!node.children.empty()) { - ainode->mNumChildren = unsigned(node.children.size()); - ainode->mChildren = new aiNode*[ainode->mNumChildren]; + if (!node.children.empty()) { + ainode->mNumChildren = unsigned(node.children.size()); + ainode->mChildren = new aiNode *[ainode->mNumChildren]; - for (unsigned int i = 0; i < ainode->mNumChildren; ++i) { - aiNode* child = ImportNode(pScene, r, meshOffsets, node.children[i]); - child->mParent = ainode; - ainode->mChildren[i] = child; - } - } + for (unsigned int i = 0; i < ainode->mNumChildren; ++i) { + aiNode *child = ImportNode(pScene, r, meshOffsets, node.children[i]); + child->mParent = ainode; + ainode->mChildren[i] = child; + } + } - GetNodeTransform(ainode->mTransformation, node); + GetNodeTransform(ainode->mTransformation, node); - if (!node.meshes.empty()) { - // GLTF files contain at most 1 mesh per node. - assert(node.meshes.size() == 1); - int mesh_idx = node.meshes[0].GetIndex(); - int count = meshOffsets[mesh_idx + 1] - meshOffsets[mesh_idx]; + if (!node.meshes.empty()) { + // GLTF files contain at most 1 mesh per node. + assert(node.meshes.size() == 1); + int mesh_idx = node.meshes[0].GetIndex(); + int count = meshOffsets[mesh_idx + 1] - meshOffsets[mesh_idx]; - ainode->mNumMeshes = count; - ainode->mMeshes = new unsigned int[count]; + ainode->mNumMeshes = count; + ainode->mMeshes = new unsigned int[count]; - if (node.skin) { - for (int primitiveNo = 0; primitiveNo < count; ++primitiveNo) { - aiMesh* mesh = pScene->mMeshes[meshOffsets[mesh_idx]+primitiveNo]; - mesh->mNumBones = static_cast(node.skin->jointNames.size()); - mesh->mBones = new aiBone*[mesh->mNumBones]; + if (node.skin) { + for (int primitiveNo = 0; primitiveNo < count; ++primitiveNo) { + aiMesh *mesh = pScene->mMeshes[meshOffsets[mesh_idx] + primitiveNo]; + mesh->mNumBones = static_cast(node.skin->jointNames.size()); + mesh->mBones = new aiBone *[mesh->mNumBones]; - // GLTF and Assimp choose to store bone weights differently. - // GLTF has each vertex specify which bones influence the vertex. - // Assimp has each bone specify which vertices it has influence over. - // To convert this data, we first read over the vertex data and pull - // out the bone-to-vertex mapping. Then, when creating the aiBones, - // we copy the bone-to-vertex mapping into the bone. This is unfortunate - // both because it's somewhat slow and because, for many applications, - // we then need to reconvert the data back into the vertex-to-bone - // mapping which makes things doubly-slow. - std::vector> weighting(mesh->mNumBones); - BuildVertexWeightMapping(node.meshes[0]->primitives[primitiveNo], weighting); + // GLTF and Assimp choose to store bone weights differently. + // GLTF has each vertex specify which bones influence the vertex. + // Assimp has each bone specify which vertices it has influence over. + // To convert this data, we first read over the vertex data and pull + // out the bone-to-vertex mapping. Then, when creating the aiBones, + // we copy the bone-to-vertex mapping into the bone. This is unfortunate + // both because it's somewhat slow and because, for many applications, + // we then need to reconvert the data back into the vertex-to-bone + // mapping which makes things doubly-slow. + std::vector> weighting(mesh->mNumBones); + BuildVertexWeightMapping(node.meshes[0]->primitives[primitiveNo], weighting); - mat4* pbindMatrices = nullptr; - node.skin->inverseBindMatrices->ExtractData(pbindMatrices); + mat4 *pbindMatrices = nullptr; + node.skin->inverseBindMatrices->ExtractData(pbindMatrices); - for (uint32_t i = 0; i < mesh->mNumBones; ++i) { - aiBone* bone = new aiBone(); + for (uint32_t i = 0; i < mesh->mNumBones; ++i) { + aiBone *bone = new aiBone(); - Ref joint = node.skin->jointNames[i]; - if (!joint->name.empty()) { - bone->mName = joint->name; - } else { - // Assimp expects each bone to have a unique name. - static const std::string kDefaultName = "bone_"; - char postfix[10] = {0}; - ASSIMP_itoa10(postfix, i); - bone->mName = (kDefaultName + postfix); - } - GetNodeTransform(bone->mOffsetMatrix, *joint); + Ref joint = node.skin->jointNames[i]; + if (!joint->name.empty()) { + bone->mName = joint->name; + } else { + // Assimp expects each bone to have a unique name. + static const std::string kDefaultName = "bone_"; + char postfix[10] = { 0 }; + ASSIMP_itoa10(postfix, i); + bone->mName = (kDefaultName + postfix); + } + GetNodeTransform(bone->mOffsetMatrix, *joint); - CopyValue(pbindMatrices[i], bone->mOffsetMatrix); + CopyValue(pbindMatrices[i], bone->mOffsetMatrix); - std::vector& weights = weighting[i]; + std::vector &weights = weighting[i]; - bone->mNumWeights = static_cast(weights.size()); - if (bone->mNumWeights > 0) { - bone->mWeights = new aiVertexWeight[bone->mNumWeights]; - memcpy(bone->mWeights, weights.data(), bone->mNumWeights * sizeof(aiVertexWeight)); - } else { - // Assimp expects all bones to have at least 1 weight. - bone->mWeights = new aiVertexWeight[1]; - bone->mNumWeights = 1; - bone->mWeights->mVertexId = 0; - bone->mWeights->mWeight = 0.f; - } - mesh->mBones[i] = bone; - } + bone->mNumWeights = static_cast(weights.size()); + if (bone->mNumWeights > 0) { + bone->mWeights = new aiVertexWeight[bone->mNumWeights]; + memcpy(bone->mWeights, weights.data(), bone->mNumWeights * sizeof(aiVertexWeight)); + } else { + // Assimp expects all bones to have at least 1 weight. + bone->mWeights = new aiVertexWeight[1]; + bone->mNumWeights = 1; + bone->mWeights->mVertexId = 0; + bone->mWeights->mWeight = 0.f; + } + mesh->mBones[i] = bone; + } - if (pbindMatrices) { - delete[] pbindMatrices; - } - } - } + if (pbindMatrices) { + delete[] pbindMatrices; + } + } + } - int k = 0; - for (unsigned int j = meshOffsets[mesh_idx]; j < meshOffsets[mesh_idx + 1]; ++j, ++k) { - ainode->mMeshes[k] = j; - } - } + int k = 0; + for (unsigned int j = meshOffsets[mesh_idx]; j < meshOffsets[mesh_idx + 1]; ++j, ++k) { + ainode->mMeshes[k] = j; + } + } - if (node.camera) { - pScene->mCameras[node.camera.GetIndex()]->mName = ainode->mName; - } + if (node.camera) { + pScene->mCameras[node.camera.GetIndex()]->mName = ainode->mName; + } - if (node.light) { - pScene->mLights[node.light.GetIndex()]->mName = ainode->mName; + if (node.light) { + pScene->mLights[node.light.GetIndex()]->mName = ainode->mName; - //range is optional - see https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual - //it is added to meta data of parent node, because there is no other place to put it - if (node.light->range.isPresent) - { - ainode->mMetaData = aiMetadata::Alloc(1); - ainode->mMetaData->Set(0, "PBR_LightRange", node.light->range.value); - } - } + //range is optional - see https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual + //it is added to meta data of parent node, because there is no other place to put it + if (node.light->range.isPresent) { + ainode->mMetaData = aiMetadata::Alloc(1); + ainode->mMetaData->Set(0, "PBR_LightRange", node.light->range.value); + } + } - return ainode; + return ainode; } -void glTF2Importer::ImportNodes(glTF2::Asset& r) -{ - if (!r.scene) return; +void glTF2Importer::ImportNodes(glTF2::Asset &r) { + if (!r.scene) return; - std::vector< Ref > rootNodes = r.scene->nodes; + std::vector> rootNodes = r.scene->nodes; - // The root nodes - unsigned int numRootNodes = unsigned(rootNodes.size()); - if (numRootNodes == 1) { // a single root node: use it - mScene->mRootNode = ImportNode(mScene, r, meshOffsets, rootNodes[0]); - } - else if (numRootNodes > 1) { // more than one root node: create a fake root - aiNode* root = new aiNode("ROOT"); - root->mChildren = new aiNode*[numRootNodes]; - for (unsigned int i = 0; i < numRootNodes; ++i) { - aiNode* node = ImportNode(mScene, r, meshOffsets, rootNodes[i]); - node->mParent = root; - root->mChildren[root->mNumChildren++] = node; - } - mScene->mRootNode = root; - } - - //if (!mScene->mRootNode) { - // mScene->mRootNode = new aiNode("EMPTY"); - //} + // The root nodes + unsigned int numRootNodes = unsigned(rootNodes.size()); + if (numRootNodes == 1) { // a single root node: use it + mScene->mRootNode = ImportNode(mScene, r, meshOffsets, rootNodes[0]); + } else if (numRootNodes > 1) { // more than one root node: create a fake root + aiNode *root = new aiNode("ROOT"); + root->mChildren = new aiNode *[numRootNodes]; + for (unsigned int i = 0; i < numRootNodes; ++i) { + aiNode *node = ImportNode(mScene, r, meshOffsets, rootNodes[i]); + node->mParent = root; + root->mChildren[root->mNumChildren++] = node; + } + mScene->mRootNode = root; + } } struct AnimationSamplers { - AnimationSamplers() - : translation(nullptr) - , rotation(nullptr) - , scale(nullptr) - , weight(nullptr) { - // empty - } + AnimationSamplers() : + translation(nullptr), + rotation(nullptr), + scale(nullptr), + weight(nullptr) { + // empty + } - Animation::Sampler* translation; - Animation::Sampler* rotation; - Animation::Sampler* scale; - Animation::Sampler* weight; + Animation::Sampler *translation; + Animation::Sampler *rotation; + Animation::Sampler *scale; + Animation::Sampler *weight; }; -aiNodeAnim* CreateNodeAnim(glTF2::Asset& r, Node& node, AnimationSamplers& samplers) -{ - aiNodeAnim* anim = new aiNodeAnim(); - anim->mNodeName = GetNodeName(node); +aiNodeAnim *CreateNodeAnim(glTF2::Asset &r, Node &node, AnimationSamplers &samplers) { + aiNodeAnim *anim = new aiNodeAnim(); + anim->mNodeName = GetNodeName(node); - static const float kMillisecondsFromSeconds = 1000.f; + static const float kMillisecondsFromSeconds = 1000.f; - if (samplers.translation) { - float* times = nullptr; - samplers.translation->input->ExtractData(times); - aiVector3D* values = nullptr; - samplers.translation->output->ExtractData(values); - anim->mNumPositionKeys = static_cast(samplers.translation->input->count); - anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys]; - for (unsigned int i = 0; i < anim->mNumPositionKeys; ++i) { - anim->mPositionKeys[i].mTime = times[i] * kMillisecondsFromSeconds; - anim->mPositionKeys[i].mValue = values[i]; - } - delete[] times; - delete[] values; - } else if (node.translation.isPresent) { - anim->mNumPositionKeys = 1; - anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys]; - anim->mPositionKeys->mTime = 0.f; - anim->mPositionKeys->mValue.x = node.translation.value[0]; - anim->mPositionKeys->mValue.y = node.translation.value[1]; - anim->mPositionKeys->mValue.z = node.translation.value[2]; - } + if (samplers.translation) { + float *times = nullptr; + samplers.translation->input->ExtractData(times); + aiVector3D *values = nullptr; + samplers.translation->output->ExtractData(values); + anim->mNumPositionKeys = static_cast(samplers.translation->input->count); + anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys]; + for (unsigned int i = 0; i < anim->mNumPositionKeys; ++i) { + anim->mPositionKeys[i].mTime = times[i] * kMillisecondsFromSeconds; + anim->mPositionKeys[i].mValue = values[i]; + } + delete[] times; + delete[] values; + } else if (node.translation.isPresent) { + anim->mNumPositionKeys = 1; + anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys]; + anim->mPositionKeys->mTime = 0.f; + anim->mPositionKeys->mValue.x = node.translation.value[0]; + anim->mPositionKeys->mValue.y = node.translation.value[1]; + anim->mPositionKeys->mValue.z = node.translation.value[2]; + } - if (samplers.rotation) { - float* times = nullptr; - samplers.rotation->input->ExtractData(times); - aiQuaternion* values = nullptr; - samplers.rotation->output->ExtractData(values); - anim->mNumRotationKeys = static_cast(samplers.rotation->input->count); - anim->mRotationKeys = new aiQuatKey[anim->mNumRotationKeys]; - for (unsigned int i = 0; i < anim->mNumRotationKeys; ++i) { - anim->mRotationKeys[i].mTime = times[i] * kMillisecondsFromSeconds; - anim->mRotationKeys[i].mValue.x = values[i].w; - anim->mRotationKeys[i].mValue.y = values[i].x; - anim->mRotationKeys[i].mValue.z = values[i].y; - anim->mRotationKeys[i].mValue.w = values[i].z; - } - delete[] times; - delete[] values; - } else if (node.rotation.isPresent) { - anim->mNumRotationKeys = 1; - anim->mRotationKeys = new aiQuatKey[anim->mNumRotationKeys]; - anim->mRotationKeys->mTime = 0.f; - anim->mRotationKeys->mValue.x = node.rotation.value[0]; - anim->mRotationKeys->mValue.y = node.rotation.value[1]; - anim->mRotationKeys->mValue.z = node.rotation.value[2]; - anim->mRotationKeys->mValue.w = node.rotation.value[3]; - } + if (samplers.rotation) { + float *times = nullptr; + samplers.rotation->input->ExtractData(times); + aiQuaternion *values = nullptr; + samplers.rotation->output->ExtractData(values); + anim->mNumRotationKeys = static_cast(samplers.rotation->input->count); + anim->mRotationKeys = new aiQuatKey[anim->mNumRotationKeys]; + for (unsigned int i = 0; i < anim->mNumRotationKeys; ++i) { + anim->mRotationKeys[i].mTime = times[i] * kMillisecondsFromSeconds; + anim->mRotationKeys[i].mValue.x = values[i].w; + anim->mRotationKeys[i].mValue.y = values[i].x; + anim->mRotationKeys[i].mValue.z = values[i].y; + anim->mRotationKeys[i].mValue.w = values[i].z; + } + delete[] times; + delete[] values; + } else if (node.rotation.isPresent) { + anim->mNumRotationKeys = 1; + anim->mRotationKeys = new aiQuatKey[anim->mNumRotationKeys]; + anim->mRotationKeys->mTime = 0.f; + anim->mRotationKeys->mValue.x = node.rotation.value[0]; + anim->mRotationKeys->mValue.y = node.rotation.value[1]; + anim->mRotationKeys->mValue.z = node.rotation.value[2]; + anim->mRotationKeys->mValue.w = node.rotation.value[3]; + } - if (samplers.scale) { - float* times = nullptr; - samplers.scale->input->ExtractData(times); - aiVector3D* values = nullptr; - samplers.scale->output->ExtractData(values); - anim->mNumScalingKeys = static_cast(samplers.scale->input->count); - anim->mScalingKeys = new aiVectorKey[anim->mNumScalingKeys]; - for (unsigned int i = 0; i < anim->mNumScalingKeys; ++i) { - anim->mScalingKeys[i].mTime = times[i] * kMillisecondsFromSeconds; - anim->mScalingKeys[i].mValue = values[i]; - } - delete[] times; - delete[] values; - } else if (node.scale.isPresent) { - anim->mNumScalingKeys = 1; - anim->mScalingKeys = new aiVectorKey[anim->mNumScalingKeys]; - anim->mScalingKeys->mTime = 0.f; - anim->mScalingKeys->mValue.x = node.scale.value[0]; - anim->mScalingKeys->mValue.y = node.scale.value[1]; - anim->mScalingKeys->mValue.z = node.scale.value[2]; - } + if (samplers.scale) { + float *times = nullptr; + samplers.scale->input->ExtractData(times); + aiVector3D *values = nullptr; + samplers.scale->output->ExtractData(values); + anim->mNumScalingKeys = static_cast(samplers.scale->input->count); + anim->mScalingKeys = new aiVectorKey[anim->mNumScalingKeys]; + for (unsigned int i = 0; i < anim->mNumScalingKeys; ++i) { + anim->mScalingKeys[i].mTime = times[i] * kMillisecondsFromSeconds; + anim->mScalingKeys[i].mValue = values[i]; + } + delete[] times; + delete[] values; + } else if (node.scale.isPresent) { + anim->mNumScalingKeys = 1; + anim->mScalingKeys = new aiVectorKey[anim->mNumScalingKeys]; + anim->mScalingKeys->mTime = 0.f; + anim->mScalingKeys->mValue.x = node.scale.value[0]; + anim->mScalingKeys->mValue.y = node.scale.value[1]; + anim->mScalingKeys->mValue.z = node.scale.value[2]; + } - return anim; + return anim; } -aiMeshMorphAnim* CreateMeshMorphAnim(glTF2::Asset& r, Node& node, AnimationSamplers& samplers) -{ - aiMeshMorphAnim* anim = new aiMeshMorphAnim(); - anim->mName = GetNodeName(node); +aiMeshMorphAnim *CreateMeshMorphAnim(glTF2::Asset &r, Node &node, AnimationSamplers &samplers) { + aiMeshMorphAnim *anim = new aiMeshMorphAnim(); + anim->mName = GetNodeName(node); - static const float kMillisecondsFromSeconds = 1000.f; + static const float kMillisecondsFromSeconds = 1000.f; - if (nullptr != samplers.weight) { - float* times = nullptr; - samplers.weight->input->ExtractData(times); - float* values = nullptr; - samplers.weight->output->ExtractData(values); - anim->mNumKeys = static_cast(samplers.weight->input->count); + if (nullptr != samplers.weight) { + float *times = nullptr; + samplers.weight->input->ExtractData(times); + float *values = nullptr; + samplers.weight->output->ExtractData(values); + anim->mNumKeys = static_cast(samplers.weight->input->count); - const unsigned int numMorphs = samplers.weight->output->count / anim->mNumKeys; + const unsigned int numMorphs = (unsigned int)samplers.weight->output->count / anim->mNumKeys; - anim->mKeys = new aiMeshMorphKey[anim->mNumKeys]; - unsigned int k = 0u; - for (unsigned int i = 0u; i < anim->mNumKeys; ++i) { - anim->mKeys[i].mTime = times[i] * kMillisecondsFromSeconds; - anim->mKeys[i].mNumValuesAndWeights = numMorphs; - anim->mKeys[i].mValues = new unsigned int[numMorphs]; - anim->mKeys[i].mWeights = new double[numMorphs]; + anim->mKeys = new aiMeshMorphKey[anim->mNumKeys]; + unsigned int k = 0u; + for (unsigned int i = 0u; i < anim->mNumKeys; ++i) { + anim->mKeys[i].mTime = times[i] * kMillisecondsFromSeconds; + anim->mKeys[i].mNumValuesAndWeights = numMorphs; + anim->mKeys[i].mValues = new unsigned int[numMorphs]; + anim->mKeys[i].mWeights = new double[numMorphs]; - for (unsigned int j = 0u; j < numMorphs; ++j, ++k) { - anim->mKeys[i].mValues[j] = j; - anim->mKeys[i].mWeights[j] = ( 0.f > values[k] ) ? 0.f : values[k]; - } - } + for (unsigned int j = 0u; j < numMorphs; ++j, ++k) { + anim->mKeys[i].mValues[j] = j; + anim->mKeys[i].mWeights[j] = (0.f > values[k]) ? 0.f : values[k]; + } + } - delete[] times; - delete[] values; - } + delete[] times; + delete[] values; + } - return anim; + return anim; } -std::unordered_map GatherSamplers(Animation& anim) -{ - std::unordered_map samplers; - for (unsigned int c = 0; c < anim.channels.size(); ++c) { - Animation::Channel& channel = anim.channels[c]; - if (channel.sampler >= static_cast(anim.samplers.size())) { - continue; - } +std::unordered_map GatherSamplers(Animation &anim) { + std::unordered_map samplers; + for (unsigned int c = 0; c < anim.channels.size(); ++c) { + Animation::Channel &channel = anim.channels[c]; + if (channel.sampler >= static_cast(anim.samplers.size())) { + continue; + } - const unsigned int node_index = channel.target.node.GetIndex(); + const unsigned int node_index = channel.target.node.GetIndex(); - AnimationSamplers& sampler = samplers[node_index]; - if (channel.target.path == AnimationPath_TRANSLATION) { - sampler.translation = &anim.samplers[channel.sampler]; - } else if (channel.target.path == AnimationPath_ROTATION) { - sampler.rotation = &anim.samplers[channel.sampler]; - } else if (channel.target.path == AnimationPath_SCALE) { - sampler.scale = &anim.samplers[channel.sampler]; - } else if (channel.target.path == AnimationPath_WEIGHTS) { - sampler.weight = &anim.samplers[channel.sampler]; - } - } + AnimationSamplers &sampler = samplers[node_index]; + if (channel.target.path == AnimationPath_TRANSLATION) { + sampler.translation = &anim.samplers[channel.sampler]; + } else if (channel.target.path == AnimationPath_ROTATION) { + sampler.rotation = &anim.samplers[channel.sampler]; + } else if (channel.target.path == AnimationPath_SCALE) { + sampler.scale = &anim.samplers[channel.sampler]; + } else if (channel.target.path == AnimationPath_WEIGHTS) { + sampler.weight = &anim.samplers[channel.sampler]; + } + } - return samplers; + return samplers; } -void glTF2Importer::ImportAnimations(glTF2::Asset& r) -{ - if (!r.scene) return; +void glTF2Importer::ImportAnimations(glTF2::Asset &r) { + if (!r.scene) return; - mScene->mNumAnimations = r.animations.Size(); - if (mScene->mNumAnimations == 0) { - return; - } + mScene->mNumAnimations = r.animations.Size(); + if (mScene->mNumAnimations == 0) { + return; + } - mScene->mAnimations = new aiAnimation*[mScene->mNumAnimations]; - for (unsigned int i = 0; i < r.animations.Size(); ++i) { - Animation& anim = r.animations[i]; + mScene->mAnimations = new aiAnimation *[mScene->mNumAnimations]; + for (unsigned int i = 0; i < r.animations.Size(); ++i) { + Animation &anim = r.animations[i]; - aiAnimation* ai_anim = new aiAnimation(); - ai_anim->mName = anim.name; - ai_anim->mDuration = 0; - ai_anim->mTicksPerSecond = 0; + aiAnimation *ai_anim = new aiAnimation(); + ai_anim->mName = anim.name; + ai_anim->mDuration = 0; + ai_anim->mTicksPerSecond = 0; - std::unordered_map samplers = GatherSamplers(anim); + std::unordered_map samplers = GatherSamplers(anim); - uint32_t numChannels = 0u; - uint32_t numMorphMeshChannels = 0u; + uint32_t numChannels = 0u; + uint32_t numMorphMeshChannels = 0u; - for (auto& iter : samplers) { - if ((nullptr != iter.second.rotation) || (nullptr != iter.second.scale) || (nullptr != iter.second.translation)) { - ++numChannels; - } - if (nullptr != iter.second.weight) { - ++numMorphMeshChannels; - } - } + for (auto &iter : samplers) { + if ((nullptr != iter.second.rotation) || (nullptr != iter.second.scale) || (nullptr != iter.second.translation)) { + ++numChannels; + } + if (nullptr != iter.second.weight) { + ++numMorphMeshChannels; + } + } - ai_anim->mNumChannels = numChannels; - if (ai_anim->mNumChannels > 0) { - ai_anim->mChannels = new aiNodeAnim*[ai_anim->mNumChannels]; - int j = 0; - for (auto& iter : samplers) { - if ((nullptr != iter.second.rotation) || (nullptr != iter.second.scale) || (nullptr != iter.second.translation)) { - ai_anim->mChannels[j] = CreateNodeAnim(r, r.nodes[iter.first], iter.second); - ++j; - } - } - } + ai_anim->mNumChannels = numChannels; + if (ai_anim->mNumChannels > 0) { + ai_anim->mChannels = new aiNodeAnim *[ai_anim->mNumChannels]; + int j = 0; + for (auto &iter : samplers) { + if ((nullptr != iter.second.rotation) || (nullptr != iter.second.scale) || (nullptr != iter.second.translation)) { + ai_anim->mChannels[j] = CreateNodeAnim(r, r.nodes[iter.first], iter.second); + ++j; + } + } + } - ai_anim->mNumMorphMeshChannels = numMorphMeshChannels; - if (ai_anim->mNumMorphMeshChannels > 0) { - ai_anim->mMorphMeshChannels = new aiMeshMorphAnim*[ai_anim->mNumMorphMeshChannels]; - int j = 0; - for (auto& iter : samplers) { - if (nullptr != iter.second.weight) { - ai_anim->mMorphMeshChannels[j] = CreateMeshMorphAnim(r, r.nodes[iter.first], iter.second); - ++j; - } - } - } + ai_anim->mNumMorphMeshChannels = numMorphMeshChannels; + if (ai_anim->mNumMorphMeshChannels > 0) { + ai_anim->mMorphMeshChannels = new aiMeshMorphAnim *[ai_anim->mNumMorphMeshChannels]; + int j = 0; + for (auto &iter : samplers) { + if (nullptr != iter.second.weight) { + ai_anim->mMorphMeshChannels[j] = CreateMeshMorphAnim(r, r.nodes[iter.first], iter.second); + ++j; + } + } + } - // Use the latest keyframe for the duration of the animation - double maxDuration = 0; - unsigned int maxNumberOfKeys = 0; - for (unsigned int j = 0; j < ai_anim->mNumChannels; ++j) { - auto chan = ai_anim->mChannels[j]; - if (chan->mNumPositionKeys) { - auto lastPosKey = chan->mPositionKeys[chan->mNumPositionKeys - 1]; - if (lastPosKey.mTime > maxDuration) { - maxDuration = lastPosKey.mTime; - } - maxNumberOfKeys = std::max(maxNumberOfKeys, chan->mNumPositionKeys); - } - if (chan->mNumRotationKeys) { - auto lastRotKey = chan->mRotationKeys[chan->mNumRotationKeys - 1]; - if (lastRotKey.mTime > maxDuration) { - maxDuration = lastRotKey.mTime; - } - maxNumberOfKeys = std::max(maxNumberOfKeys, chan->mNumRotationKeys); - } - if (chan->mNumScalingKeys) { - auto lastScaleKey = chan->mScalingKeys[chan->mNumScalingKeys - 1]; - if (lastScaleKey.mTime > maxDuration) { - maxDuration = lastScaleKey.mTime; - } - maxNumberOfKeys = std::max(maxNumberOfKeys, chan->mNumScalingKeys); - } - } + // Use the latest keyframe for the duration of the animation + double maxDuration = 0; + unsigned int maxNumberOfKeys = 0; + for (unsigned int j = 0; j < ai_anim->mNumChannels; ++j) { + auto chan = ai_anim->mChannels[j]; + if (chan->mNumPositionKeys) { + auto lastPosKey = chan->mPositionKeys[chan->mNumPositionKeys - 1]; + if (lastPosKey.mTime > maxDuration) { + maxDuration = lastPosKey.mTime; + } + maxNumberOfKeys = std::max(maxNumberOfKeys, chan->mNumPositionKeys); + } + if (chan->mNumRotationKeys) { + auto lastRotKey = chan->mRotationKeys[chan->mNumRotationKeys - 1]; + if (lastRotKey.mTime > maxDuration) { + maxDuration = lastRotKey.mTime; + } + maxNumberOfKeys = std::max(maxNumberOfKeys, chan->mNumRotationKeys); + } + if (chan->mNumScalingKeys) { + auto lastScaleKey = chan->mScalingKeys[chan->mNumScalingKeys - 1]; + if (lastScaleKey.mTime > maxDuration) { + maxDuration = lastScaleKey.mTime; + } + maxNumberOfKeys = std::max(maxNumberOfKeys, chan->mNumScalingKeys); + } + } - for (unsigned int j = 0; j < ai_anim->mNumMorphMeshChannels; ++j) { - const auto* const chan = ai_anim->mMorphMeshChannels[j]; + for (unsigned int j = 0; j < ai_anim->mNumMorphMeshChannels; ++j) { + const auto *const chan = ai_anim->mMorphMeshChannels[j]; - if (0u != chan->mNumKeys) { - const auto& lastKey = chan->mKeys[chan->mNumKeys - 1u]; - if (lastKey.mTime > maxDuration) { - maxDuration = lastKey.mTime; - } - maxNumberOfKeys = std::max(maxNumberOfKeys, chan->mNumKeys); - } - } + if (0u != chan->mNumKeys) { + const auto &lastKey = chan->mKeys[chan->mNumKeys - 1u]; + if (lastKey.mTime > maxDuration) { + maxDuration = lastKey.mTime; + } + maxNumberOfKeys = std::max(maxNumberOfKeys, chan->mNumKeys); + } + } - ai_anim->mDuration = maxDuration; - ai_anim->mTicksPerSecond = 1000.0; + ai_anim->mDuration = maxDuration; + ai_anim->mTicksPerSecond = 1000.0; - mScene->mAnimations[i] = ai_anim; - } + mScene->mAnimations[i] = ai_anim; + } } -void glTF2Importer::ImportEmbeddedTextures(glTF2::Asset& r) -{ - embeddedTexIdxs.resize(r.images.Size(), -1); +void glTF2Importer::ImportEmbeddedTextures(glTF2::Asset &r) { + embeddedTexIdxs.resize(r.images.Size(), -1); - int numEmbeddedTexs = 0; - for (size_t i = 0; i < r.images.Size(); ++i) { - if (r.images[i].HasData()) - numEmbeddedTexs += 1; - } + int numEmbeddedTexs = 0; + for (size_t i = 0; i < r.images.Size(); ++i) { + if (r.images[i].HasData()) + numEmbeddedTexs += 1; + } - if (numEmbeddedTexs == 0) - return; + if (numEmbeddedTexs == 0) + return; - mScene->mTextures = new aiTexture*[numEmbeddedTexs]; + mScene->mTextures = new aiTexture *[numEmbeddedTexs]; - // Add the embedded textures - for (size_t i = 0; i < r.images.Size(); ++i) { - Image &img = r.images[i]; - if (!img.HasData()) continue; + // Add the embedded textures + for (size_t i = 0; i < r.images.Size(); ++i) { + Image &img = r.images[i]; + if (!img.HasData()) continue; - int idx = mScene->mNumTextures++; - embeddedTexIdxs[i] = idx; + int idx = mScene->mNumTextures++; + embeddedTexIdxs[i] = idx; - aiTexture* tex = mScene->mTextures[idx] = new aiTexture(); + aiTexture *tex = mScene->mTextures[idx] = new aiTexture(); - size_t length = img.GetDataLength(); - void* data = img.StealData(); + size_t length = img.GetDataLength(); + void *data = img.StealData(); - tex->mWidth = static_cast(length); - tex->mHeight = 0; - tex->pcData = reinterpret_cast(data); + tex->mWidth = static_cast(length); + tex->mHeight = 0; + tex->pcData = reinterpret_cast(data); - if (!img.mimeType.empty()) { - const char* ext = strchr(img.mimeType.c_str(), '/') + 1; - if (ext) { - if (strcmp(ext, "jpeg") == 0) ext = "jpg"; + if (!img.mimeType.empty()) { + const char *ext = strchr(img.mimeType.c_str(), '/') + 1; + if (ext) { + if (strcmp(ext, "jpeg") == 0) ext = "jpg"; - size_t len = strlen(ext); - if (len <= 3) { - strcpy(tex->achFormatHint, ext); - } - } - } - } + size_t len = strlen(ext); + if (len <= 3) { + strcpy(tex->achFormatHint, ext); + } + } + } + } } -void glTF2Importer::InternReadFile(const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler) -{ - // clean all member arrays - meshOffsets.clear(); - embeddedTexIdxs.clear(); +void glTF2Importer::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) { + // clean all member arrays + meshOffsets.clear(); + embeddedTexIdxs.clear(); - this->mScene = pScene; + this->mScene = pScene; - // read the asset file - glTF2::Asset asset(pIOHandler); - asset.Load(pFile, GetExtension(pFile) == "glb"); + // read the asset file + glTF2::Asset asset(pIOHandler); + asset.Load(pFile, GetExtension(pFile) == "glb"); - // - // Copy the data out - // + // + // Copy the data out + // - ImportEmbeddedTextures(asset); - ImportMaterials(asset); + ImportEmbeddedTextures(asset); + ImportMaterials(asset); - ImportMeshes(asset); + ImportMeshes(asset); - ImportCameras(asset); - ImportLights(asset); + ImportCameras(asset); + ImportLights(asset); - ImportNodes(asset); + ImportNodes(asset); - ImportAnimations(asset); + ImportAnimations(asset); - if (pScene->mNumMeshes == 0) { - pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE; - } + if (pScene->mNumMeshes == 0) { + pScene->mFlags |= AI_SCENE_FLAGS_INCOMPLETE; + } } #endif // ASSIMP_BUILD_NO_GLTF_IMPORTER - diff --git a/contrib/irrXML/CXMLReaderImpl.h b/contrib/irrXML/CXMLReaderImpl.h index 6f3bec5fa..a125312a1 100644 --- a/contrib/irrXML/CXMLReaderImpl.h +++ b/contrib/irrXML/CXMLReaderImpl.h @@ -15,6 +15,9 @@ #include //using namespace Assimp; +// For locale independent number conversion +#include +#include #ifdef _DEBUG #define IRR_DEBUGPRINT(x) printf((x)); @@ -178,8 +181,11 @@ public: return 0; core::stringc c = attrvalue; - return static_cast(atof(c.c_str())); - //return fast_atof(c.c_str()); + std::istringstream sstr(c.c_str()); + sstr.imbue(std::locale("C")); // Locale free number convert + float fNum; + sstr >> fNum; + return fNum; } diff --git a/contrib/zip/.gitignore b/contrib/zip/.gitignore index a7904a1ef..49b2cb2fd 100644 --- a/contrib/zip/.gitignore +++ b/contrib/zip/.gitignore @@ -1,6 +1,7 @@ /build/ /test/build/ /xcodeproj/ +.vscode/ # Object files *.o @@ -54,3 +55,4 @@ zip.dir/ test/test.exe.vcxproj.filters test/test.exe.vcxproj test/test.exe.dir/ + diff --git a/contrib/zip/CMakeLists.txt b/contrib/zip/CMakeLists.txt index b46dbb1db..77916d2e1 100644 --- a/contrib/zip/CMakeLists.txt +++ b/contrib/zip/CMakeLists.txt @@ -1,10 +1,14 @@ -cmake_minimum_required(VERSION 2.8) -project(zip) -enable_language(C) +cmake_minimum_required(VERSION 3.0) + +project(zip + LANGUAGES C + VERSION "0.1.15") set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) +option(CMAKE_DISABLE_TESTING "Disable test creation" OFF) + if (MSVC) - # Use secure functions by defaualt and suppress warnings about "deprecated" functions + # Use secure functions by default and suppress warnings about "deprecated" functions set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /D _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /D _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES_COUNT=1") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /D _CRT_NONSTDC_NO_WARNINGS=1 /D _CRT_SECURE_NO_WARNINGS=1") @@ -12,28 +16,80 @@ elseif ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "AppleClang") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -Wall -Wextra -Werror -pedantic") + if(ENABLE_COVERAGE) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0 -fprofile-arcs -ftest-coverage") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage") + endif() endif (MSVC) # zip set(SRC src/miniz.h src/zip.h src/zip.c) add_library(${PROJECT_NAME} ${SRC}) -target_include_directories(${PROJECT_NAME} INTERFACE src) +target_include_directories(${PROJECT_NAME} PUBLIC + $ + $ +) # test if (NOT CMAKE_DISABLE_TESTING) enable_testing() add_subdirectory(test) find_package(Sanitizers) - add_sanitizers(${PROJECT_NAME} test.exe) - add_sanitizers(${PROJECT_NAME} test_miniz.exe) + add_sanitizers(${PROJECT_NAME} ${test_out} ${test_miniz_out}) endif() +#### +# Installation (https://github.com/forexample/package-example) { + +set(CONFIG_INSTALL_DIR "lib/cmake/${PROJECT_NAME}") +set(INCLUDE_INSTALL_DIR "include") + +set(GENERATED_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated") + +# Configuration +set(VERSION_CONFIG "${GENERATED_DIR}/${PROJECT_NAME}ConfigVersion.cmake") +set(PROJECT_CONFIG "${GENERATED_DIR}/${PROJECT_NAME}Config.cmake") +set(TARGETS_EXPORT_NAME "${PROJECT_NAME}Targets") +set(NAMESPACE "${PROJECT_NAME}::") + +# Include module with fuction 'write_basic_package_version_file' +include(CMakePackageConfigHelpers) + +# Note: PROJECT_VERSION is used as a VERSION +write_basic_package_version_file( + "${VERSION_CONFIG}" COMPATIBILITY SameMajorVersion +) + +# Use variables: +# * TARGETS_EXPORT_NAME +# * PROJECT_NAME +configure_package_config_file( + "cmake/Config.cmake.in" + "${PROJECT_CONFIG}" + INSTALL_DESTINATION "${CONFIG_INSTALL_DIR}" +) + +install( + FILES "${PROJECT_CONFIG}" "${VERSION_CONFIG}" + DESTINATION "${CONFIG_INSTALL_DIR}" +) + +install( + EXPORT "${TARGETS_EXPORT_NAME}" + NAMESPACE "${NAMESPACE}" + DESTINATION "${CONFIG_INSTALL_DIR}" +) + +# } + install(TARGETS ${PROJECT_NAME} + EXPORT ${TARGETS_EXPORT_NAME} RUNTIME DESTINATION bin ARCHIVE DESTINATION lib LIBRARY DESTINATION lib - COMPONENT library) -install(FILES ${PROJECT_SOURCE_DIR}/src/zip.h DESTINATION include) + INCLUDES DESTINATION ${INCLUDE_INSTALL_DIR} +) +install(FILES ${PROJECT_SOURCE_DIR}/src/zip.h DESTINATION ${INCLUDE_INSTALL_DIR}/zip) # uninstall target (https://gitlab.kitware.com/cmake/community/wikis/FAQ#can-i-do-make-uninstall-with-cmake) if(NOT TARGET uninstall) @@ -45,3 +101,12 @@ if(NOT TARGET uninstall) add_custom_target(uninstall COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake/cmake_uninstall.cmake) endif() + +find_package(Doxygen) +if(DOXYGEN_FOUND) + configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile @ONLY) + add_custom_target(doc + ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Generating API documentation with Doxygen" VERBATIM) +endif() diff --git a/contrib/zip/README.md b/contrib/zip/README.md index d5fb8cd20..14eb9a34c 100644 --- a/contrib/zip/README.md +++ b/contrib/zip/README.md @@ -71,7 +71,7 @@ int arg = 2; zip_extract("foo.zip", "/tmp", on_extract_entry, &arg); ``` -* Extract a zip entry into memory. +* Extract a zip entry into memory. ```c void *buf = NULL; size_t bufsize; @@ -89,7 +89,7 @@ zip_close(zip); free(buf); ``` -* Extract a zip entry into memory (no internal allocation). +* Extract a zip entry into memory (no internal allocation). ```c unsigned char *buf; size_t bufsize; @@ -110,7 +110,7 @@ zip_close(zip); free(buf); ``` -* Extract a zip entry into memory using callback. +* Extract a zip entry into memory using callback. ```c struct buffer_t { char *data; @@ -144,7 +144,7 @@ free(buf.data); ``` -* Extract a zip entry into a file. +* Extract a zip entry into a file. ```c struct zip_t *zip = zip_open("foo.zip", 0, 'r'); { @@ -157,7 +157,7 @@ struct zip_t *zip = zip_open("foo.zip", 0, 'r'); zip_close(zip); ``` -* List of all zip entries +* List of all zip entries ```c struct zip_t *zip = zip_open("foo.zip", 0, 'r'); int i, n = zip_total_entries(zip); @@ -174,7 +174,7 @@ for (i = 0; i < n; ++i) { zip_close(zip); ``` -## Bindings +# Bindings Compile zip library as a dynamic library. ```shell $ mkdir build diff --git a/contrib/zip/appveyor.yml b/contrib/zip/appveyor.yml index 0be6373ca..ea17f5de9 100644 --- a/contrib/zip/appveyor.yml +++ b/contrib/zip/appveyor.yml @@ -1,4 +1,4 @@ -version: zip-0.1.9.{build} +version: zip-0.1.15.{build} build_script: - cmd: >- cd c:\projects\zip diff --git a/contrib/zip/src/miniz.h b/contrib/zip/src/miniz.h index 2c27a94d8..c4fcfb83e 100644 --- a/contrib/zip/src/miniz.h +++ b/contrib/zip/src/miniz.h @@ -221,6 +221,7 @@ #ifndef MINIZ_HEADER_INCLUDED #define MINIZ_HEADER_INCLUDED +#include #include // Defines to completely disable specific portions of miniz.c: @@ -284,7 +285,8 @@ /* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES only if not set */ #if !defined(MINIZ_USE_UNALIGNED_LOADS_AND_STORES) #if MINIZ_X86_OR_X64_CPU -/* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES to 1 on CPU's that permit efficient integer loads and stores from unaligned addresses. */ +/* Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES to 1 on CPU's that permit efficient + * integer loads and stores from unaligned addresses. */ #define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 #define MINIZ_UNALIGNED_USE_MEMCPY #else @@ -354,6 +356,44 @@ enum { MZ_FIXED = 4 }; +/* miniz error codes. Be sure to update mz_zip_get_error_string() if you add or + * modify this enum. */ +typedef enum { + MZ_ZIP_NO_ERROR = 0, + MZ_ZIP_UNDEFINED_ERROR, + MZ_ZIP_TOO_MANY_FILES, + MZ_ZIP_FILE_TOO_LARGE, + MZ_ZIP_UNSUPPORTED_METHOD, + MZ_ZIP_UNSUPPORTED_ENCRYPTION, + MZ_ZIP_UNSUPPORTED_FEATURE, + MZ_ZIP_FAILED_FINDING_CENTRAL_DIR, + MZ_ZIP_NOT_AN_ARCHIVE, + MZ_ZIP_INVALID_HEADER_OR_CORRUPTED, + MZ_ZIP_UNSUPPORTED_MULTIDISK, + MZ_ZIP_DECOMPRESSION_FAILED, + MZ_ZIP_COMPRESSION_FAILED, + MZ_ZIP_UNEXPECTED_DECOMPRESSED_SIZE, + MZ_ZIP_CRC_CHECK_FAILED, + MZ_ZIP_UNSUPPORTED_CDIR_SIZE, + MZ_ZIP_ALLOC_FAILED, + MZ_ZIP_FILE_OPEN_FAILED, + MZ_ZIP_FILE_CREATE_FAILED, + MZ_ZIP_FILE_WRITE_FAILED, + MZ_ZIP_FILE_READ_FAILED, + MZ_ZIP_FILE_CLOSE_FAILED, + MZ_ZIP_FILE_SEEK_FAILED, + MZ_ZIP_FILE_STAT_FAILED, + MZ_ZIP_INVALID_PARAMETER, + MZ_ZIP_INVALID_FILENAME, + MZ_ZIP_BUF_TOO_SMALL, + MZ_ZIP_INTERNAL_ERROR, + MZ_ZIP_FILE_NOT_FOUND, + MZ_ZIP_ARCHIVE_TOO_LARGE, + MZ_ZIP_VALIDATION_FAILED, + MZ_ZIP_WRITE_CALLBACK_FAILED, + MZ_ZIP_TOTAL_ERRORS +} mz_zip_error; + // Method #define MZ_DEFLATED 8 @@ -696,6 +736,7 @@ typedef size_t (*mz_file_read_func)(void *pOpaque, mz_uint64 file_ofs, void *pBuf, size_t n); typedef size_t (*mz_file_write_func)(void *pOpaque, mz_uint64 file_ofs, const void *pBuf, size_t n); +typedef mz_bool (*mz_file_needs_keepalive)(void *pOpaque); struct mz_zip_internal_state_tag; typedef struct mz_zip_internal_state_tag mz_zip_internal_state; @@ -707,13 +748,27 @@ typedef enum { MZ_ZIP_MODE_WRITING_HAS_BEEN_FINALIZED = 3 } mz_zip_mode; -typedef struct mz_zip_archive_tag { +typedef enum { + MZ_ZIP_TYPE_INVALID = 0, + MZ_ZIP_TYPE_USER, + MZ_ZIP_TYPE_MEMORY, + MZ_ZIP_TYPE_HEAP, + MZ_ZIP_TYPE_FILE, + MZ_ZIP_TYPE_CFILE, + MZ_ZIP_TOTAL_TYPES +} mz_zip_type; + +typedef struct { mz_uint64 m_archive_size; mz_uint64 m_central_directory_file_ofs; - mz_uint m_total_files; - mz_zip_mode m_zip_mode; - mz_uint m_file_offset_alignment; + /* We only support up to UINT32_MAX files in zip64 mode. */ + mz_uint32 m_total_files; + mz_zip_mode m_zip_mode; + mz_zip_type m_zip_type; + mz_zip_error m_last_error; + + mz_uint64 m_file_offset_alignment; mz_alloc_func m_pAlloc; mz_free_func m_pFree; @@ -722,6 +777,7 @@ typedef struct mz_zip_archive_tag { mz_file_read_func m_pRead; mz_file_write_func m_pWrite; + mz_file_needs_keepalive m_pNeeds_keepalive; void *m_pIO_opaque; mz_zip_internal_state *m_pState; @@ -1263,6 +1319,9 @@ mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, int strategy); #endif // #ifndef MINIZ_NO_ZLIB_APIS +#define MZ_UINT16_MAX (0xFFFFU) +#define MZ_UINT32_MAX (0xFFFFFFFFU) + #ifdef __cplusplus } #endif @@ -1311,6 +1370,11 @@ typedef unsigned char mz_validate_uint64[sizeof(mz_uint64) == 8 ? 1 : -1]; ((mz_uint32)(((const mz_uint8 *)(p))[3]) << 24U)) #endif +#define MZ_READ_LE64(p) \ + (((mz_uint64)MZ_READ_LE32(p)) | \ + (((mz_uint64)MZ_READ_LE32((const mz_uint8 *)(p) + sizeof(mz_uint32))) \ + << 32U)) + #ifdef _MSC_VER #define MZ_FORCEINLINE __forceinline #elif defined(__GNUC__) @@ -4160,6 +4224,17 @@ enum { MZ_ZIP_LOCAL_DIR_HEADER_SIZE = 30, MZ_ZIP_CENTRAL_DIR_HEADER_SIZE = 46, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE = 22, + + /* ZIP64 archive identifier and record sizes */ + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG = 0x06064b50, + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG = 0x07064b50, + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE = 56, + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE = 20, + MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID = 0x0001, + MZ_ZIP_DATA_DESCRIPTOR_ID = 0x08074b50, + MZ_ZIP_DATA_DESCRIPTER_SIZE64 = 24, + MZ_ZIP_DATA_DESCRIPTER_SIZE32 = 16, + // Central directory header record offsets MZ_ZIP_CDH_SIG_OFS = 0, MZ_ZIP_CDH_VERSION_MADE_BY_OFS = 4, @@ -4199,6 +4274,31 @@ enum { MZ_ZIP_ECDH_CDIR_SIZE_OFS = 12, MZ_ZIP_ECDH_CDIR_OFS_OFS = 16, MZ_ZIP_ECDH_COMMENT_SIZE_OFS = 20, + + /* ZIP64 End of central directory locator offsets */ + MZ_ZIP64_ECDL_SIG_OFS = 0, /* 4 bytes */ + MZ_ZIP64_ECDL_NUM_DISK_CDIR_OFS = 4, /* 4 bytes */ + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS = 8, /* 8 bytes */ + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS = 16, /* 4 bytes */ + + /* ZIP64 End of central directory header offsets */ + MZ_ZIP64_ECDH_SIG_OFS = 0, /* 4 bytes */ + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS = 4, /* 8 bytes */ + MZ_ZIP64_ECDH_VERSION_MADE_BY_OFS = 12, /* 2 bytes */ + MZ_ZIP64_ECDH_VERSION_NEEDED_OFS = 14, /* 2 bytes */ + MZ_ZIP64_ECDH_NUM_THIS_DISK_OFS = 16, /* 4 bytes */ + MZ_ZIP64_ECDH_NUM_DISK_CDIR_OFS = 20, /* 4 bytes */ + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS = 24, /* 8 bytes */ + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS = 32, /* 8 bytes */ + MZ_ZIP64_ECDH_CDIR_SIZE_OFS = 40, /* 8 bytes */ + MZ_ZIP64_ECDH_CDIR_OFS_OFS = 48, /* 8 bytes */ + MZ_ZIP_VERSION_MADE_BY_DOS_FILESYSTEM_ID = 0, + MZ_ZIP_DOS_DIR_ATTRIBUTE_BITFLAG = 0x10, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_IS_ENCRYPTED = 1, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_COMPRESSED_PATCH_FLAG = 32, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_USES_STRONG_ENCRYPTION = 64, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_LOCAL_DIR_IS_MASKED = 8192, + MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8 = 1 << 11 }; typedef struct { @@ -4211,7 +4311,24 @@ struct mz_zip_internal_state_tag { mz_zip_array m_central_dir; mz_zip_array m_central_dir_offsets; mz_zip_array m_sorted_central_dir_offsets; + + /* The flags passed in when the archive is initially opened. */ + uint32_t m_init_flags; + + /* MZ_TRUE if the archive has a zip64 end of central directory headers, etc. + */ + mz_bool m_zip64; + + /* MZ_TRUE if we found zip64 extended info in the central directory (m_zip64 + * will also be slammed to true too, even if we didn't find a zip64 end of + * central dir header, etc.) */ + mz_bool m_zip64_has_extended_info_fields; + + /* These fields are used by the file, FILE, memory, and memory/heap read/write + * helpers. */ MZ_FILE *m_pFile; + mz_uint64 m_file_archive_start_ofs; + void *m_pMem; size_t m_mem_size; size_t m_mem_capacity; @@ -4363,6 +4480,13 @@ static mz_bool mz_zip_set_file_times(const char *pFilename, time_t access_time, #endif /* #ifndef MINIZ_NO_STDIO */ #endif /* #ifndef MINIZ_NO_TIME */ +static MZ_FORCEINLINE mz_bool mz_zip_set_error(mz_zip_archive *pZip, + mz_zip_error err_num) { + if (pZip) + pZip->m_last_error = err_num; + return MZ_FALSE; +} + static mz_bool mz_zip_reader_init_internal(mz_zip_archive *pZip, mz_uint32 flags) { (void)flags; @@ -4480,127 +4604,346 @@ mz_zip_reader_sort_central_dir_offsets_by_filename(mz_zip_archive *pZip) { } } -static mz_bool mz_zip_reader_read_central_dir(mz_zip_archive *pZip, - mz_uint32 flags) { - mz_uint cdir_size, num_this_disk, cdir_disk_index; - mz_uint64 cdir_ofs; +static mz_bool mz_zip_reader_locate_header_sig(mz_zip_archive *pZip, + mz_uint32 record_sig, + mz_uint32 record_size, + mz_int64 *pOfs) { mz_int64 cur_file_ofs; - const mz_uint8 *p; mz_uint32 buf_u32[4096 / sizeof(mz_uint32)]; mz_uint8 *pBuf = (mz_uint8 *)buf_u32; - mz_bool sort_central_dir = - ((flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0); - // Basic sanity checks - reject files which are too small, and check the first - // 4 bytes of the file to make sure a local header is there. - if (pZip->m_archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + + /* Basic sanity checks - reject files which are too small */ + if (pZip->m_archive_size < record_size) return MZ_FALSE; - // Find the end of central directory record by scanning the file from the end - // towards the beginning. + + /* Find the record by scanning the file from the end towards the beginning. */ cur_file_ofs = MZ_MAX((mz_int64)pZip->m_archive_size - (mz_int64)sizeof(buf_u32), 0); for (;;) { int i, n = (int)MZ_MIN(sizeof(buf_u32), pZip->m_archive_size - cur_file_ofs); + if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, n) != (mz_uint)n) return MZ_FALSE; - for (i = n - 4; i >= 0; --i) - if (MZ_READ_LE32(pBuf + i) == MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) - break; + + for (i = n - 4; i >= 0; --i) { + mz_uint s = MZ_READ_LE32(pBuf + i); + if (s == record_sig) { + if ((pZip->m_archive_size - (cur_file_ofs + i)) >= record_size) + break; + } + } + if (i >= 0) { cur_file_ofs += i; break; } + + /* Give up if we've searched the entire file, or we've gone back "too far" + * (~64kb) */ if ((!cur_file_ofs) || ((pZip->m_archive_size - cur_file_ofs) >= - (0xFFFF + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE))) + (MZ_UINT16_MAX + record_size))) return MZ_FALSE; + cur_file_ofs = MZ_MAX(cur_file_ofs - (sizeof(buf_u32) - 3), 0); } - // Read and verify the end of central directory record. + + *pOfs = cur_file_ofs; + return MZ_TRUE; +} + +static mz_bool mz_zip_reader_read_central_dir(mz_zip_archive *pZip, + mz_uint flags) { + mz_uint cdir_size = 0, cdir_entries_on_this_disk = 0, num_this_disk = 0, + cdir_disk_index = 0; + mz_uint64 cdir_ofs = 0; + mz_int64 cur_file_ofs = 0; + const mz_uint8 *p; + + mz_uint32 buf_u32[4096 / sizeof(mz_uint32)]; + mz_uint8 *pBuf = (mz_uint8 *)buf_u32; + mz_bool sort_central_dir = + ((flags & MZ_ZIP_FLAG_DO_NOT_SORT_CENTRAL_DIRECTORY) == 0); + mz_uint32 zip64_end_of_central_dir_locator_u32 + [(MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE + sizeof(mz_uint32) - 1) / + sizeof(mz_uint32)]; + mz_uint8 *pZip64_locator = (mz_uint8 *)zip64_end_of_central_dir_locator_u32; + + mz_uint32 zip64_end_of_central_dir_header_u32 + [(MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE + sizeof(mz_uint32) - 1) / + sizeof(mz_uint32)]; + mz_uint8 *pZip64_end_of_central_dir = + (mz_uint8 *)zip64_end_of_central_dir_header_u32; + + mz_uint64 zip64_end_of_central_dir_ofs = 0; + + /* Basic sanity checks - reject files which are too small, and check the first + * 4 bytes of the file to make sure a local header is there. */ + if (pZip->m_archive_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + + if (!mz_zip_reader_locate_header_sig( + pZip, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG, + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE, &cur_file_ofs)) + return mz_zip_set_error(pZip, MZ_ZIP_FAILED_FINDING_CENTRAL_DIR); + + /* Read and verify the end of central directory record. */ if (pZip->m_pRead(pZip->m_pIO_opaque, cur_file_ofs, pBuf, MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) != MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE) - return MZ_FALSE; - if ((MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_SIG_OFS) != - MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) || - ((pZip->m_total_files = - MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS)) != - MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS))) - return MZ_FALSE; + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + if (MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_SIG_OFS) != + MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIG) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + + if (cur_file_ofs >= (MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE + + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE)) { + if (pZip->m_pRead(pZip->m_pIO_opaque, + cur_file_ofs - MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE, + pZip64_locator, + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) == + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIZE) { + if (MZ_READ_LE32(pZip64_locator + MZ_ZIP64_ECDL_SIG_OFS) == + MZ_ZIP64_END_OF_CENTRAL_DIR_LOCATOR_SIG) { + zip64_end_of_central_dir_ofs = MZ_READ_LE64( + pZip64_locator + MZ_ZIP64_ECDL_REL_OFS_TO_ZIP64_ECDR_OFS); + if (zip64_end_of_central_dir_ofs > + (pZip->m_archive_size - MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE)) + return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE); + + if (pZip->m_pRead(pZip->m_pIO_opaque, zip64_end_of_central_dir_ofs, + pZip64_end_of_central_dir, + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) == + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE) { + if (MZ_READ_LE32(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_SIG_OFS) == + MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIG) { + pZip->m_pState->m_zip64 = MZ_TRUE; + } + } + } + } + } + + pZip->m_total_files = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_TOTAL_ENTRIES_OFS); + cdir_entries_on_this_disk = + MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS); num_this_disk = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_THIS_DISK_OFS); cdir_disk_index = MZ_READ_LE16(pBuf + MZ_ZIP_ECDH_NUM_DISK_CDIR_OFS); + cdir_size = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_SIZE_OFS); + cdir_ofs = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_OFS_OFS); + + if (pZip->m_pState->m_zip64) { + mz_uint32 zip64_total_num_of_disks = + MZ_READ_LE32(pZip64_locator + MZ_ZIP64_ECDL_TOTAL_NUMBER_OF_DISKS_OFS); + mz_uint64 zip64_cdir_total_entries = MZ_READ_LE64( + pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_TOTAL_ENTRIES_OFS); + mz_uint64 zip64_cdir_total_entries_on_this_disk = MZ_READ_LE64( + pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_NUM_ENTRIES_ON_DISK_OFS); + mz_uint64 zip64_size_of_end_of_central_dir_record = MZ_READ_LE64( + pZip64_end_of_central_dir + MZ_ZIP64_ECDH_SIZE_OF_RECORD_OFS); + mz_uint64 zip64_size_of_central_directory = + MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_SIZE_OFS); + + if (zip64_size_of_end_of_central_dir_record < + (MZ_ZIP64_END_OF_CENTRAL_DIR_HEADER_SIZE - 12)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + + if (zip64_total_num_of_disks != 1U) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + + /* Check for miniz's practical limits */ + if (zip64_cdir_total_entries > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + + pZip->m_total_files = (mz_uint32)zip64_cdir_total_entries; + + if (zip64_cdir_total_entries_on_this_disk > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_TOO_MANY_FILES); + + cdir_entries_on_this_disk = + (mz_uint32)zip64_cdir_total_entries_on_this_disk; + + /* Check for miniz's current practical limits (sorry, this should be enough + * for millions of files) */ + if (zip64_size_of_central_directory > MZ_UINT32_MAX) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_CDIR_SIZE); + + cdir_size = (mz_uint32)zip64_size_of_central_directory; + + num_this_disk = MZ_READ_LE32(pZip64_end_of_central_dir + + MZ_ZIP64_ECDH_NUM_THIS_DISK_OFS); + + cdir_disk_index = MZ_READ_LE32(pZip64_end_of_central_dir + + MZ_ZIP64_ECDH_NUM_DISK_CDIR_OFS); + + cdir_ofs = + MZ_READ_LE64(pZip64_end_of_central_dir + MZ_ZIP64_ECDH_CDIR_OFS_OFS); + } + + if (pZip->m_total_files != cdir_entries_on_this_disk) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + if (((num_this_disk | cdir_disk_index) != 0) && ((num_this_disk != 1) || (cdir_disk_index != 1))) - return MZ_FALSE; + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); - if ((cdir_size = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_SIZE_OFS)) < - pZip->m_total_files * MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) - return MZ_FALSE; + if (cdir_size < pZip->m_total_files * MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); - cdir_ofs = MZ_READ_LE32(pBuf + MZ_ZIP_ECDH_CDIR_OFS_OFS); if ((cdir_ofs + (mz_uint64)cdir_size) > pZip->m_archive_size) - return MZ_FALSE; + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); pZip->m_central_directory_file_ofs = cdir_ofs; if (pZip->m_total_files) { mz_uint i, n; - - // Read the entire central directory into a heap block, and allocate another - // heap block to hold the unsorted central dir file record offsets, and - // another to hold the sorted indices. + /* Read the entire central directory into a heap block, and allocate another + * heap block to hold the unsorted central dir file record offsets, and + * possibly another to hold the sorted indices. */ if ((!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir, cdir_size, MZ_FALSE)) || (!mz_zip_array_resize(pZip, &pZip->m_pState->m_central_dir_offsets, pZip->m_total_files, MZ_FALSE))) - return MZ_FALSE; + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); if (sort_central_dir) { if (!mz_zip_array_resize(pZip, &pZip->m_pState->m_sorted_central_dir_offsets, pZip->m_total_files, MZ_FALSE)) - return MZ_FALSE; + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); } if (pZip->m_pRead(pZip->m_pIO_opaque, cdir_ofs, pZip->m_pState->m_central_dir.m_p, cdir_size) != cdir_size) - return MZ_FALSE; + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); - // Now create an index into the central directory file records, do some - // basic sanity checking on each record, and check for zip64 entries (which - // are not yet supported). + /* Now create an index into the central directory file records, do some + * basic sanity checking on each record */ p = (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p; for (n = cdir_size, i = 0; i < pZip->m_total_files; ++i) { - mz_uint total_header_size, comp_size, decomp_size, disk_index; + mz_uint total_header_size, disk_index, bit_flags, filename_size, + ext_data_size; + mz_uint64 comp_size, decomp_size, local_header_ofs; + if ((n < MZ_ZIP_CENTRAL_DIR_HEADER_SIZE) || (MZ_READ_LE32(p) != MZ_ZIP_CENTRAL_DIR_HEADER_SIG)) - return MZ_FALSE; + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_central_dir_offsets, mz_uint32, i) = (mz_uint32)(p - (const mz_uint8 *)pZip->m_pState->m_central_dir.m_p); + if (sort_central_dir) MZ_ZIP_ARRAY_ELEMENT(&pZip->m_pState->m_sorted_central_dir_offsets, mz_uint32, i) = i; + comp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_COMPRESSED_SIZE_OFS); decomp_size = MZ_READ_LE32(p + MZ_ZIP_CDH_DECOMPRESSED_SIZE_OFS); - if (((!MZ_READ_LE32(p + MZ_ZIP_CDH_METHOD_OFS)) && - (decomp_size != comp_size)) || - (decomp_size && !comp_size) || (decomp_size == 0xFFFFFFFF) || - (comp_size == 0xFFFFFFFF)) - return MZ_FALSE; + local_header_ofs = MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS); + filename_size = MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS); + ext_data_size = MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS); + + if ((!pZip->m_pState->m_zip64_has_extended_info_fields) && + (ext_data_size) && + (MZ_MAX(MZ_MAX(comp_size, decomp_size), local_header_ofs) == + MZ_UINT32_MAX)) { + /* Attempt to find zip64 extended information field in the entry's extra + * data */ + mz_uint32 extra_size_remaining = ext_data_size; + + if (extra_size_remaining) { + const mz_uint8 *pExtra_data; + void *buf = NULL; + + if (MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size + ext_data_size > + n) { + buf = MZ_MALLOC(ext_data_size); + if (buf == NULL) + return mz_zip_set_error(pZip, MZ_ZIP_ALLOC_FAILED); + + if (pZip->m_pRead(pZip->m_pIO_opaque, + cdir_ofs + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + + filename_size, + buf, ext_data_size) != ext_data_size) { + MZ_FREE(buf); + return mz_zip_set_error(pZip, MZ_ZIP_FILE_READ_FAILED); + } + + pExtra_data = (mz_uint8 *)buf; + } else { + pExtra_data = p + MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + filename_size; + } + + do { + mz_uint32 field_id; + mz_uint32 field_data_size; + + if (extra_size_remaining < (sizeof(mz_uint16) * 2)) { + MZ_FREE(buf); + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + field_id = MZ_READ_LE16(pExtra_data); + field_data_size = MZ_READ_LE16(pExtra_data + sizeof(mz_uint16)); + + if ((field_data_size + sizeof(mz_uint16) * 2) > + extra_size_remaining) { + MZ_FREE(buf); + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + if (field_id == MZ_ZIP64_EXTENDED_INFORMATION_FIELD_HEADER_ID) { + /* Ok, the archive didn't have any zip64 headers but it uses a + * zip64 extended information field so mark it as zip64 anyway + * (this can occur with infozip's zip util when it reads + * compresses files from stdin). */ + pZip->m_pState->m_zip64 = MZ_TRUE; + pZip->m_pState->m_zip64_has_extended_info_fields = MZ_TRUE; + break; + } + + pExtra_data += sizeof(mz_uint16) * 2 + field_data_size; + extra_size_remaining = + extra_size_remaining - sizeof(mz_uint16) * 2 - field_data_size; + } while (extra_size_remaining); + + MZ_FREE(buf); + } + } + + /* I've seen archives that aren't marked as zip64 that uses zip64 ext + * data, argh */ + if ((comp_size != MZ_UINT32_MAX) && (decomp_size != MZ_UINT32_MAX)) { + if (((!MZ_READ_LE32(p + MZ_ZIP_CDH_METHOD_OFS)) && + (decomp_size != comp_size)) || + (decomp_size && !comp_size)) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + disk_index = MZ_READ_LE16(p + MZ_ZIP_CDH_DISK_START_OFS); - if ((disk_index != num_this_disk) && (disk_index != 1)) - return MZ_FALSE; - if (((mz_uint64)MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS) + - MZ_ZIP_LOCAL_DIR_HEADER_SIZE + comp_size) > pZip->m_archive_size) - return MZ_FALSE; + if ((disk_index == MZ_UINT16_MAX) || + ((disk_index != num_this_disk) && (disk_index != 1))) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_MULTIDISK); + + if (comp_size != MZ_UINT32_MAX) { + if (((mz_uint64)MZ_READ_LE32(p + MZ_ZIP_CDH_LOCAL_HEADER_OFS) + + MZ_ZIP_LOCAL_DIR_HEADER_SIZE + comp_size) > pZip->m_archive_size) + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + } + + bit_flags = MZ_READ_LE16(p + MZ_ZIP_CDH_BIT_FLAG_OFS); + if (bit_flags & MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_LOCAL_DIR_IS_MASKED) + return mz_zip_set_error(pZip, MZ_ZIP_UNSUPPORTED_ENCRYPTION); + if ((total_header_size = MZ_ZIP_CENTRAL_DIR_HEADER_SIZE + MZ_READ_LE16(p + MZ_ZIP_CDH_FILENAME_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_EXTRA_LEN_OFS) + MZ_READ_LE16(p + MZ_ZIP_CDH_COMMENT_LEN_OFS)) > n) - return MZ_FALSE; + return mz_zip_set_error(pZip, MZ_ZIP_INVALID_HEADER_OR_CORRUPTED); + n -= total_header_size; p += total_header_size; } diff --git a/contrib/zip/src/zip.c b/contrib/zip/src/zip.c index ff3a8fe1e..1abcfd8fd 100644 --- a/contrib/zip/src/zip.c +++ b/contrib/zip/src/zip.c @@ -24,7 +24,6 @@ ((((P)[0] >= 'A' && (P)[0] <= 'Z') || ((P)[0] >= 'a' && (P)[0] <= 'z')) && \ (P)[1] == ':') #define FILESYSTEM_PREFIX_LEN(P) (HAS_DEVICE(P) ? 2 : 0) -#define ISSLASH(C) ((C) == '/' || (C) == '\\') #else @@ -48,7 +47,7 @@ int symlink(const char *target, const char *linkpath); // needed on Linux #endif #ifndef ISSLASH -#define ISSLASH(C) ((C) == '/') +#define ISSLASH(C) ((C) == '/' || (C) == '\\') #endif #define CLEANUP(ptr) \ @@ -78,26 +77,34 @@ static const char *base_name(const char *name) { return base; } -static int mkpath(const char *path) { - char const *p; +static int mkpath(char *path) { + char *p; char npath[MAX_PATH + 1]; int len = 0; int has_device = HAS_DEVICE(path); memset(npath, 0, MAX_PATH + 1); - -#ifdef _WIN32 - // only on windows fix the path - npath[0] = path[0]; - npath[1] = path[1]; - len = 2; -#endif // _WIN32 - + if (has_device) { + // only on windows + npath[0] = path[0]; + npath[1] = path[1]; + len = 2; + } for (p = path + len; *p && len < MAX_PATH; p++) { if (ISSLASH(*p) && ((!has_device && len > 0) || (has_device && len > 2))) { - if (MKDIR(npath) == -1) - if (errno != EEXIST) +#if defined(_WIN32) || defined(__WIN32__) || defined(_MSC_VER) || \ + defined(__MINGW32__) +#else + if ('\\' == *p) { + *p = '/'; + } +#endif + + if (MKDIR(npath) == -1) { + if (errno != EEXIST) { return -1; + } + } } npath[len++] = *p; } @@ -279,7 +286,14 @@ int zip_entry_open(struct zip_t *zip, const char *entryname) { zip->entry.header_offset = zip->archive.m_archive_size; memset(zip->entry.header, 0, MZ_ZIP_LOCAL_DIR_HEADER_SIZE * sizeof(mz_uint8)); zip->entry.method = 0; + + // UNIX or APPLE +#if MZ_PLATFORM == 3 || MZ_PLATFORM == 19 + // regular file with rw-r--r-- persmissions + zip->entry.external_attr = (mz_uint32)(0100644) << 16; +#else zip->entry.external_attr = 0; +#endif num_alignment_padding_bytes = mz_zip_writer_compute_padding_needed_for_file_alignment(pzip); @@ -660,7 +674,7 @@ ssize_t zip_entry_noallocread(struct zip_t *zip, void *buf, size_t bufsize) { } if (!mz_zip_reader_extract_to_mem_no_alloc(pzip, (mz_uint)zip->entry.index, - buf, bufsize, 0, NULL, 0)) { + buf, bufsize, 0, NULL, 0)) { return -1; } @@ -670,10 +684,7 @@ ssize_t zip_entry_noallocread(struct zip_t *zip, void *buf, size_t bufsize) { int zip_entry_fread(struct zip_t *zip, const char *filename) { mz_zip_archive *pzip = NULL; mz_uint idx; -#if defined(_MSC_VER) -#else mz_uint32 xattr = 0; -#endif mz_zip_archive_file_stat info; if (!zip) { @@ -875,12 +886,19 @@ int zip_extract(const char *zipname, const char *dir, goto out; } - if ((((info.m_version_made_by >> 8) == 3) || ((info.m_version_made_by >> 8) == 19)) // if zip is produced on Unix or macOS (3 and 19 from section 4.4.2.2 of zip standard) - && info.m_external_attr & (0x20 << 24)) { // and has sym link attribute (0x80 is file, 0x40 is directory) + if ((((info.m_version_made_by >> 8) == 3) || + ((info.m_version_made_by >> 8) == + 19)) // if zip is produced on Unix or macOS (3 and 19 from + // section 4.4.2.2 of zip standard) + && info.m_external_attr & + (0x20 << 24)) { // and has sym link attribute (0x80 is file, 0x40 + // is directory) #if defined(_WIN32) || defined(__WIN32__) || defined(_MSC_VER) || \ defined(__MINGW32__) -#else - if (info.m_uncomp_size > MAX_PATH || !mz_zip_reader_extract_to_mem_no_alloc(&zip_archive, i, symlink_to, MAX_PATH, 0, NULL, 0)) { +#else + if (info.m_uncomp_size > MAX_PATH || + !mz_zip_reader_extract_to_mem_no_alloc(&zip_archive, i, symlink_to, + MAX_PATH, 0, NULL, 0)) { goto out; } symlink_to[info.m_uncomp_size] = '\0'; diff --git a/contrib/zip/src/zip.h b/contrib/zip/src/zip.h index 5f39df50a..a48d64d6d 100644 --- a/contrib/zip/src/zip.h +++ b/contrib/zip/src/zip.h @@ -20,241 +20,240 @@ extern "C" { #endif #if !defined(_SSIZE_T_DEFINED) && !defined(_SSIZE_T_DEFINED_) && \ - !defined(_SSIZE_T) && !defined(_SSIZE_T_) && !defined(__ssize_t_defined) -#define _SSIZE_T + !defined(__DEFINED_ssize_t) && !defined(__ssize_t_defined) && \ + !defined(_SSIZE_T) && !defined(_SSIZE_T_) + // 64-bit Windows is the only mainstream platform // where sizeof(long) != sizeof(void*) #ifdef _WIN64 -typedef long long ssize_t; /* byte count or error */ +typedef long long ssize_t; /* byte count or error */ #else -typedef long ssize_t; /* byte count or error */ +typedef long ssize_t; /* byte count or error */ #endif + +#define _SSIZE_T_DEFINED +#define _SSIZE_T_DEFINED_ +#define __DEFINED_ssize_t +#define __ssize_t_defined +#define _SSIZE_T +#define _SSIZE_T_ + #endif #ifndef MAX_PATH #define MAX_PATH 32767 /* # chars in a path name including NULL */ #endif +/** + * @mainpage + * + * Documenation for @ref zip. + */ + +/** + * @addtogroup zip + * @{ + */ + +/** + * Default zip compression level. + */ + #define ZIP_DEFAULT_COMPRESSION_LEVEL 6 -/* - This data structure is used throughout the library to represent zip archive - - forward declaration. -*/ +/** + * @struct zip_t + * + * This data structure is used throughout the library to represent zip archive - + * forward declaration. + */ struct zip_t; -/* - Opens zip archive with compression level using the given mode. - - Args: - zipname: zip archive file name. - level: compression level (0-9 are the standard zlib-style levels). - mode: file access mode. - 'r': opens a file for reading/extracting (the file must exists). - 'w': creates an empty file for writing. - 'a': appends to an existing archive. - - Returns: - The zip archive handler or NULL on error -*/ +/** + * Opens zip archive with compression level using the given mode. + * + * @param zipname zip archive file name. + * @param level compression level (0-9 are the standard zlib-style levels). + * @param mode file access mode. + * - 'r': opens a file for reading/extracting (the file must exists). + * - 'w': creates an empty file for writing. + * - 'a': appends to an existing archive. + * + * @return the zip archive handler or NULL on error + */ extern struct zip_t *zip_open(const char *zipname, int level, char mode); -/* - Closes the zip archive, releases resources - always finalize. - - Args: - zip: zip archive handler. -*/ +/** + * Closes the zip archive, releases resources - always finalize. + * + * @param zip zip archive handler. + */ extern void zip_close(struct zip_t *zip); -/* - Opens an entry by name in the zip archive. - For zip archive opened in 'w' or 'a' mode the function will append - a new entry. In readonly mode the function tries to locate the entry - in global dictionary. - - Args: - zip: zip archive handler. - entryname: an entry name in local dictionary. - - Returns: - The return code - 0 on success, negative number (< 0) on error. -*/ +/** + * Opens an entry by name in the zip archive. + * + * For zip archive opened in 'w' or 'a' mode the function will append + * a new entry. In readonly mode the function tries to locate the entry + * in global dictionary. + * + * @param zip zip archive handler. + * @param entryname an entry name in local dictionary. + * + * @return the return code - 0 on success, negative number (< 0) on error. + */ extern int zip_entry_open(struct zip_t *zip, const char *entryname); -/* - Opens a new entry by index in the zip archive. - This function is only valid if zip archive was opened in 'r' (readonly) mode. - - Args: - zip: zip archive handler. - index: index in local dictionary. - - Returns: - The return code - 0 on success, negative number (< 0) on error. -*/ +/** + * Opens a new entry by index in the zip archive. + * + * This function is only valid if zip archive was opened in 'r' (readonly) mode. + * + * @param zip zip archive handler. + * @param index index in local dictionary. + * + * @return the return code - 0 on success, negative number (< 0) on error. + */ extern int zip_entry_openbyindex(struct zip_t *zip, int index); -/* - Closes a zip entry, flushes buffer and releases resources. - - Args: - zip: zip archive handler. - - Returns: - The return code - 0 on success, negative number (< 0) on error. -*/ +/** + * Closes a zip entry, flushes buffer and releases resources. + * + * @param zip zip archive handler. + * + * @return the return code - 0 on success, negative number (< 0) on error. + */ extern int zip_entry_close(struct zip_t *zip); -/* - Returns a local name of the current zip entry. - The main difference between user's entry name and local entry name - is optional relative path. - Following .ZIP File Format Specification - the path stored MUST not contain - a drive or device letter, or a leading slash. - All slashes MUST be forward slashes '/' as opposed to backwards slashes '\' - for compatibility with Amiga and UNIX file systems etc. - - Args: - zip: zip archive handler. - - Returns: - The pointer to the current zip entry name, or NULL on error. -*/ +/** + * Returns a local name of the current zip entry. + * + * The main difference between user's entry name and local entry name + * is optional relative path. + * Following .ZIP File Format Specification - the path stored MUST not contain + * a drive or device letter, or a leading slash. + * All slashes MUST be forward slashes '/' as opposed to backwards slashes '\' + * for compatibility with Amiga and UNIX file systems etc. + * + * @param zip: zip archive handler. + * + * @return the pointer to the current zip entry name, or NULL on error. + */ extern const char *zip_entry_name(struct zip_t *zip); -/* - Returns an index of the current zip entry. - - Args: - zip: zip archive handler. - - Returns: - The index on success, negative number (< 0) on error. -*/ +/** + * Returns an index of the current zip entry. + * + * @param zip zip archive handler. + * + * @return the index on success, negative number (< 0) on error. + */ extern int zip_entry_index(struct zip_t *zip); -/* - Determines if the current zip entry is a directory entry. - - Args: - zip: zip archive handler. - - Returns: - The return code - 1 (true), 0 (false), negative number (< 0) on error. -*/ +/** + * Determines if the current zip entry is a directory entry. + * + * @param zip zip archive handler. + * + * @return the return code - 1 (true), 0 (false), negative number (< 0) on + * error. + */ extern int zip_entry_isdir(struct zip_t *zip); -/* - Returns an uncompressed size of the current zip entry. - - Args: - zip: zip archive handler. - - Returns: - The uncompressed size in bytes. -*/ +/** + * Returns an uncompressed size of the current zip entry. + * + * @param zip zip archive handler. + * + * @return the uncompressed size in bytes. + */ extern unsigned long long zip_entry_size(struct zip_t *zip); -/* - Returns CRC-32 checksum of the current zip entry. - - Args: - zip: zip archive handler. - - Returns: - The CRC-32 checksum. -*/ +/** + * Returns CRC-32 checksum of the current zip entry. + * + * @param zip zip archive handler. + * + * @return the CRC-32 checksum. + */ extern unsigned int zip_entry_crc32(struct zip_t *zip); -/* - Compresses an input buffer for the current zip entry. - - Args: - zip: zip archive handler. - buf: input buffer. - bufsize: input buffer size (in bytes). - - Returns: - The return code - 0 on success, negative number (< 0) on error. -*/ +/** + * Compresses an input buffer for the current zip entry. + * + * @param zip zip archive handler. + * @param buf input buffer. + * @param bufsize input buffer size (in bytes). + * + * @return the return code - 0 on success, negative number (< 0) on error. + */ extern int zip_entry_write(struct zip_t *zip, const void *buf, size_t bufsize); -/* - Compresses a file for the current zip entry. - - Args: - zip: zip archive handler. - filename: input file. - - Returns: - The return code - 0 on success, negative number (< 0) on error. -*/ +/** + * Compresses a file for the current zip entry. + * + * @param zip zip archive handler. + * @param filename input file. + * + * @return the return code - 0 on success, negative number (< 0) on error. + */ extern int zip_entry_fwrite(struct zip_t *zip, const char *filename); -/* - Extracts the current zip entry into output buffer. - The function allocates sufficient memory for a output buffer. - - Args: - zip: zip archive handler. - buf: output buffer. - bufsize: output buffer size (in bytes). - - Note: - - remember to release memory allocated for a output buffer. - - for large entries, please take a look at zip_entry_extract function. - - Returns: - The return code - the number of bytes actually read on success. - Otherwise a -1 on error. -*/ +/** + * Extracts the current zip entry into output buffer. + * + * The function allocates sufficient memory for a output buffer. + * + * @param zip zip archive handler. + * @param buf output buffer. + * @param bufsize output buffer size (in bytes). + * + * @note remember to release memory allocated for a output buffer. + * for large entries, please take a look at zip_entry_extract function. + * + * @return the return code - the number of bytes actually read on success. + * Otherwise a -1 on error. + */ extern ssize_t zip_entry_read(struct zip_t *zip, void **buf, size_t *bufsize); -/* - Extracts the current zip entry into a memory buffer using no memory - allocation. +/** + * Extracts the current zip entry into a memory buffer using no memory + * allocation. + * + * @param zip zip archive handler. + * @param buf preallocated output buffer. + * @param bufsize output buffer size (in bytes). + * + * @note ensure supplied output buffer is large enough. + * zip_entry_size function (returns uncompressed size for the current + * entry) can be handy to estimate how big buffer is needed. for large + * entries, please take a look at zip_entry_extract function. + * + * @return the return code - the number of bytes actually read on success. + * Otherwise a -1 on error (e.g. bufsize is not large enough). + */ +extern ssize_t zip_entry_noallocread(struct zip_t *zip, void *buf, + size_t bufsize); - Args: - zip: zip archive handler. - buf: preallocated output buffer. - bufsize: output buffer size (in bytes). - - Note: - - ensure supplied output buffer is large enough. - - zip_entry_size function (returns uncompressed size for the current entry) - can be handy to estimate how big buffer is needed. - - for large entries, please take a look at zip_entry_extract function. - - Returns: - The return code - the number of bytes actually read on success. - Otherwise a -1 on error (e.g. bufsize is not large enough). -*/ -extern ssize_t zip_entry_noallocread(struct zip_t *zip, void *buf, size_t bufsize); - -/* - Extracts the current zip entry into output file. - - Args: - zip: zip archive handler. - filename: output file. - - Returns: - The return code - 0 on success, negative number (< 0) on error. -*/ +/** + * Extracts the current zip entry into output file. + * + * @param zip zip archive handler. + * @param filename output file. + * + * @return the return code - 0 on success, negative number (< 0) on error. + */ extern int zip_entry_fread(struct zip_t *zip, const char *filename); -/* - Extracts the current zip entry using a callback function (on_extract). - - Args: - zip: zip archive handler. - on_extract: callback function. - arg: opaque pointer (optional argument, - which you can pass to the on_extract callback) - - Returns: - The return code - 0 on success, negative number (< 0) on error. +/** + * Extracts the current zip entry using a callback function (on_extract). + * + * @param zip zip archive handler. + * @param on_extract callback function. + * @param arg opaque pointer (optional argument, which you can pass to the + * on_extract callback) + * + * @return the return code - 0 on success, negative number (< 0) on error. */ extern int zip_entry_extract(struct zip_t *zip, @@ -262,53 +261,49 @@ zip_entry_extract(struct zip_t *zip, const void *data, size_t size), void *arg); -/* - Returns the number of all entries (files and directories) in the zip archive. - - Args: - zip: zip archive handler. - - Returns: - The return code - the number of entries on success, - negative number (< 0) on error. -*/ +/** + * Returns the number of all entries (files and directories) in the zip archive. + * + * @param zip zip archive handler. + * + * @return the return code - the number of entries on success, negative number + * (< 0) on error. + */ extern int zip_total_entries(struct zip_t *zip); -/* - Creates a new archive and puts files into a single zip archive. - - Args: - zipname: zip archive file. - filenames: input files. - len: number of input files. - - Returns: - The return code - 0 on success, negative number (< 0) on error. -*/ +/** + * Creates a new archive and puts files into a single zip archive. + * + * @param zipname zip archive file. + * @param filenames input files. + * @param len: number of input files. + * + * @return the return code - 0 on success, negative number (< 0) on error. + */ extern int zip_create(const char *zipname, const char *filenames[], size_t len); -/* - Extracts a zip archive file into directory. - - If on_extract_entry is not NULL, the callback will be called after - successfully extracted each zip entry. - Returning a negative value from the callback will cause abort and return an - error. The last argument (void *arg) is optional, which you can use to pass - data to the on_extract_entry callback. - - Args: - zipname: zip archive file. - dir: output directory. - on_extract_entry: on extract callback. - arg: opaque pointer. - - Returns: - The return code - 0 on success, negative number (< 0) on error. -*/ +/** + * Extracts a zip archive file into directory. + * + * If on_extract_entry is not NULL, the callback will be called after + * successfully extracted each zip entry. + * Returning a negative value from the callback will cause abort and return an + * error. The last argument (void *arg) is optional, which you can use to pass + * data to the on_extract_entry callback. + * + * @param zipname zip archive file. + * @param dir output directory. + * @param on_extract_entry on extract callback. + * @param arg opaque pointer. + * + * @return the return code - 0 on success, negative number (< 0) on error. + */ extern int zip_extract(const char *zipname, const char *dir, int (*on_extract_entry)(const char *filename, void *arg), void *arg); +/** @} */ + #ifdef __cplusplus } #endif diff --git a/contrib/zip/test/CMakeLists.txt b/contrib/zip/test/CMakeLists.txt index 9b2a8db10..cc060b00f 100644 --- a/contrib/zip/test/CMakeLists.txt +++ b/contrib/zip/test/CMakeLists.txt @@ -1,19 +1,16 @@ cmake_minimum_required(VERSION 2.8) -if ("${CMAKE_C_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "AppleClang") - if(ENABLE_COVERAGE) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g ") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fprofile-arcs") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ftest-coverage") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage") - endif() -endif () - # test -include_directories(../src) -add_executable(test.exe test.c ../src/zip.c) -add_executable(test_miniz.exe test_miniz.c) +set(test_out test.out) +set(test_miniz_out test_miniz.out) -add_test(NAME test COMMAND test.exe) -add_test(NAME test_miniz COMMAND test_miniz.exe) +add_executable(${test_out} test.c) +target_link_libraries(${test_out} zip) +add_executable(${test_miniz_out} test_miniz.c) +target_link_libraries(${test_miniz_out} zip) + +add_test(NAME ${test_out} COMMAND ${test_out}) +add_test(NAME ${test_miniz_out} COMMAND ${test_miniz_out}) + +set(test_out ${test_out} PARENT_SCOPE) +set(test_miniz_out ${test_miniz_out} PARENT_SCOPE) diff --git a/contrib/zip/test/test.c b/contrib/zip/test/test.c index 454430533..a9b2ddab1 100644 --- a/contrib/zip/test/test.c +++ b/contrib/zip/test/test.c @@ -29,6 +29,8 @@ #define XFILE "7.txt\0" #define XMODE 0100777 +#define UNIXMODE 0100644 + #define UNUSED(x) (void)x static int total_entries = 0; @@ -102,7 +104,8 @@ static void test_read(void) { assert(0 == zip_entry_close(zip)); free(buf); buf = NULL; - + bufsize = 0; + assert(0 == zip_entry_open(zip, "test/test-2.txt")); assert(strlen(TESTDATA2) == zip_entry_size(zip)); assert(CRC32DATA2 == zip_entry_crc32(zip)); @@ -131,7 +134,8 @@ static void test_read(void) { assert(0 == zip_entry_close(zip)); free(buf); buf = NULL; - + bufsize = 0; + buftmp = strlen(TESTDATA1); buf = calloc(buftmp, sizeof(char)); assert(0 == zip_entry_open(zip, "test/test-1.txt")); @@ -433,6 +437,35 @@ static void test_mtime(void) { remove(ZIPNAME); } +static void test_unix_permissions(void) { +#if defined(_WIN64) || defined(_WIN32) || defined(__WIN32__) +#else + // UNIX or APPLE + struct MZ_FILE_STAT_STRUCT file_stats; + + remove(ZIPNAME); + + struct zip_t *zip = zip_open(ZIPNAME, ZIP_DEFAULT_COMPRESSION_LEVEL, 'w'); + assert(zip != NULL); + + assert(0 == zip_entry_open(zip, RFILE)); + assert(0 == zip_entry_write(zip, TESTDATA1, strlen(TESTDATA1))); + assert(0 == zip_entry_close(zip)); + + zip_close(zip); + + remove(RFILE); + + assert(0 == zip_extract(ZIPNAME, ".", NULL, NULL)); + + assert(0 == MZ_FILE_STAT(RFILE, &file_stats)); + assert(UNIXMODE == file_stats.st_mode); + + remove(RFILE); + remove(ZIPNAME); +#endif +} + int main(int argc, char *argv[]) { UNUSED(argc); UNUSED(argv); @@ -453,6 +486,7 @@ int main(int argc, char *argv[]) { test_write_permissions(); test_exe_permissions(); test_mtime(); + test_unix_permissions(); remove(ZIPNAME); return 0; diff --git a/contrib/zip/test/test_miniz.c b/contrib/zip/test/test_miniz.c index ebc0564dc..babcaecdb 100644 --- a/contrib/zip/test/test_miniz.c +++ b/contrib/zip/test/test_miniz.c @@ -23,16 +23,39 @@ int main(int argc, char *argv[]) { uint step = 0; int cmp_status; uLong src_len = (uLong)strlen(s_pStr); - uLong cmp_len = compressBound(src_len); uLong uncomp_len = src_len; + uLong cmp_len; uint8 *pCmp, *pUncomp; + size_t sz; uint total_succeeded = 0; (void)argc, (void)argv; printf("miniz.c version: %s\n", MZ_VERSION); do { + pCmp = (uint8 *)tdefl_compress_mem_to_heap(s_pStr, src_len, &cmp_len, 0); + if (!pCmp) { + printf("tdefl_compress_mem_to_heap failed\n"); + return EXIT_FAILURE; + } + if (src_len <= cmp_len) { + printf("tdefl_compress_mem_to_heap failed: from %u to %u bytes\n", + (mz_uint32)uncomp_len, (mz_uint32)cmp_len); + free(pCmp); + return EXIT_FAILURE; + } + + sz = tdefl_compress_mem_to_mem(pCmp, cmp_len, s_pStr, src_len, 0); + if (sz != cmp_len) { + printf("tdefl_compress_mem_to_mem failed: expected %u, got %u\n", + (mz_uint32)cmp_len, (mz_uint32)sz); + free(pCmp); + return EXIT_FAILURE; + } + // Allocate buffers to hold compressed and uncompressed data. + free(pCmp); + cmp_len = compressBound(src_len); pCmp = (mz_uint8 *)malloc((size_t)cmp_len); pUncomp = (mz_uint8 *)malloc((size_t)src_len); if ((!pCmp) || (!pUncomp)) { diff --git a/include/assimp/Exporter.hpp b/include/assimp/Exporter.hpp index 2612e1f9d..20e7c6c6f 100644 --- a/include/assimp/Exporter.hpp +++ b/include/assimp/Exporter.hpp @@ -5,8 +5,6 @@ Open Asset Import Library (assimp) Copyright (c) 2006-2019, assimp team - - All rights reserved. Redistribution and use of this software in source and binary forms, diff --git a/include/assimp/defs.h b/include/assimp/defs.h index 71579a5c7..d8fc98179 100644 --- a/include/assimp/defs.h +++ b/include/assimp/defs.h @@ -128,16 +128,14 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * GENBOUNDINGBOXES */ ////////////////////////////////////////////////////////////////////////// -#ifdef _MSC_VER +#ifdef _WIN32 # undef ASSIMP_API - ////////////////////////////////////////////////////////////////////////// /* Define 'ASSIMP_BUILD_DLL_EXPORT' to build a DLL of the library */ ////////////////////////////////////////////////////////////////////////// # ifdef ASSIMP_BUILD_DLL_EXPORT # define ASSIMP_API __declspec(dllexport) # define ASSIMP_API_WINONLY __declspec(dllexport) -# pragma warning (disable : 4251) ////////////////////////////////////////////////////////////////////////// /* Define 'ASSIMP_DLL' before including Assimp to link to ASSIMP in @@ -150,7 +148,19 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # define ASSIMP_API # define ASSIMP_API_WINONLY # endif +#elif defined(SWIG) + /* Do nothing, the relevant defines are all in AssimpSwigPort.i */ + +#else +# define ASSIMP_API __attribute__ ((visibility("default"))) +# define ASSIMP_API_WINONLY +#endif + +#ifdef _MSC_VER +# ifdef ASSIMP_BUILD_DLL_EXPORT +# pragma warning (disable : 4251) +# endif /* Force the compiler to inline a function, if possible */ # define AI_FORCE_INLINE __forceinline @@ -158,17 +168,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /* Tells the compiler that a function never returns. Used in code analysis * to skip dead paths (e.g. after an assertion evaluated to false). */ # define AI_WONT_RETURN __declspec(noreturn) - #elif defined(SWIG) /* Do nothing, the relevant defines are all in AssimpSwigPort.i */ #else - # define AI_WONT_RETURN - -# define ASSIMP_API __attribute__ ((visibility("default"))) -# define ASSIMP_API_WINONLY # define AI_FORCE_INLINE inline #endif // (defined _MSC_VER) @@ -301,7 +306,11 @@ static const ai_real ai_epsilon = (ai_real) 0.00001; #define AI_MAX_ALLOC(type) ((256U * 1024 * 1024) / sizeof(type)) #ifndef _MSC_VER -# define AI_NO_EXCEPT noexcept +# if __cplusplus >= 201103L // C++11 +# define AI_NO_EXCEPT noexcept +# else +# define AI_NO_EXCEPT +# endif #else # if (_MSC_VER >= 1915 ) # define AI_NO_EXCEPT noexcept diff --git a/include/assimp/mesh.h b/include/assimp/mesh.h index eb30ad5df..fbf2a857a 100644 --- a/include/assimp/mesh.h +++ b/include/assimp/mesh.h @@ -252,6 +252,9 @@ struct aiVertexWeight { }; +// Forward declare aiNode (pointer use only) +struct aiNode; + // --------------------------------------------------------------------------- /** @brief A single bone of a mesh. * @@ -268,6 +271,16 @@ struct aiBone { //! The maximum value for this member is #AI_MAX_BONE_WEIGHTS. unsigned int mNumWeights; +#ifndef ASSIMP_BUILD_NO_ARMATUREPOPULATE_PROCESS + // The bone armature node - used for skeleton conversion + // you must enable aiProcess_PopulateArmatureData to populate this + C_STRUCT aiNode* mArmature; + + // The bone node in the scene - used for skeleton conversion + // you must enable aiProcess_PopulateArmatureData to populate this + C_STRUCT aiNode* mNode; + +#endif //! The influence weights of this bone, by vertex index. C_STRUCT aiVertexWeight* mWeights; @@ -422,11 +435,11 @@ struct aiAnimMesh /**Anim Mesh name */ C_STRUCT aiString mName; - /** Replacement for aiMesh::mVertices. If this array is non-NULL, + /** Replacement for aiMesh::mVertices. If this array is non-nullptr, * it *must* contain mNumVertices entries. The corresponding - * array in the host mesh must be non-NULL as well - animation + * array in the host mesh must be non-nullptr as well - animation * meshes may neither add or nor remove vertex components (if - * a replacement array is NULL and the corresponding source + * a replacement array is nullptr and the corresponding source * array is not, the source data is taken instead)*/ C_STRUCT aiVector3D* mVertices; @@ -600,7 +613,7 @@ struct aiMesh C_STRUCT aiVector3D* mVertices; /** Vertex normals. - * The array contains normalized vectors, NULL if not present. + * The array contains normalized vectors, nullptr if not present. * The array is mNumVertices in size. Normals are undefined for * point and line primitives. A mesh consisting of points and * lines only may not have normal vectors. Meshes with mixed @@ -623,7 +636,7 @@ struct aiMesh /** Vertex tangents. * The tangent of a vertex points in the direction of the positive - * X texture axis. The array contains normalized vectors, NULL if + * X texture axis. The array contains normalized vectors, nullptr if * not present. The array is mNumVertices in size. A mesh consisting * of points and lines only may not have normal vectors. Meshes with * mixed primitive types (i.e. lines and triangles) may have @@ -637,7 +650,7 @@ struct aiMesh /** Vertex bitangents. * The bitangent of a vertex points in the direction of the positive - * Y texture axis. The array contains normalized vectors, NULL if not + * Y texture axis. The array contains normalized vectors, nullptr if not * present. The array is mNumVertices in size. * @note If the mesh contains tangents, it automatically also contains * bitangents. @@ -646,14 +659,14 @@ struct aiMesh /** Vertex color sets. * A mesh may contain 0 to #AI_MAX_NUMBER_OF_COLOR_SETS vertex - * colors per vertex. NULL if not present. Each array is + * colors per vertex. nullptr if not present. Each array is * mNumVertices in size if present. */ C_STRUCT aiColor4D* mColors[AI_MAX_NUMBER_OF_COLOR_SETS]; /** Vertex texture coords, also known as UV channels. * A mesh may contain 0 to AI_MAX_NUMBER_OF_TEXTURECOORDS per - * vertex. NULL if not present. The array is mNumVertices in size. + * vertex. nullptr if not present. The array is mNumVertices in size. */ C_STRUCT aiVector3D* mTextureCoords[AI_MAX_NUMBER_OF_TEXTURECOORDS]; @@ -675,7 +688,7 @@ struct aiMesh C_STRUCT aiFace* mFaces; /** The number of bones this mesh contains. - * Can be 0, in which case the mBones array is NULL. + * Can be 0, in which case the mBones array is nullptr. */ unsigned int mNumBones; @@ -773,7 +786,10 @@ struct aiMesh // DO NOT REMOVE THIS ADDITIONAL CHECK if (mNumBones && mBones) { for( unsigned int a = 0; a < mNumBones; a++) { - delete mBones[a]; + if(mBones[a]) + { + delete mBones[a]; + } } delete [] mBones; } diff --git a/include/assimp/postprocess.h b/include/assimp/postprocess.h index 77d387c7e..4b6732e80 100644 --- a/include/assimp/postprocess.h +++ b/include/assimp/postprocess.h @@ -320,6 +320,19 @@ enum aiPostProcessSteps */ aiProcess_FixInfacingNormals = 0x2000, + + + // ------------------------------------------------------------------------- + /** + * This step generically populates aiBone->mArmature and aiBone->mNode generically + * The point of these is it saves you later having to calculate these elements + * This is useful when handling rest information or skin information + * If you have multiple armatures on your models we strongly recommend enabling this + * Instead of writing your own multi-root, multi-armature lookups we have done the + * hard work for you :) + */ + aiProcess_PopulateArmatureData = 0x4000, + // ------------------------------------------------------------------------- /**
This step splits meshes with more than one primitive type in * homogeneous sub-meshes. @@ -537,6 +550,8 @@ enum aiPostProcessSteps */ aiProcess_Debone = 0x4000000, + + // ------------------------------------------------------------------------- /**
This step will perform a global scale of the model. * diff --git a/include/assimp/scene.h b/include/assimp/scene.h index e69c81803..b76709eb1 100644 --- a/include/assimp/scene.h +++ b/include/assimp/scene.h @@ -110,13 +110,13 @@ struct ASSIMP_API aiNode /** The transformation relative to the node's parent. */ C_STRUCT aiMatrix4x4 mTransformation; - /** Parent node. NULL if this node is the root node. */ + /** Parent node. nullptr if this node is the root node. */ C_STRUCT aiNode* mParent; /** The number of child nodes of this node. */ unsigned int mNumChildren; - /** The child nodes of this node. NULL if mNumChildren is 0. */ + /** The child nodes of this node. nullptr if mNumChildren is 0. */ C_STRUCT aiNode** mChildren; /** The number of meshes of this node. */ @@ -127,7 +127,7 @@ struct ASSIMP_API aiNode */ unsigned int* mMeshes; - /** Metadata associated with this node or NULL if there is no metadata. + /** Metadata associated with this node or nullptr if there is no metadata. * Whether any metadata is generated depends on the source file format. See the * @link importer_notes @endlink page for more information on every source file * format. Importers that don't document any metadata don't write any. @@ -149,7 +149,7 @@ struct ASSIMP_API aiNode * of the scene. * * @param name Name to search for - * @return NULL or a valid Node if the search was successful. + * @return nullptr or a valid Node if the search was successful. */ inline const aiNode* FindNode(const aiString& name) const { @@ -344,7 +344,7 @@ struct aiScene #ifdef __cplusplus - //! Default constructor - set everything to 0/NULL + //! Default constructor - set everything to 0/nullptr ASSIMP_API aiScene(); //! Destructor @@ -353,33 +353,33 @@ struct aiScene //! Check whether the scene contains meshes //! Unless no special scene flags are set this will always be true. inline bool HasMeshes() const { - return mMeshes != NULL && mNumMeshes > 0; + return mMeshes != nullptr && mNumMeshes > 0; } //! Check whether the scene contains materials //! Unless no special scene flags are set this will always be true. inline bool HasMaterials() const { - return mMaterials != NULL && mNumMaterials > 0; + return mMaterials != nullptr && mNumMaterials > 0; } //! Check whether the scene contains lights inline bool HasLights() const { - return mLights != NULL && mNumLights > 0; + return mLights != nullptr && mNumLights > 0; } //! Check whether the scene contains textures inline bool HasTextures() const { - return mTextures != NULL && mNumTextures > 0; + return mTextures != nullptr && mNumTextures > 0; } //! Check whether the scene contains cameras inline bool HasCameras() const { - return mCameras != NULL && mNumCameras > 0; + return mCameras != nullptr && mNumCameras > 0; } //! Check whether the scene contains animations inline bool HasAnimations() const { - return mAnimations != NULL && mNumAnimations > 0; + return mAnimations != nullptr && mNumAnimations > 0; } //! Returns a short filename from a full path diff --git a/include/assimp/version.h b/include/assimp/version.h index 2fdd37a43..90645a38f 100644 --- a/include/assimp/version.h +++ b/include/assimp/version.h @@ -62,6 +62,13 @@ extern "C" { */ ASSIMP_API const char* aiGetLegalString (void); +// --------------------------------------------------------------------------- +/** @brief Returns the current patch version number of Assimp. + * @return Patch version of the Assimp runtime the application was + * linked/built against + */ +ASSIMP_API unsigned int aiGetVersionPatch(void); + // --------------------------------------------------------------------------- /** @brief Returns the current minor version number of Assimp. * @return Minor version of the Assimp runtime the application was diff --git a/samples/SimpleTexturedDirectx11/CMakeLists.txt b/samples/SimpleTexturedDirectx11/CMakeLists.txt new file mode 100644 index 000000000..373b5a9db --- /dev/null +++ b/samples/SimpleTexturedDirectx11/CMakeLists.txt @@ -0,0 +1,46 @@ +FIND_PACKAGE(DirectX) + +IF ( MSVC ) + SET(M_LIB) +ENDIF ( MSVC ) + +if ( MSVC ) + ADD_DEFINITIONS( -D_SCL_SECURE_NO_WARNINGS ) + ADD_DEFINITIONS( -D_CRT_SECURE_NO_WARNINGS ) + REMOVE_DEFINITIONS( -DUNICODE -D_UNICODE ) +endif ( MSVC ) + +INCLUDE_DIRECTORIES( + ${Assimp_SOURCE_DIR}/include + ${Assimp_SOURCE_DIR}/code + ${OPENGL_INCLUDE_DIR} + ${GLUT_INCLUDE_DIR} + ${Assimp_SOURCE_DIR}/samples/freeglut/include +) + +LINK_DIRECTORIES( + ${Assimp_BINARY_DIR} + ${Assimp_BINARY_DIR}/lib +) + +ADD_EXECUTABLE( assimp_simpletextureddirectx11 WIN32 + SimpleTexturedDirectx11/Mesh.h + SimpleTexturedDirectx11/ModelLoader.cpp + SimpleTexturedDirectx11/ModelLoader.h + #SimpleTexturedDirectx11/PixelShader.hlsl + SimpleTexturedDirectx11/TextureLoader.cpp + SimpleTexturedDirectx11/TextureLoader.h + #SimpleTexturedDirectx11/VertexShader.hlsl + SimpleTexturedDirectx11/main.cpp +) + +SET_PROPERTY(TARGET assimp_simpletextureddirectx11 PROPERTY DEBUG_POSTFIX ${CMAKE_DEBUG_POSTFIX}) + +TARGET_LINK_LIBRARIES( assimp_simpletextureddirectx11 assimp ${DirectX_LIBRARY} comctl32.lib winmm.lib ) +SET_TARGET_PROPERTIES( assimp_simpletextureddirectx11 PROPERTIES + OUTPUT_NAME assimp_simpletextureddirectx11 +) + +INSTALL( TARGETS assimp_simpletextureddirectx11 + DESTINATION "${ASSIMP_BIN_INSTALL_DIR}" COMPONENT assimp-dev +) diff --git a/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11.sln b/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11.sln deleted file mode 100644 index 381ac8f94..000000000 --- a/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11.sln +++ /dev/null @@ -1,28 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26228.9 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SimpleTexturedDirectx11", "SimpleTexturedDirectx11\SimpleTexturedDirectx11.vcxproj", "{E3B160B5-E71F-4F3F-9310-B8F156F736D8}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {E3B160B5-E71F-4F3F-9310-B8F156F736D8}.Debug|x64.ActiveCfg = Debug|x64 - {E3B160B5-E71F-4F3F-9310-B8F156F736D8}.Debug|x64.Build.0 = Debug|x64 - {E3B160B5-E71F-4F3F-9310-B8F156F736D8}.Debug|x86.ActiveCfg = Debug|Win32 - {E3B160B5-E71F-4F3F-9310-B8F156F736D8}.Debug|x86.Build.0 = Debug|Win32 - {E3B160B5-E71F-4F3F-9310-B8F156F736D8}.Release|x64.ActiveCfg = Release|x64 - {E3B160B5-E71F-4F3F-9310-B8F156F736D8}.Release|x64.Build.0 = Release|x64 - {E3B160B5-E71F-4F3F-9310-B8F156F736D8}.Release|x86.ActiveCfg = Release|Win32 - {E3B160B5-E71F-4F3F-9310-B8F156F736D8}.Release|x86.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/ModelLoader.cpp b/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/ModelLoader.cpp index a2d3faeb3..10ba07a98 100644 --- a/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/ModelLoader.cpp +++ b/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/ModelLoader.cpp @@ -180,6 +180,8 @@ string ModelLoader::determineTextureType(const aiScene * scene, aiMaterial * mat { return "textures are on disk"; } + + return "."; } int ModelLoader::getTextureIndex(aiString * str) diff --git a/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/SimpleTexturedDirectx11.vcxproj b/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/SimpleTexturedDirectx11.vcxproj deleted file mode 100644 index 6584b7d7c..000000000 --- a/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/SimpleTexturedDirectx11.vcxproj +++ /dev/null @@ -1,146 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 15.0 - {E3B160B5-E71F-4F3F-9310-B8F156F736D8} - SimpleTexturedDirectx11 - 10.0.14393.0 - - - - Application - true - v141 - MultiByte - - - Application - false - v141 - true - MultiByte - - - Application - true - v141 - MultiByte - - - Application - false - v141 - true - MultiByte - - - - - - - - - - - - - - - - - - - - - $(IncludePath);E:\OpenGL VS Files\include - $(LibraryPath);E:\OpenGL VS Files\lib - - - - Level3 - Disabled - true - - - assimp-vc140-mt.lib;%(AdditionalDependencies) - - - - - Level3 - Disabled - true - - - - - Level3 - MaxSpeed - true - true - true - - - true - true - - - - - Level3 - MaxSpeed - true - true - true - - - true - true - - - - - - - - - - Pixel - Pixel - Pixel - Pixel - - - Vertex - Vertex - Vertex - Vertex - - - - - - - - - - - \ No newline at end of file diff --git a/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/SimpleTexturedDirectx11.vcxproj.filters b/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/SimpleTexturedDirectx11.vcxproj.filters deleted file mode 100644 index 3568b73c5..000000000 --- a/samples/SimpleTexturedDirectx11/SimpleTexturedDirectx11/SimpleTexturedDirectx11.vcxproj.filters +++ /dev/null @@ -1,50 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - {b6a86d3e-70a5-4d1e-ba05-c20902300206} - - - - - Source Files - - - Source Files - - - Source Files - - - - - Shaders - - - Shaders - - - - - Header Files - - - Header Files - - - Header Files - - - \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 913813c3b..23caae88c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -118,6 +118,7 @@ SET( IMPORTERS unit/utColladaImportExport.cpp unit/utCSMImportExport.cpp unit/utB3DImportExport.cpp + unit/utM3DImportExport.cpp unit/utMDCImportExport.cpp unit/utAssbinImportExport.cpp unit/ImportExport/utAssjsonImportExport.cpp @@ -148,6 +149,7 @@ SET( POST_PROCESSES unit/utRemoveRedundantMaterials.cpp unit/utRemoveVCProcess.cpp unit/utScaleProcess.cpp + unit/utArmaturePopulate.cpp unit/utJoinVertices.cpp unit/utRemoveComments.cpp unit/utRemoveComponent.cpp diff --git a/test/models/Collada/duck.zae b/test/models/Collada/duck.zae index 978235355..7e6748cb0 100644 Binary files a/test/models/Collada/duck.zae and b/test/models/Collada/duck.zae differ diff --git a/test/models/FBX/box_orphant_embedded_texture.fbx b/test/models/FBX/box_orphant_embedded_texture.fbx new file mode 100644 index 000000000..d874751d0 --- /dev/null +++ b/test/models/FBX/box_orphant_embedded_texture.fbx @@ -0,0 +1,723 @@ +; FBX 7.5.0 project file +; ---------------------------------------------------- + +FBXHeaderExtension: { + FBXHeaderVersion: 1003 + FBXVersion: 7500 + CreationTimeStamp: { + Version: 1000 + Year: 2019 + Month: 3 + Day: 1 + Hour: 12 + Minute: 46 + Second: 3 + Millisecond: 995 + } + Creator: "FBX SDK/FBX Plugins version 2018.1.1" + SceneInfo: "SceneInfo::GlobalInfo", "UserData" { + Type: "UserData" + Version: 100 + MetaData: { + Version: 100 + Title: "" + Subject: "" + Author: "" + Keywords: "" + Revision: "" + Comment: "" + } + Properties70: { + P: "DocumentUrl", "KString", "Url", "", "U:\Some\Absolute\Path\box_embedded_texture.fbx" + P: "SrcDocumentUrl", "KString", "Url", "", "U:\Some\Absolute\Path\box_embedded_texture.fbx" + P: "Original", "Compound", "", "" + P: "Original|ApplicationVendor", "KString", "", "", "Autodesk" + P: "Original|ApplicationName", "KString", "", "", "Maya" + P: "Original|ApplicationVersion", "KString", "", "", "201800" + P: "Original|DateTime_GMT", "DateTime", "", "", "01/03/2019 12:46:03.994" + P: "Original|FileName", "KString", "", "", "U:\Some\Absolute\Path\box_embedded_texture.fbx" + P: "LastSaved", "Compound", "", "" + P: "LastSaved|ApplicationVendor", "KString", "", "", "Autodesk" + P: "LastSaved|ApplicationName", "KString", "", "", "Maya" + P: "LastSaved|ApplicationVersion", "KString", "", "", "201800" + P: "LastSaved|DateTime_GMT", "DateTime", "", "", "01/03/2019 12:46:03.994" + P: "Original|ApplicationActiveProject", "KString", "", "", "U:\Some\Absolute\Path" + } + } +} +GlobalSettings: { + Version: 1000 + Properties70: { + P: "UpAxis", "int", "Integer", "",1 + P: "UpAxisSign", "int", "Integer", "",1 + P: "FrontAxis", "int", "Integer", "",2 + P: "FrontAxisSign", "int", "Integer", "",1 + P: "CoordAxis", "int", "Integer", "",0 + P: "CoordAxisSign", "int", "Integer", "",1 + P: "OriginalUpAxis", "int", "Integer", "",1 + P: "OriginalUpAxisSign", "int", "Integer", "",1 + P: "UnitScaleFactor", "double", "Number", "",100 + P: "OriginalUnitScaleFactor", "double", "Number", "",1 + P: "AmbientColor", "ColorRGB", "Color", "",0,0,0 + P: "DefaultCamera", "KString", "", "", "Producer Perspective" + P: "TimeMode", "enum", "", "",6 + P: "TimeProtocol", "enum", "", "",2 + P: "SnapOnFrameMode", "enum", "", "",0 + P: "TimeSpanStart", "KTime", "Time", "",0 + P: "TimeSpanStop", "KTime", "Time", "",153953860000 + P: "CustomFrameRate", "double", "Number", "",-1 + P: "TimeMarker", "Compound", "", "" + P: "CurrentTimeMarker", "int", "Integer", "",-1 + } +} + +; Documents Description +;------------------------------------------------------------------ + +Documents: { + Count: 1 + Document: 2957686739424, "", "Scene" { + Properties70: { + P: "SourceObject", "object", "", "" + P: "ActiveAnimStackName", "KString", "", "", "Take 001" + } + RootNode: 0 + } +} + +; Document References +;------------------------------------------------------------------ + +References: { +} + +; Object definitions +;------------------------------------------------------------------ + +Definitions: { + Version: 100 + Count: 17 + ObjectType: "GlobalSettings" { + Count: 1 + } + ObjectType: "AnimationStack" { + Count: 1 + PropertyTemplate: "FbxAnimStack" { + Properties70: { + P: "Description", "KString", "", "", "" + P: "LocalStart", "KTime", "Time", "",0 + P: "LocalStop", "KTime", "Time", "",0 + P: "ReferenceStart", "KTime", "Time", "",0 + P: "ReferenceStop", "KTime", "Time", "",0 + } + } + } + ObjectType: "AnimationLayer" { + Count: 1 + PropertyTemplate: "FbxAnimLayer" { + Properties70: { + P: "Weight", "Number", "", "A",100 + P: "Mute", "bool", "", "",0 + P: "Solo", "bool", "", "",0 + P: "Lock", "bool", "", "",0 + P: "Color", "ColorRGB", "Color", "",0.8,0.8,0.8 + P: "BlendMode", "enum", "", "",0 + P: "RotationAccumulationMode", "enum", "", "",0 + P: "ScaleAccumulationMode", "enum", "", "",0 + P: "BlendModeBypass", "ULongLong", "", "",0 + } + } + } + ObjectType: "Geometry" { + Count: 1 + PropertyTemplate: "FbxMesh" { + Properties70: { + P: "Color", "ColorRGB", "Color", "",0.8,0.8,0.8 + P: "BBoxMin", "Vector3D", "Vector", "",0,0,0 + P: "BBoxMax", "Vector3D", "Vector", "",0,0,0 + P: "Primary Visibility", "bool", "", "",1 + P: "Casts Shadows", "bool", "", "",1 + P: "Receive Shadows", "bool", "", "",1 + } + } + } + ObjectType: "Material" { + Count: 1 + PropertyTemplate: "FbxSurfacePhong" { + Properties70: { + P: "ShadingModel", "KString", "", "", "Phong" + P: "MultiLayer", "bool", "", "",0 + P: "EmissiveColor", "Color", "", "A",0,0,0 + P: "EmissiveFactor", "Number", "", "A",1 + P: "AmbientColor", "Color", "", "A",0.2,0.2,0.2 + P: "AmbientFactor", "Number", "", "A",1 + P: "DiffuseColor", "Color", "", "A",0.8,0.8,0.8 + P: "DiffuseFactor", "Number", "", "A",1 + P: "Bump", "Vector3D", "Vector", "",0,0,0 + P: "NormalMap", "Vector3D", "Vector", "",0,0,0 + P: "BumpFactor", "double", "Number", "",1 + P: "TransparentColor", "Color", "", "A",0,0,0 + P: "TransparencyFactor", "Number", "", "A",0 + P: "DisplacementColor", "ColorRGB", "Color", "",0,0,0 + P: "DisplacementFactor", "double", "Number", "",1 + P: "VectorDisplacementColor", "ColorRGB", "Color", "",0,0,0 + P: "VectorDisplacementFactor", "double", "Number", "",1 + P: "SpecularColor", "Color", "", "A",0.2,0.2,0.2 + P: "SpecularFactor", "Number", "", "A",1 + P: "ShininessExponent", "Number", "", "A",20 + P: "ReflectionColor", "Color", "", "A",0,0,0 + P: "ReflectionFactor", "Number", "", "A",1 + } + } + } + ObjectType: "Texture" { + Count: 1 + PropertyTemplate: "FbxFileTexture" { + Properties70: { + P: "TextureTypeUse", "enum", "", "",0 + P: "Texture alpha", "Number", "", "A",1 + P: "CurrentMappingType", "enum", "", "",0 + P: "WrapModeU", "enum", "", "",0 + P: "WrapModeV", "enum", "", "",0 + P: "UVSwap", "bool", "", "",0 + P: "PremultiplyAlpha", "bool", "", "",1 + P: "Translation", "Vector", "", "A",0,0,0 + P: "Rotation", "Vector", "", "A",0,0,0 + P: "Scaling", "Vector", "", "A",1,1,1 + P: "TextureRotationPivot", "Vector3D", "Vector", "",0,0,0 + P: "TextureScalingPivot", "Vector3D", "Vector", "",0,0,0 + P: "CurrentTextureBlendMode", "enum", "", "",1 + P: "UVSet", "KString", "", "", "default" + P: "UseMaterial", "bool", "", "",0 + P: "UseMipMap", "bool", "", "",0 + } + } + } + ObjectType: "Model" { + Count: 1 + PropertyTemplate: "FbxNode" { + Properties70: { + P: "QuaternionInterpolate", "enum", "", "",0 + P: "RotationOffset", "Vector3D", "Vector", "",0,0,0 + P: "RotationPivot", "Vector3D", "Vector", "",0,0,0 + P: "ScalingOffset", "Vector3D", "Vector", "",0,0,0 + P: "ScalingPivot", "Vector3D", "Vector", "",0,0,0 + P: "TranslationActive", "bool", "", "",0 + P: "TranslationMin", "Vector3D", "Vector", "",0,0,0 + P: "TranslationMax", "Vector3D", "Vector", "",0,0,0 + P: "TranslationMinX", "bool", "", "",0 + P: "TranslationMinY", "bool", "", "",0 + P: "TranslationMinZ", "bool", "", "",0 + P: "TranslationMaxX", "bool", "", "",0 + P: "TranslationMaxY", "bool", "", "",0 + P: "TranslationMaxZ", "bool", "", "",0 + P: "RotationOrder", "enum", "", "",0 + P: "RotationSpaceForLimitOnly", "bool", "", "",0 + P: "RotationStiffnessX", "double", "Number", "",0 + P: "RotationStiffnessY", "double", "Number", "",0 + P: "RotationStiffnessZ", "double", "Number", "",0 + P: "AxisLen", "double", "Number", "",10 + P: "PreRotation", "Vector3D", "Vector", "",0,0,0 + P: "PostRotation", "Vector3D", "Vector", "",0,0,0 + P: "RotationActive", "bool", "", "",0 + P: "RotationMin", "Vector3D", "Vector", "",0,0,0 + P: "RotationMax", "Vector3D", "Vector", "",0,0,0 + P: "RotationMinX", "bool", "", "",0 + P: "RotationMinY", "bool", "", "",0 + P: "RotationMinZ", "bool", "", "",0 + P: "RotationMaxX", "bool", "", "",0 + P: "RotationMaxY", "bool", "", "",0 + P: "RotationMaxZ", "bool", "", "",0 + P: "InheritType", "enum", "", "",0 + P: "ScalingActive", "bool", "", "",0 + P: "ScalingMin", "Vector3D", "Vector", "",0,0,0 + P: "ScalingMax", "Vector3D", "Vector", "",1,1,1 + P: "ScalingMinX", "bool", "", "",0 + P: "ScalingMinY", "bool", "", "",0 + P: "ScalingMinZ", "bool", "", "",0 + P: "ScalingMaxX", "bool", "", "",0 + P: "ScalingMaxY", "bool", "", "",0 + P: "ScalingMaxZ", "bool", "", "",0 + P: "GeometricTranslation", "Vector3D", "Vector", "",0,0,0 + P: "GeometricRotation", "Vector3D", "Vector", "",0,0,0 + P: "GeometricScaling", "Vector3D", "Vector", "",1,1,1 + P: "MinDampRangeX", "double", "Number", "",0 + P: "MinDampRangeY", "double", "Number", "",0 + P: "MinDampRangeZ", "double", "Number", "",0 + P: "MaxDampRangeX", "double", "Number", "",0 + P: "MaxDampRangeY", "double", "Number", "",0 + P: "MaxDampRangeZ", "double", "Number", "",0 + P: "MinDampStrengthX", "double", "Number", "",0 + P: "MinDampStrengthY", "double", "Number", "",0 + P: "MinDampStrengthZ", "double", "Number", "",0 + P: "MaxDampStrengthX", "double", "Number", "",0 + P: "MaxDampStrengthY", "double", "Number", "",0 + P: "MaxDampStrengthZ", "double", "Number", "",0 + P: "PreferedAngleX", "double", "Number", "",0 + P: "PreferedAngleY", "double", "Number", "",0 + P: "PreferedAngleZ", "double", "Number", "",0 + P: "LookAtProperty", "object", "", "" + P: "UpVectorProperty", "object", "", "" + P: "Show", "bool", "", "",1 + P: "NegativePercentShapeSupport", "bool", "", "",1 + P: "DefaultAttributeIndex", "int", "Integer", "",-1 + P: "Freeze", "bool", "", "",0 + P: "LODBox", "bool", "", "",0 + P: "Lcl Translation", "Lcl Translation", "", "A",0,0,0 + P: "Lcl Rotation", "Lcl Rotation", "", "A",0,0,0 + P: "Lcl Scaling", "Lcl Scaling", "", "A",1,1,1 + P: "Visibility", "Visibility", "", "A",1 + P: "Visibility Inheritance", "Visibility Inheritance", "", "",1 + } + } + } + ObjectType: "AnimationCurveNode" { + Count: 8 + PropertyTemplate: "FbxAnimCurveNode" { + Properties70: { + P: "d", "Compound", "", "" + } + } + } + ObjectType: "CollectionExclusive" { + Count: 1 + PropertyTemplate: "FbxDisplayLayer" { + Properties70: { + P: "Color", "ColorRGB", "Color", "",0.8,0.8,0.8 + P: "Show", "bool", "", "",1 + P: "Freeze", "bool", "", "",0 + P: "LODBox", "bool", "", "",0 + } + } + } + ObjectType: "Video" { + Count: 1 + PropertyTemplate: "FbxVideo" { + Properties70: { + P: "Path", "KString", "XRefUrl", "", "" + P: "RelPath", "KString", "XRefUrl", "", "" + P: "Color", "ColorRGB", "Color", "",0.8,0.8,0.8 + P: "ClipIn", "KTime", "Time", "",0 + P: "ClipOut", "KTime", "Time", "",0 + P: "Offset", "KTime", "Time", "",0 + P: "PlaySpeed", "double", "Number", "",0 + P: "FreeRunning", "bool", "", "",0 + P: "Loop", "bool", "", "",0 + P: "Mute", "bool", "", "",0 + P: "AccessMode", "enum", "", "",0 + P: "ImageSequence", "bool", "", "",0 + P: "ImageSequenceOffset", "int", "Integer", "",0 + P: "FrameRate", "double", "Number", "",0 + P: "LastFrame", "int", "Integer", "",0 + P: "Width", "int", "Integer", "",0 + P: "Height", "int", "Integer", "",0 + P: "StartFrame", "int", "Integer", "",0 + P: "StopFrame", "int", "Integer", "",0 + P: "InterlaceMode", "enum", "", "",0 + } + } + } +} + +; Object properties +;------------------------------------------------------------------ + +Objects: { + Geometry: 2957764348544, "Geometry::", "Mesh" { + Vertices: *24 { + a: -0.5,-0.5,-0.5,0.5,-0.50000011920929,-0.5,-0.5,0.50000011920929,-0.5,0.5,0.50000011920929,-0.5,-0.5,-0.500000059604645,0.5,0.5,-0.500000059604645,0.5,-0.5,0.500000059604645,0.5,0.5,0.500000059604645,0.5 + } + PolygonVertexIndex: *24 { + a: 0,2,3,-2,4,5,7,-7,0,1,5,-5,1,3,7,-6,3,2,6,-8,2,0,4,-7 + } + Edges: *12 { + a: 0,1,2,3,4,5,6,7,9,11,13,17 + } + GeometryVersion: 124 + LayerElementNormal: 0 { + Version: 102 + Name: "" + MappingInformationType: "ByPolygonVertex" + ReferenceInformationType: "Direct" + Normals: *72 { + a: 0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,1,0,0,1,0,0,1,0,0,1,-5.96046447753906e-08,-1,0,-5.96046447753906e-08,-1,0,-5.96046447753906e-08,-1,0,-5.96046447753906e-08,-1,0,1,0,0,1,0,0,1,0,0,1,0,0,0,1,5.96046447753906e-08,0,1,5.96046447753906e-08,0,1,5.96046447753906e-08,0,1,5.96046447753906e-08,-1,0,0,-1,0,0,-1,0,0,-1,0,0 + } + NormalsW: *24 { + a: 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 + } + } + LayerElementBinormal: 0 { + Version: 102 + Name: "UVChannel_1" + MappingInformationType: "ByPolygonVertex" + ReferenceInformationType: "Direct" + Binormals: *72 { + a: 1.19209289550781e-07,1,0,5.96046447753906e-08,1,0,0,1,0,5.96046447753906e-08,1,0,0,1,-0,0,1,-0,0,1,-0,0,1,-0,0,0,1,0,0,1,0,0,1,0,0,1,-0,0,1,-0,0,1,-0,0,1,-0,0,1,-0,-5.96046447753906e-08,1,-0,-5.96046447753906e-08,1,-0,-5.96046447753906e-08,1,-0,-5.96046447753906e-08,1,0,0,1,0,0,1,0,0,1,0,0,1 + } + BinormalsW: *24 { + a: 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 + } + + } + LayerElementBinormal: 1 { + Version: 102 + Name: "UVChannel_3" + MappingInformationType: "ByPolygonVertex" + ReferenceInformationType: "Direct" + Binormals: *72 { + a: -1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-0,0,-1,-0,0,-1,0,-0,-1,-0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,5.96046447753906e-08,-1,0,5.96046447753906e-08,-1,0,5.96046447753906e-08,-1,0,5.96046447753906e-08,-1,-0,0,-1,-0,0,-1,-0,0,-1,-0,0,-1 + } + BinormalsW: *24 { + a: 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 + } + + } + LayerElementTangent: 0 { + Version: 102 + Name: "UVChannel_1" + MappingInformationType: "ByPolygonVertex" + ReferenceInformationType: "Direct" + Tangents: *72 { + a: -1,1.19209289550781e-07,0,-1,5.96046447753906e-08,0,-1,-0,0,-1,5.96046447753906e-08,0,1,-0,-0,1,-0,0,1,-0,0,1,-0,0,1,-5.96046447753906e-08,-0,1,-5.96046447753906e-08,0,1,-5.96046447753906e-08,0,1,-5.96046447753906e-08,0,-0,1,-0,0,1,-0,0,1,-0,0,1,-0,-1,0,-0,-1,0,-0,-1,0,-0,-1,0,-0,0,-1,-0,0,-1,0,0,-1,0,0,-1,0 + } + TangentsW: *24 { + a: 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 + } + } + LayerElementTangent: 1 { + Version: 102 + Name: "UVChannel_3" + MappingInformationType: "ByPolygonVertex" + ReferenceInformationType: "Direct" + Tangents: *72 { + a: -0,-1,0,-0,-1,0,-0,-1,0,-0,-1,0,0,1,0,0,1,0,-0,1,-0,0,1,0,-1,5.96046447753906e-08,0,-1,5.96046447753906e-08,0,-1,5.96046447753906e-08,-0,-1,5.96046447753906e-08,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,1,-0,-0,1,-0,-0,1,-0,-0,1,-0,-0,0,1,-0,0,1,-0,0,1,-0,0,1,-0 + } + TangentsW: *24 { + a: 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 + } + } + LayerElementUV: 0 { + Version: 101 + Name: "UVChannel_1" + MappingInformationType: "ByPolygonVertex" + ReferenceInformationType: "IndexToDirect" + UV: *48 { + a: 1,0,0,0,1,1,0,1,0,0,1,0,0,1,1,1,0,0,1,0,0,1,1,1,0,0,1,0,0,1,1,1,0,0,1,0,0,1,1,1,0,0,1,0,0,1,1,1 + } + UVIndex: *24 { + a: 0,2,3,1,4,5,7,6,8,9,11,10,12,13,15,14,16,17,19,18,20,21,23,22 + } + } + LayerElementUV: 1 { + Version: 101 + Name: "UVChannel_3" + MappingInformationType: "ByPolygonVertex" + ReferenceInformationType: "IndexToDirect" + UV: *48 { + a: 0.28125,0.28125,0,0.28125,0,0,0.28125,0,0.34375,1,0.34375,0.71875,0.625,0.71875,0.625,1,0.28125,0.65625,0,0.65625,0,0.375,0.28125,0.375,0.625,0.28125,0.34375,0.28125,0.34375,0,0.625,0,0.28125,1,0,1,0,0.71875,0.28125,0.71875,0.625,0.65625,0.34375,0.65625,0.34375,0.375,0.625,0.375 + } + UVIndex: *24 { + a: 0,1,2,3,4,5,6,7,20,21,22,23,8,9,10,11,16,17,18,19,12,13,14,15 + } + } + LayerElementSmoothing: 0 { + Version: 102 + Name: "" + MappingInformationType: "ByEdge" + ReferenceInformationType: "Direct" + Smoothing: *12 { + a: 0,0,0,0,0,0,0,0,0,0,0,0 + } + } + LayerElementMaterial: 0 { + Version: 101 + Name: "" + MappingInformationType: "AllSame" + ReferenceInformationType: "IndexToDirect" + Materials: *1 { + a: 0 + } + } + Layer: 0 { + Version: 100 + LayerElement: { + Type: "LayerElementNormal" + TypedIndex: 0 + } + LayerElement: { + Type: "LayerElementBinormal" + TypedIndex: 0 + } + LayerElement: { + Type: "LayerElementTangent" + TypedIndex: 0 + } + LayerElement: { + Type: "LayerElementMaterial" + TypedIndex: 0 + } + LayerElement: { + Type: "LayerElementSmoothing" + TypedIndex: 0 + } + LayerElement: { + Type: "LayerElementUV" + TypedIndex: 0 + } + } + Layer: 1 { + Version: 100 + LayerElement: { + Type: "LayerElementBinormal" + TypedIndex: 1 + } + LayerElement: { + Type: "LayerElementTangent" + TypedIndex: 1 + } + LayerElement: { + Type: "LayerElementUV" + TypedIndex: 1 + } + } + } + Model: 2957618625584, "Model::Box", "Mesh" { + Version: 232 + Properties70: { + P: "RotationActive", "bool", "", "",1 + P: "InheritType", "enum", "", "",1 + P: "ScalingMax", "Vector3D", "Vector", "",0,0,0 + P: "DefaultAttributeIndex", "int", "Integer", "",0 + P: "Lcl Translation", "Lcl Translation", "", "A",0,0.5,2.18556946492754e-06 + P: "Lcl Rotation", "Lcl Rotation", "", "A",-90,0,0 + P: "currentUVSet", "KString", "", "U", "UVChannel_1" + P: "mr displacement use global settings", "Bool", "", "A+U",1 + P: "mr displacement view dependent", "Bool", "", "A+U",1 + P: "mr displacement method", "Integer", "", "A+U",6,6,6 + P: "mr displacement smoothing on", "Bool", "", "A+U",1 + P: "mr displacement edge length", "Number", "", "A+U",2,2,2 + P: "mr displacement max displace", "Number", "", "A+U",20,20,20 + P: "mr displacement parametric subdivision level", "Integer", "", "A+U",5,5,5 + P: "MaxHandle", "Integer", "", "A+UH",1,0,0 + } + Shading: T + Culling: "CullingOff" + } + Material: 2957776713840, "Material::Default", "" { + Version: 102 + ShadingModel: "phong" + MultiLayer: 0 + Properties70: { + P: "AmbientColor", "Color", "", "A",0,0,0 + P: "DiffuseColor", "Color", "", "A",1,1,1 + P: "TransparencyFactor", "Number", "", "A",1 + P: "SpecularColor", "Color", "", "A",0,0,0 + P: "ShininessExponent", "Number", "", "A",2 + P: "Emissive", "Vector3D", "Vector", "",0,0,0 + P: "Ambient", "Vector3D", "Vector", "",0,0,0 + P: "Diffuse", "Vector3D", "Vector", "",1,1,1 + P: "Specular", "Vector3D", "Vector", "",0,0,0 + P: "Shininess", "double", "Number", "",2 + P: "Opacity", "double", "Number", "",1 + P: "Reflectivity", "double", "Number", "",0 + } + } + Video: 2957776707120, "Video::Map #2", "Clip" { + Type: "Clip" + Properties70: { + P: "Path", "KString", "XRefUrl", "", "U:/Some/Absolute/Primitives/GridGrey.tga" + P: "RelPath", "KString", "XRefUrl", "", "..\Primitives\GridGrey.tga" + } + UseMipMap: 0 + Filename: "U:/Some/Absolute/Primitives/GridGrey.tga" + RelativeFilename: "..\Primitives\GridGrey.tga" + } + Video: 2957776707121, "Video::Map #2", "Clip" { + Type: "Clip" + Properties70: { + P: "Path", "KString", "XRefUrl", "", "U:/Some/Absolute/Primitives/GridGrey.tga" + P: "RelPath", "KString", "XRefUrl", "", "..\Primitives\GridGrey.tga" + } + UseMipMap: 0 + Filename: "U:/Some/Absolute/Primitives/GridGrey.tga" + RelativeFilename: "..\Primitives\GridGrey.tga" + Content: , + "AAAKAAAAAAAAAAAAAAEAARgAh+rq6v+Pj4/wj4+Phurq6ofq6uq2dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+3dHR0hurq6oHq6uq8dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+8dHR0gerq6oHq6uq8dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+8dHR0gerq6oHq6uq8dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+8dHR0gerq6oHq6uq8dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+8dHR0gerq6oHq6uq8dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+8dHR0gerq6oHq6uq8dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+8dHR0gerq6gCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+Pj/+Pj4//j4+P/4+Pj/+Pj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj48Aj4+PvXR0dIGPj4+9dHR0gY+Pj710dHSBj4+PvXR0dACPj4//j4+P/4+Pj/+Pj4//j4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+PAI+Pj710dHSBj4+PvXR0dIGPj4+9dHR0gY+Pj710dHQAj4+P/4+Pj/+Pj4//j4+P/4+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+PjwCPj4+9dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+9dHR0AI+Pj4Hq6uq8dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+8dHR0gerq6oHq6uq8dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+8dHR0gerq6oHq6uq8dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+8dHR0gerq6oHq6uq8dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+8dHR0gerq6oHq6uq8dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+8dHR0gerq6oHq6uq8dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+8dHR0gerq6ofq6uq2dHR0gY+Pj710dHSBj4+PvXR0dIGPj4+3dHR0hurq6ofq6ur/j4+P8I+Pj4bq6uo=" + } + Texture: 2957776714320, "Texture::Map #2", "" { + Type: "TextureVideoClip" + Version: 202 + TextureName: "Texture::Map #2" + Properties70: { + P: "CurrentTextureBlendMode", "enum", "", "",0 + P: "UVSet", "KString", "", "", "UVChannel_1" + P: "UseMaterial", "bool", "", "",1 + } + Media: "Video::Map #2" + FileName: "U:/Some/Absolute/Primitives/GridGrey.tga" + RelativeFilename: "..\Primitives\GridGrey.tga" + ModelUVTranslation: 0,0 + ModelUVScaling: 1,1 + Texture_Alpha_Source: "None" + Cropping: 0,0,0,0 + } + Texture: 2957776714321, "Texture::Map #2", "" { + Type: "TextureVideoClip" + Version: 202 + TextureName: "Texture::Map #2" + Properties70: { + P: "CurrentTextureBlendMode", "enum", "", "",0 + P: "UVSet", "KString", "", "", "UVChannel_1" + P: "UseMaterial", "bool", "", "",1 + } + Media: "Video::Map #2" + FileName: "U:/Some/Absolute/Primitives/GridGrey.tga" + RelativeFilename: "..\Primitives\GridGrey.tga" + ModelUVTranslation: 0,0 + ModelUVScaling: 1,1 + Texture_Alpha_Source: "None" + Cropping: 0,0,0,0 + } + AnimationStack: 2957627494560, "AnimStack::Take 001", "" { + Properties70: { + P: "LocalStop", "KTime", "Time", "",153953860000 + P: "ReferenceStop", "KTime", "Time", "",153953860000 + } + } + AnimationCurveNode: 2957627500176, "AnimCurveNode::mr displacement use global settings", "" { + Properties70: { + P: "d|mr displacement use global settings", "Bool", "", "A",1 + } + } + AnimationCurveNode: 2957627490192, "AnimCurveNode::mr displacement view dependent", "" { + Properties70: { + P: "d|mr displacement view dependent", "Bool", "", "A",1 + } + } + AnimationCurveNode: 2957627487904, "AnimCurveNode::mr displacement method", "" { + Properties70: { + P: "d|mr displacement method", "Integer", "", "A",6 + } + } + AnimationCurveNode: 2957627492688, "AnimCurveNode::mr displacement smoothing on", "" { + Properties70: { + P: "d|mr displacement smoothing on", "Bool", "", "A",1 + } + } + AnimationCurveNode: 2957627492064, "AnimCurveNode::mr displacement edge length", "" { + Properties70: { + P: "d|mr displacement edge length", "Number", "", "A",2 + } + } + AnimationCurveNode: 2957627492896, "AnimCurveNode::mr displacement max displace", "" { + Properties70: { + P: "d|mr displacement max displace", "Number", "", "A",20 + } + } + AnimationCurveNode: 2957627492272, "AnimCurveNode::mr displacement parametric subdivision level", "" { + Properties70: { + P: "d|mr displacement parametric subdivision level", "Integer", "", "A",5 + } + } + AnimationCurveNode: 2957627488320, "AnimCurveNode::MaxHandle", "" { + Properties70: { + P: "d|MaxHandle", "Integer", "", "A",1 + } + } + AnimationLayer: 2957566198176, "AnimLayer::BaseLayer", "" { + } + CollectionExclusive: 2959111896352, "DisplayLayer::Box", "DisplayLayer" { + Properties70: { + P: "Color", "ColorRGB", "Color", "",0.607999980449677,0,0.157000005245209 + } + } +} + +; Object connections +;------------------------------------------------------------------ + +Connections: { + + ;Model::Box, Model::RootNode + C: "OO",2957618625584,0 + + ;AnimLayer::BaseLayer, AnimStack::Take 001 + C: "OO",2957566198176,2957627494560 + + ;AnimCurveNode::mr displacement use global settings, AnimLayer::BaseLayer + C: "OO",2957627500176,2957566198176 + + ;AnimCurveNode::mr displacement view dependent, AnimLayer::BaseLayer + C: "OO",2957627490192,2957566198176 + + ;AnimCurveNode::mr displacement method, AnimLayer::BaseLayer + C: "OO",2957627487904,2957566198176 + + ;AnimCurveNode::mr displacement smoothing on, AnimLayer::BaseLayer + C: "OO",2957627492688,2957566198176 + + ;AnimCurveNode::mr displacement edge length, AnimLayer::BaseLayer + C: "OO",2957627492064,2957566198176 + + ;AnimCurveNode::mr displacement max displace, AnimLayer::BaseLayer + C: "OO",2957627492896,2957566198176 + + ;AnimCurveNode::mr displacement parametric subdivision level, AnimLayer::BaseLayer + C: "OO",2957627492272,2957566198176 + + ;AnimCurveNode::MaxHandle, AnimLayer::BaseLayer + C: "OO",2957627488320,2957566198176 + + ;Texture::Map #2, Material::Default + C: "OP",2957776714320,2957776713840, "DiffuseColor" + + ;Video::Map #2, Texture::Map #2 + C: "OO",2957776707120,2957776714320 + + ;Video::Map #2, Texture::Map #2 + C: "OO",2957776707121,2957776714321 + + ;Geometry::, Model::Box + C: "OO",2957764348544,2957618625584 + + ;Material::Default, Model::Box + C: "OO",2957776713840,2957618625584 + + ;AnimCurveNode::mr displacement use global settings, Model::Box + C: "OP",2957627500176,2957618625584, "mr displacement use global settings" + + ;AnimCurveNode::mr displacement view dependent, Model::Box + C: "OP",2957627490192,2957618625584, "mr displacement view dependent" + + ;AnimCurveNode::mr displacement method, Model::Box + C: "OP",2957627487904,2957618625584, "mr displacement method" + + ;AnimCurveNode::mr displacement smoothing on, Model::Box + C: "OP",2957627492688,2957618625584, "mr displacement smoothing on" + + ;AnimCurveNode::mr displacement edge length, Model::Box + C: "OP",2957627492064,2957618625584, "mr displacement edge length" + + ;AnimCurveNode::mr displacement max displace, Model::Box + C: "OP",2957627492896,2957618625584, "mr displacement max displace" + + ;AnimCurveNode::mr displacement parametric subdivision level, Model::Box + C: "OP",2957627492272,2957618625584, "mr displacement parametric subdivision level" + + ;AnimCurveNode::MaxHandle, Model::Box + C: "OP",2957627488320,2957618625584, "MaxHandle" + + ;Model::Box, DisplayLayer::Box + C: "OO",2957618625584,2959111896352 +} +;Takes section +;---------------------------------------------------- + +Takes: { + Current: "Take 001" + Take: "Take 001" { + FileName: "Take_001.tak" + LocalTime: 0,153953860000 + ReferenceTime: 0,153953860000 + } +} diff --git a/test/models/FBX/huesitos.fbx b/test/models/FBX/huesitos.fbx new file mode 100644 index 000000000..646271392 Binary files /dev/null and b/test/models/FBX/huesitos.fbx differ diff --git a/test/models/M3D/WusonBlitz0.m3d b/test/models/M3D/WusonBlitz0.m3d new file mode 100644 index 000000000..29a397b79 Binary files /dev/null and b/test/models/M3D/WusonBlitz0.m3d differ diff --git a/test/models/M3D/WusonBlitz1.m3d b/test/models/M3D/WusonBlitz1.m3d new file mode 100644 index 000000000..366125257 Binary files /dev/null and b/test/models/M3D/WusonBlitz1.m3d differ diff --git a/test/models/M3D/WusonBlitz2.m3d b/test/models/M3D/WusonBlitz2.m3d new file mode 100644 index 000000000..682876816 Binary files /dev/null and b/test/models/M3D/WusonBlitz2.m3d differ diff --git a/test/models/M3D/cube_normals.m3d b/test/models/M3D/cube_normals.m3d new file mode 100644 index 000000000..ec4e4ae99 Binary files /dev/null and b/test/models/M3D/cube_normals.m3d differ diff --git a/test/models/M3D/cube_usemtl.m3d b/test/models/M3D/cube_usemtl.m3d new file mode 100644 index 000000000..e3f0b79e5 Binary files /dev/null and b/test/models/M3D/cube_usemtl.m3d differ diff --git a/test/models/M3D/cube_with_vertexcolors.a3d b/test/models/M3D/cube_with_vertexcolors.a3d new file mode 100644 index 000000000..55fe5a353 --- /dev/null +++ b/test/models/M3D/cube_with_vertexcolors.a3d @@ -0,0 +1,33 @@ +3dmodel 1 +cube_with_vertexcolors.obj +MIT +bzt +comment + +Vertex +0 0 0 1 #ff786d7b +0 0 -1 1 +1 1 0 1 #ff320a4d +1 0 0 1 #ff19c718 +0 1 0 1 #ff2c0004 +-1 0 0 1 +0 1 1 1 #ff0a00df +0 0 1 1 #ff790018 +1 1 1 1 #ffc70017 +1 0 1 1 #ff380a7b +0 -1 0 1 + +Mesh +0//1 2//1 3//1 +0//1 4//1 2//1 +0//5 6//5 4//5 +0//5 7//5 6//5 +4//4 8//4 2//4 +4//4 6//4 8//4 +3//3 2//3 8//3 +3//3 8//3 9//3 +0//10 3//10 9//10 +0//10 9//10 7//10 +7//7 9//7 8//7 +7//7 8//7 6//7 + diff --git a/test/models/M3D/cube_with_vertexcolors.m3d b/test/models/M3D/cube_with_vertexcolors.m3d new file mode 100644 index 000000000..598de4ee9 Binary files /dev/null and b/test/models/M3D/cube_with_vertexcolors.m3d differ diff --git a/test/models/M3D/suzanne.m3d b/test/models/M3D/suzanne.m3d new file mode 100644 index 000000000..9bc64d7d7 Binary files /dev/null and b/test/models/M3D/suzanne.m3d differ diff --git a/test/models/glTF2/textureTransform/Arrow.png b/test/models/glTF2/textureTransform/Arrow.png new file mode 100644 index 000000000..fe3405b3f Binary files /dev/null and b/test/models/glTF2/textureTransform/Arrow.png differ diff --git a/test/models/glTF2/textureTransform/Correct.png b/test/models/glTF2/textureTransform/Correct.png new file mode 100644 index 000000000..e332824fb Binary files /dev/null and b/test/models/glTF2/textureTransform/Correct.png differ diff --git a/test/models/glTF2/textureTransform/Error.png b/test/models/glTF2/textureTransform/Error.png new file mode 100644 index 000000000..35acf6e94 Binary files /dev/null and b/test/models/glTF2/textureTransform/Error.png differ diff --git a/test/models/glTF2/textureTransform/License.txt b/test/models/glTF2/textureTransform/License.txt new file mode 100644 index 000000000..e69de29bb diff --git a/test/models/glTF2/textureTransform/NotSupported.png b/test/models/glTF2/textureTransform/NotSupported.png new file mode 100644 index 000000000..721e7fd76 Binary files /dev/null and b/test/models/glTF2/textureTransform/NotSupported.png differ diff --git a/test/models/glTF2/textureTransform/TextureTransformTest.bin b/test/models/glTF2/textureTransform/TextureTransformTest.bin new file mode 100644 index 000000000..6765a1300 Binary files /dev/null and b/test/models/glTF2/textureTransform/TextureTransformTest.bin differ diff --git a/test/models/glTF2/textureTransform/TextureTransformTest.gltf b/test/models/glTF2/textureTransform/TextureTransformTest.gltf new file mode 100644 index 000000000..6dde51d9a --- /dev/null +++ b/test/models/glTF2/textureTransform/TextureTransformTest.gltf @@ -0,0 +1,540 @@ +{ + "accessors": [ + { + "bufferView": 0, + "componentType": 5126, + "count": 4, + "type": "VEC3", + "max": [ + 0.5, + 0.5, + 0.0 + ], + "min": [ + -0.5, + -0.5, + 0.0 + ], + "name": "Positions" + }, + { + "bufferView": 1, + "componentType": 5126, + "count": 4, + "type": "VEC2", + "name": "UV0" + }, + { + "bufferView": 2, + "componentType": 5126, + "count": 4, + "type": "VEC2", + "name": "UV1" + }, + { + "bufferView": 3, + "componentType": 5125, + "count": 6, + "type": "SCALAR", + "name": "Indices" + } + ], + "asset": { + "version": "2.0" + }, + "buffers": [ + { + "uri": "TextureTransformTest.bin", + "byteLength": 136 + } + ], + "bufferViews": [ + { + "buffer": 0, + "byteLength": 48, + "name": "Positions" + }, + { + "buffer": 0, + "byteOffset": 48, + "byteLength": 32, + "name": "UV0" + }, + { + "buffer": 0, + "byteOffset": 80, + "byteLength": 32, + "name": "UV1" + }, + { + "buffer": 0, + "byteOffset": 112, + "byteLength": 24, + "name": "Indices" + } + ], + "extensionsUsed": [ + "KHR_texture_transform" + ], + "images": [ + { + "uri": "UV.png" + }, + { + "uri": "Arrow.png" + }, + { + "uri": "Correct.png" + }, + { + "uri": "NotSupported.png" + }, + { + "uri": "Error.png" + } + ], + "materials": [ + { + "name": "Offset U", + "pbrMetallicRoughness": { + "baseColorTexture": { + "index": 0, + "extensions": { + "KHR_texture_transform": { + "offset": [ + 0.5, + 0.0 + ] + } + } + }, + "metallicFactor": 0 + } + }, + { + "name": "Offset V", + "pbrMetallicRoughness": { + "baseColorTexture": { + "index": 0, + "extensions": { + "KHR_texture_transform": { + "offset": [ + 0.0, + 0.5 + ] + } + } + }, + "metallicFactor": 0 + } + }, + { + "name": "Offset UV", + "pbrMetallicRoughness": { + "baseColorTexture": { + "index": 0, + "extensions": { + "KHR_texture_transform": { + "offset": [ + 0.5, + 0.5 + ] + } + } + }, + "metallicFactor": 0 + } + }, + { + "name": "Rotation", + "pbrMetallicRoughness": { + "baseColorTexture": { + "index": 1, + "extensions": { + "KHR_texture_transform": { + "rotation": 0.39269908169872415480783042290994 + } + } + }, + "metallicFactor": 0 + } + }, + { + "name": "Scale", + "pbrMetallicRoughness": { + "baseColorTexture": { + "index": 1, + "extensions": { + "KHR_texture_transform": { + "scale": [ + 1.5, + 1.5 + ] + } + } + }, + "metallicFactor": 0 + } + }, + { + "name": "All", + "pbrMetallicRoughness": { + "baseColorTexture": { + "index": 1, + "extensions": { + "KHR_texture_transform": { + "offset": [ + -0.2, + -0.1 + ], + "rotation": 0.3, + "scale": [ + 1.5, + 1.5 + ] + } + } + }, + "metallicFactor": 0 + } + }, + { + "name": "Correct", + "pbrMetallicRoughness": { + "baseColorTexture": { + "index": 2 + }, + "metallicFactor": 0 + } + }, + { + "name": "NotSupported", + "pbrMetallicRoughness": { + "baseColorTexture": { + "index": 3 + }, + "metallicFactor": 0 + } + }, + { + "name": "Error", + "pbrMetallicRoughness": { + "baseColorTexture": { + "index": 4 + }, + "metallicFactor": 0 + } + } + ], + "meshes": [ + { + "name": "Offset U", + "primitives": [ + { + "attributes": { + "POSITION": 0, + "TEXCOORD_0": 2 + }, + "indices": 3, + "material": 0 + } + ] + }, + { + "name": "Offset V", + "primitives": [ + { + "attributes": { + "POSITION": 0, + "TEXCOORD_0": 2 + }, + "indices": 3, + "material": 1 + } + ] + }, + { + "name": "Offset UV", + "primitives": [ + { + "attributes": { + "POSITION": 0, + "TEXCOORD_0": 2 + }, + "indices": 3, + "material": 2 + } + ] + }, + { + "name": "Rotation", + "primitives": [ + { + "attributes": { + "POSITION": 0, + "TEXCOORD_0": 1 + }, + "indices": 3, + "material": 3 + } + ] + }, + { + "name": "Scale", + "primitives": [ + { + "attributes": { + "POSITION": 0, + "TEXCOORD_0": 1 + }, + "indices": 3, + "material": 4 + } + ] + }, + { + "name": "All", + "primitives": [ + { + "attributes": { + "POSITION": 0, + "TEXCOORD_0": 1 + }, + "indices": 3, + "material": 5 + } + ] + }, + { + "name": "Correct Marker", + "primitives": [ + { + "attributes": { + "POSITION": 0, + "TEXCOORD_0": 1 + }, + "indices": 3, + "material": 6 + } + ] + }, + { + "name": "Not Supported Marker", + "primitives": [ + { + "attributes": { + "POSITION": 0, + "TEXCOORD_0": 1 + }, + "indices": 3, + "material": 7 + } + ] + }, + { + "name": "Error Marker", + "primitives": [ + { + "attributes": { + "POSITION": 0, + "TEXCOORD_0": 1 + }, + "indices": 3, + "material": 8 + } + ] + } + ], + "nodes": [ + { + "name": "Offset U", + "mesh": 0, + "translation": [ + -1.1, + 0.55, + 0 + ] + }, + { + "name": "Offset V", + "mesh": 1, + "translation": [ + 0, + 0.55, + 0 + ] + }, + { + "name": "Offset UV", + "mesh": 2, + "translation": [ + 1.1, + 0.55, + 0 + ] + }, + { + "name": "Rotation", + "mesh": 3, + "translation": [ + -1.1, + -0.55, + 0 + ], + "children": [ + 4, + 5, + 6 + ] + }, + { + "name": "Rotation - Correct", + "mesh": 6, + "translation": [ + -0.07904822439840125109869401756656, + -0.51626748576241543174100150833647, + 0.01 + ], + "scale": [ + 0.15, + 0.15, + 0.15 + ] + }, + { + "name": "Rotation - Not Supported", + "mesh": 7, + "translation": [ + 0.27781745930520227684092879831533, + -0.27781745930520227684092879831533, + 0.01 + ], + "scale": [ + 0.15, + 0.15, + 0.15 + ] + }, + { + "name": "Rotation - Error", + "mesh": 8, + "translation": [ + 0.51626748576241543174100150833647, + 0.07904822439840125109869401756656, + 0.01 + ], + "scale": [ + 0.15, + 0.15, + 0.15 + ] + }, + { + "name": "Scale", + "mesh": 4, + "translation": [ + 0, + -0.55, + 0 + ], + "children": [ + 8, + 9 + ] + }, + { + "name": "Scale - Correct", + "mesh": 6, + "translation": [ + 0.01854497287013485122728586554355, + -0.01854497287013485122728586554355, + 0.01 + ], + "scale": [ + 0.1, + 0.1, + 0.1 + ] + }, + { + "name": "Scale - Not Supported", + "mesh": 7, + "translation": [ + 0.27781745930520227684092879831533, + -0.27781745930520227684092879831533, + 0.01 + ], + "scale": [ + 0.15, + 0.15, + 0.15 + ] + }, + { + "name": "All", + "mesh": 5, + "translation": [ + 1.1, + -0.55, + 0 + ], + "children": [ + 11 + ] + }, + { + "name": "All - Correct", + "mesh": 6, + "translation": [ + -0.07, + -0.25, + 0.01 + ], + "scale": [ + 0.1, + 0.1, + 0.1 + ] + } + ], + "scene": 0, + "scenes": [ + { + "nodes": [ + 0, + 1, + 2, + 3, + 7, + 10 + ] + } + ], + "textures": [ + { + "source": 0, + "sampler": 0 + }, + { + "source": 1, + "sampler": 0 + }, + { + "source": 2 + }, + { + "source": 3 + }, + { + "source": 4 + } + ], + "samplers": [ + { + "wrapS": 33071, + "wrapT": 33071, + "magFilter": 9729, + "minFilter": 9729 + } + ] +} \ No newline at end of file diff --git a/test/models/glTF2/textureTransform/UV.png b/test/models/glTF2/textureTransform/UV.png new file mode 100644 index 000000000..c1a6d4d37 Binary files /dev/null and b/test/models/glTF2/textureTransform/UV.png differ diff --git a/test/unit/Main.cpp b/test/unit/Main.cpp index 5ba5c487d..333fd655d 100644 --- a/test/unit/Main.cpp +++ b/test/unit/Main.cpp @@ -16,7 +16,7 @@ int main(int argc, char* argv[]) // create a logger from both CPP Assimp::DefaultLogger::create("AssimpLog_Cpp.txt",Assimp::Logger::VERBOSE, - aiDefaultLogStream_DEBUGGER | aiDefaultLogStream_FILE); + aiDefaultLogStream_STDOUT | aiDefaultLogStream_DEBUGGER | aiDefaultLogStream_FILE); // .. and C. They should smoothly work together aiEnableVerboseLogging(AI_TRUE); diff --git a/test/unit/utArmaturePopulate.cpp b/test/unit/utArmaturePopulate.cpp new file mode 100644 index 000000000..8eb577d61 --- /dev/null +++ b/test/unit/utArmaturePopulate.cpp @@ -0,0 +1,83 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the following +conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--------------------------------------------------------------------------- +*/ +#include "UnitTestPCH.h" +#include "TestModelFactory.h" + + +#include "SceneDiffer.h" +#include "AbstractImportExportBase.h" + +#include +#include +#include +#include +#include + +#include "PostProcessing/ArmaturePopulate.h" + +namespace Assimp { +namespace UnitTest { + +class utArmaturePopulate : public ::testing::Test { + // empty +}; + +TEST_F( utArmaturePopulate, importCheckForArmatureTest) { + Assimp::Importer importer; + unsigned int mask = aiProcess_PopulateArmatureData | aiProcess_ValidateDataStructure; + const aiScene *scene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/FBX/huesitos.fbx", mask); + EXPECT_NE( nullptr, scene ); + EXPECT_EQ(scene->mNumMeshes, 1u); + aiMesh* mesh = scene->mMeshes[0]; + EXPECT_EQ(mesh->mNumFaces, 68u); + EXPECT_EQ(mesh->mNumVertices, 256u); + EXPECT_GT(mesh->mNumBones, 0u); + + aiBone* exampleBone = mesh->mBones[0]; + EXPECT_NE(exampleBone, nullptr); + EXPECT_NE(exampleBone->mArmature, nullptr); + EXPECT_NE(exampleBone->mNode, nullptr); +} + +} // Namespace UnitTest +} // Namespace Assimp diff --git a/test/unit/utColladaImportExport.cpp b/test/unit/utColladaImportExport.cpp index 1a2a6bc9d..8bce3a3dd 100644 --- a/test/unit/utColladaImportExport.cpp +++ b/test/unit/utColladaImportExport.cpp @@ -44,6 +44,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "AbstractImportExportBase.h" #include +#include #include using namespace Assimp; @@ -52,8 +53,19 @@ class utColladaImportExport : public AbstractImportExportBase { public: virtual bool importerTest() { Assimp::Importer importer; - const aiScene *scene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/Collada/duck.dae", aiProcess_ValidateDataStructure ); - return nullptr != scene; + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/Collada/duck.dae", aiProcess_ValidateDataStructure); + if (scene == nullptr) + return false; + + // Expected number of items + EXPECT_EQ(scene->mNumMeshes, 1u); + EXPECT_EQ(scene->mNumMaterials, 1u); + EXPECT_EQ(scene->mNumAnimations, 0u); + EXPECT_EQ(scene->mNumTextures, 0u); + EXPECT_EQ(scene->mNumLights, 1u); + EXPECT_EQ(scene->mNumCameras, 1u); + + return true; } }; @@ -64,9 +76,37 @@ TEST_F(utColladaImportExport, importBlenFromFileTest) { class utColladaZaeImportExport : public AbstractImportExportBase { public: virtual bool importerTest() { - Assimp::Importer importer; - const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/Collada/duck.zae", aiProcess_ValidateDataStructure); - return nullptr != scene; + { + Assimp::Importer importer; + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/Collada/duck.zae", aiProcess_ValidateDataStructure); + if (scene == nullptr) + return false; + + // Expected number of items + EXPECT_EQ(scene->mNumMeshes, 1u); + EXPECT_EQ(scene->mNumMaterials, 1u); + EXPECT_EQ(scene->mNumAnimations, 0u); + EXPECT_EQ(scene->mNumTextures, 1u); + EXPECT_EQ(scene->mNumLights, 1u); + EXPECT_EQ(scene->mNumCameras, 1u); + } + + { + Assimp::Importer importer; + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/Collada/duck_nomanifest.zae", aiProcess_ValidateDataStructure); + if (scene == nullptr) + return false; + + // Expected number of items + EXPECT_EQ(scene->mNumMeshes, 1u); + EXPECT_EQ(scene->mNumMaterials, 1u); + EXPECT_EQ(scene->mNumAnimations, 0u); + EXPECT_EQ(scene->mNumTextures, 1u); + EXPECT_EQ(scene->mNumLights, 1u); + EXPECT_EQ(scene->mNumCameras, 1u); + } + + return true; } }; diff --git a/test/unit/utFBXImporterExporter.cpp b/test/unit/utFBXImporterExporter.cpp index 1445e3c3e..43dc40e88 100644 --- a/test/unit/utFBXImporterExporter.cpp +++ b/test/unit/utFBXImporterExporter.cpp @@ -76,6 +76,7 @@ TEST_F( utFBXImporterExporter, importBareBoxWithoutColorsAndTextureCoords ) { EXPECT_EQ(mesh->mNumVertices, 36u); } + TEST_F(utFBXImporterExporter, importCubesWithNoNames) { Assimp::Importer importer; const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/FBX/cubes_nonames.fbx", aiProcess_ValidateDataStructure); @@ -86,26 +87,6 @@ TEST_F(utFBXImporterExporter, importCubesWithNoNames) { ASSERT_STREQ(root->mName.C_Str(), "RootNode"); ASSERT_TRUE(root->mChildren); ASSERT_EQ(root->mNumChildren, 2u); - - const auto child0 = root->mChildren[0]; - ASSERT_TRUE(child0); - ASSERT_STREQ(child0->mName.C_Str(), "RootNode001"); - ASSERT_TRUE(child0->mChildren); - ASSERT_EQ(child0->mNumChildren, 1u); - - const auto child00 = child0->mChildren[0]; - ASSERT_TRUE(child00); - ASSERT_STREQ(child00->mName.C_Str(), "RootNode001001"); - - const auto child1 = root->mChildren[1]; - ASSERT_TRUE(child1); - ASSERT_STREQ(child1->mName.C_Str(), "RootNode002"); - ASSERT_TRUE(child1->mChildren); - ASSERT_EQ(child1->mNumChildren, 1u); - - const auto child10 = child1->mChildren[0]; - ASSERT_TRUE(child10); - ASSERT_STREQ(child10->mName.C_Str(), "RootNode002001"); } TEST_F(utFBXImporterExporter, importCubesWithUnicodeDuplicatedNames) { @@ -137,7 +118,7 @@ TEST_F(utFBXImporterExporter, importCubesWithUnicodeDuplicatedNames) { const auto child10 = child1->mChildren[0]; ASSERT_TRUE(child10); - ASSERT_STREQ(child10->mName.C_Str(), "\xd0\x9a\xd1\x83\xd0\xb1\x31""001"); + ASSERT_STREQ(child10->mName.C_Str(), "\xd0\x9a\xd1\x83\xd0\xb1\x31"); } TEST_F(utFBXImporterExporter, importCubesComplexTransform) { @@ -168,14 +149,14 @@ TEST_F(utFBXImporterExporter, importCubesComplexTransform) { auto parent = child1; const size_t chain_length = 8u; const char* chainStr[chain_length] = { - "Cube1001_$AssimpFbx$_Translation", - "Cube1001_$AssimpFbx$_RotationPivot", - "Cube1001_$AssimpFbx$_RotationPivotInverse", - "Cube1001_$AssimpFbx$_ScalingOffset", - "Cube1001_$AssimpFbx$_ScalingPivot", - "Cube1001_$AssimpFbx$_Scaling", - "Cube1001_$AssimpFbx$_ScalingPivotInverse", - "Cube1001" + "Cube1_$AssimpFbx$_Translation", + "Cube1_$AssimpFbx$_RotationPivot", + "Cube1_$AssimpFbx$_RotationPivotInverse", + "Cube1_$AssimpFbx$_ScalingOffset", + "Cube1_$AssimpFbx$_ScalingPivot", + "Cube1_$AssimpFbx$_Scaling", + "Cube1_$AssimpFbx$_ScalingPivotInverse", + "Cube1" }; for (size_t i = 0; i < chain_length; ++i) { ASSERT_TRUE(parent->mChildren); @@ -282,3 +263,23 @@ TEST_F(utFBXImporterExporter, fbxTokenizeTestTest) { //const aiScene* scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/FBX/transparentTest2.fbx", aiProcess_ValidateDataStructure); //EXPECT_NE(nullptr, scene); } + +TEST_F(utFBXImporterExporter, importOrphantEmbeddedTextureTest) { + // see https://github.com/assimp/assimp/issues/1957 + Assimp::Importer importer; + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/FBX/box_orphant_embedded_texture.fbx", aiProcess_ValidateDataStructure); + EXPECT_NE(nullptr, scene); + + EXPECT_EQ(1u, scene->mNumMaterials); + aiMaterial *mat = scene->mMaterials[0]; + ASSERT_NE(nullptr, mat); + + aiString path; + aiTextureMapMode modes[2]; + ASSERT_EQ(aiReturn_SUCCESS, mat->GetTexture(aiTextureType_DIFFUSE, 0, &path, nullptr, nullptr, nullptr, nullptr, modes)); + ASSERT_STREQ(path.C_Str(), "..\\Primitives\\GridGrey.tga"); + + ASSERT_EQ(1u, scene->mNumTextures); + ASSERT_TRUE(scene->mTextures[0]->pcData); + ASSERT_EQ(9026u, scene->mTextures[0]->mWidth) << "FBX ASCII base64 compression used for a texture."; +} diff --git a/test/unit/utM3DImportExport.cpp b/test/unit/utM3DImportExport.cpp new file mode 100644 index 000000000..31028235d --- /dev/null +++ b/test/unit/utM3DImportExport.cpp @@ -0,0 +1,68 @@ +/* +--------------------------------------------------------------------------- +Open Asset Import Library (assimp) +--------------------------------------------------------------------------- + +Copyright (c) 2006-2019, assimp team + + + +All rights reserved. + +Redistribution and use of this software in source and binary forms, +with or without modification, are permitted provided that the following +conditions are met: + +* Redistributions of source code must retain the above +copyright notice, this list of conditions and the +following disclaimer. + +* Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the +following disclaimer in the documentation and/or other +materials provided with the distribution. + +* Neither the name of the assimp team, nor the names of its +contributors may be used to endorse or promote products +derived from this software without specific prior +written permission of the assimp team. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--------------------------------------------------------------------------- +*/ + +#include "UnitTestPCH.h" +#include "SceneDiffer.h" +#include "AbstractImportExportBase.h" + +#include +#include + +using namespace Assimp; + +class utM3DImportExport : public AbstractImportExportBase { +public: + virtual bool importerTest() { + Assimp::Importer importer; + const aiScene *scene = importer.ReadFile( ASSIMP_TEST_MODELS_DIR "/M3D/cube_normals.m3d", aiProcess_ValidateDataStructure ); +#ifndef ASSIMP_BUILD_NO_M3D_IMPORTER + return nullptr != scene; +#else + return nullptr == scene; +#endif // ASSIMP_BUILD_NO_M3D_IMPORTER + } +}; + +TEST_F( utM3DImportExport, importM3DFromFileTest ) { + EXPECT_TRUE( importerTest() ); +} diff --git a/test/unit/utVersion.cpp b/test/unit/utVersion.cpp index 233b2fb0b..66e832baa 100644 --- a/test/unit/utVersion.cpp +++ b/test/unit/utVersion.cpp @@ -48,7 +48,7 @@ TEST_F( utVersion, aiGetLegalStringTest ) { EXPECT_NE( lv, nullptr ); std::string text( lv ); - size_t pos( text.find( std::string( "2017" ) ) ); + size_t pos( text.find( std::string( "2019" ) ) ); EXPECT_NE( pos, std::string::npos ); } diff --git a/test/unit/utglTF2ImportExport.cpp b/test/unit/utglTF2ImportExport.cpp index 6925098b9..0df692f0b 100644 --- a/test/unit/utglTF2ImportExport.cpp +++ b/test/unit/utglTF2ImportExport.cpp @@ -405,6 +405,13 @@ TEST_F(utglTF2ImportExport, incorrect_vertex_arrays) { EXPECT_EQ(scene->mMeshes[7]->mNumFaces, 17u); } +TEST_F( utglTF2ImportExport, texture_transform_test ) { + Assimp::Importer importer; + const aiScene *scene = importer.ReadFile(ASSIMP_TEST_MODELS_DIR "/glTF2/textureTransform/TextureTransformTest.gltf", + aiProcess_ValidateDataStructure); + EXPECT_NE(nullptr, scene); +} + #ifndef ASSIMP_BUILD_NO_EXPORT TEST_F( utglTF2ImportExport, exportglTF2FromFileTest ) { EXPECT_TRUE( exporterTest() );