├── .clang-format ├── .dockerignore ├── .gitattributes ├── .gitignore ├── .travis.yml ├── CMakeLists.txt ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Dockerfile ├── FindFBX.cmake ├── LICENSE ├── README.md ├── azure-pipelines.yml ├── conanfile.py ├── docker-compose.yaml ├── npm └── fbx2gltf │ ├── LICENSE │ ├── README.md │ ├── bin │ ├── Darwin │ │ └── .keep │ ├── Linux │ │ └── .keep │ ├── README │ └── Windows_NT │ │ └── .keep │ ├── index.js │ ├── package-lock.json │ ├── package.json │ └── yarn.lock ├── src ├── FBX2glTF.cpp ├── FBX2glTF.h ├── fbx │ ├── Fbx2Raw.cpp │ ├── Fbx2Raw.hpp │ ├── FbxBlendShapesAccess.cpp │ ├── FbxBlendShapesAccess.hpp │ ├── FbxLayerElementAccess.hpp │ ├── FbxSkinningAccess.cpp │ ├── FbxSkinningAccess.hpp │ └── materials │ │ ├── 3dsMaxPhysicalMaterial.cpp │ │ ├── FbxMaterials.cpp │ │ ├── FbxMaterials.hpp │ │ ├── RoughnessMetallicMaterials.hpp │ │ ├── StingrayPBSMaterial.cpp │ │ ├── TraditionalMaterials.cpp │ │ └── TraditionalMaterials.hpp ├── gltf │ ├── GltfModel.cpp │ ├── GltfModel.hpp │ ├── Raw2Gltf.cpp │ ├── Raw2Gltf.hpp │ ├── TextureBuilder.cpp │ ├── TextureBuilder.hpp │ └── properties │ │ ├── AccessorData.cpp │ │ ├── AccessorData.hpp │ │ ├── AnimationData.cpp │ │ ├── AnimationData.hpp │ │ ├── BufferData.cpp │ │ ├── BufferData.hpp │ │ ├── BufferViewData.cpp │ │ ├── BufferViewData.hpp │ │ ├── CameraData.cpp │ │ ├── CameraData.hpp │ │ ├── ImageData.cpp │ │ ├── ImageData.hpp │ │ ├── LightData.cpp │ │ ├── LightData.hpp │ │ ├── MaterialData.cpp │ │ ├── MaterialData.hpp │ │ ├── MeshData.cpp │ │ ├── MeshData.hpp │ │ ├── NodeData.cpp │ │ ├── NodeData.hpp │ │ ├── PrimitiveData.cpp │ │ ├── PrimitiveData.hpp │ │ ├── SamplerData.hpp │ │ ├── SceneData.cpp │ │ ├── SceneData.hpp │ │ ├── SkinData.cpp │ │ ├── SkinData.hpp │ │ ├── TextureData.cpp │ │ └── TextureData.hpp ├── mathfu.hpp ├── raw │ ├── RawModel.cpp │ └── RawModel.hpp └── utils │ ├── File_Utils.cpp │ ├── File_Utils.hpp │ ├── Image_Utils.cpp │ ├── Image_Utils.hpp │ └── String_Utils.hpp └── third_party ├── CLI11 └── CLI11.hpp ├── json └── json.hpp └── stb ├── stb_image.h └── stb_image_write.h /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | AccessModifierOffset: -1 3 | AlignAfterOpenBracket: AlwaysBreak 4 | AlignConsecutiveAssignments: false 5 | AlignConsecutiveDeclarations: false 6 | AlignEscapedNewlinesLeft: true 7 | AlignOperands: false 8 | AlignTrailingComments: false 9 | AllowAllParametersOfDeclarationOnNextLine: false 10 | AllowShortBlocksOnASingleLine: false 11 | AllowShortCaseLabelsOnASingleLine: false 12 | AllowShortFunctionsOnASingleLine: Empty 13 | AllowShortIfStatementsOnASingleLine: false 14 | AllowShortLoopsOnASingleLine: false 15 | AlwaysBreakAfterReturnType: None 16 | AlwaysBreakBeforeMultilineStrings: true 17 | AlwaysBreakTemplateDeclarations: true 18 | BinPackArguments: false 19 | BinPackParameters: false 20 | BraceWrapping: 21 | AfterClass: false 22 | AfterControlStatement: false 23 | AfterEnum: false 24 | AfterFunction: false 25 | AfterNamespace: false 26 | AfterObjCDeclaration: false 27 | AfterStruct: false 28 | AfterUnion: false 29 | BeforeCatch: false 30 | BeforeElse: false 31 | IndentBraces: false 32 | BreakBeforeBinaryOperators: None 33 | BreakBeforeBraces: Attach 34 | BreakBeforeTernaryOperators: true 35 | BreakConstructorInitializersBeforeComma: false 36 | BreakAfterJavaFieldAnnotations: false 37 | BreakStringLiterals: false 38 | ColumnLimit: 100 39 | CommentPragmas: '^ IWYU pragma:' 40 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 41 | ConstructorInitializerIndentWidth: 4 42 | ContinuationIndentWidth: 4 43 | Cpp11BracedListStyle: true 44 | DerivePointerAlignment: false 45 | DisableFormat: false 46 | ForEachMacros: [ FOR_EACH, FOR_EACH_ENUMERATE, FOR_EACH_KV, FOR_EACH_R, FOR_EACH_RANGE, FOR_EACH_RANGE_R, ] 47 | IncludeCategories: 48 | - Regex: '^<.*\.h(pp)?>' 49 | Priority: 1 50 | - Regex: '^<.*' 51 | Priority: 2 52 | - Regex: '.*' 53 | Priority: 3 54 | IndentCaseLabels: true 55 | IndentWidth: 2 56 | IndentWrappedFunctionNames: false 57 | KeepEmptyLinesAtTheStartOfBlocks: false 58 | MacroBlockBegin: '' 59 | MacroBlockEnd: '' 60 | MaxEmptyLinesToKeep: 1 61 | NamespaceIndentation: None 62 | ObjCBlockIndentWidth: 2 63 | ObjCSpaceAfterProperty: false 64 | ObjCSpaceBeforeProtocolList: false 65 | PenaltyBreakBeforeFirstCallParameter: 1 66 | PenaltyBreakComment: 300 67 | PenaltyBreakFirstLessLess: 120 68 | PenaltyBreakString: 1000 69 | PenaltyExcessCharacter: 1000000 70 | PenaltyReturnTypeOnItsOwnLine: 200 71 | PointerAlignment: Left 72 | ReflowComments: true 73 | SortIncludes: true 74 | SpaceAfterCStyleCast: false 75 | SpaceBeforeAssignmentOperators: true 76 | SpaceBeforeParens: ControlStatements 77 | SpaceInEmptyParentheses: false 78 | SpacesBeforeTrailingComments: 1 79 | SpacesInAngles: false 80 | SpacesInContainerLiterals: true 81 | SpacesInCStyleCastParentheses: false 82 | SpacesInParentheses: false 83 | SpacesInSquareBrackets: false 84 | Standard: Cpp11 85 | TabWidth: 8 86 | UseTab: Never 87 | ... 88 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .dockerignore 2 | Dockerfile 3 | sdk 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # FBX SDK 2 | *.a filter=lfs diff=lfs merge=lfs -text 3 | *.dylib filter=lfs diff=lfs merge=lfs -text 4 | *.so filter=lfs diff=lfs merge=lfs -text 5 | *.dll filter=lfs diff=lfs merge=lfs -text 6 | *.lib filter=lfs diff=lfs merge=lfs -text 7 | 8 | # TEST FILES 9 | *.glb filter=lfs diff=lfs merge=lfs -text 10 | *.fbx filter=lfs diff=lfs merge=lfs -text 11 | *.tga filter=lfs diff=lfs merge=lfs -text 12 | *.png filter=lfs diff=lfs merge=lfs -text 13 | *.jpg filter=lfs diff=lfs merge=lfs -text 14 | 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | npm/fbx2gltf/bin/Darwin/FBX2glTF 2 | npm/fbx2gltf/bin/Linux/FBX2glTF 3 | npm/fbx2gltf/bin/Windows_NT/FBX2glTF.exe 4 | npm/fbx2gltf/node_modules/ 5 | npm/tests/node_modules/ 6 | npm/tests/test/*.js 7 | npm/tests/test/*.js.map 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | git: 2 | lfs_skip_smudge: true 3 | 4 | matrix: 5 | include: 6 | - os: linux 7 | dist: xenial 8 | env: 9 | - APP_NAME="FBX2glTF-linux-x64" 10 | - CONAN_CONFIG="-s compiler.libcxx=libstdc++11" 11 | - FBXSDK_TARBALL="https://github.com/zellski/FBXSDK-Linux/archive/2019.2.tar.gz" 12 | - TAR_WILDCARDS="--wildcards" 13 | - os: osx 14 | osx_image: xcode10.2 15 | env: 16 | - APP_NAME="FBX2glTF-darwin-x64" 17 | - CONAN_CONFIG="-s compiler=apple-clang -s compiler.version=10.0 -s compiler.libcxx=libc++" 18 | - FBXSDK_TARBALL="https://github.com/zellski/FBXSDK-Darwin/archive/2019.2.tar.gz" 19 | - TAR_WILDCARDS="" 20 | compiler: gcc 21 | language: generic 22 | 23 | #disabled for now 24 | #cache: 25 | # directories: 26 | # - ${HOME}/.conan 27 | 28 | addons: 29 | apt: 30 | packages: zstd 31 | homebrew: 32 | packages: zstd 33 | 34 | install: 35 | - curl -sL "${FBXSDK_TARBALL}" | tar xz --strip-components=1 ${TAR_WILDCARDS} */sdk 36 | - zstd -d -r --rm sdk 37 | - git clone --depth 1 git://github.com/astropy/ci-helpers.git 38 | - source ci-helpers/travis/setup_conda.sh 39 | - conda config --set always_yes yes 40 | - conda info -a 41 | - conda create -n travis_env python=3.7 pip 42 | - conda activate travis_env 43 | - pip install conan 44 | - conan user 45 | - conan remote add --force bincrafters https://api.bintray.com/conan/bincrafters/public-conan 46 | 47 | script: 48 | - conan install . -i build -s build_type=Release ${CONAN_CONFIG} 49 | - conan build . -bf build 50 | - mv build/FBX2glTF build/${APP_NAME} 51 | 52 | notifications: 53 | webhooks: 54 | - "https://code.facebook.com/travis/webhook/" 55 | deploy: 56 | provider: releases 57 | api_key: 58 | secure: V9CTmZKM7yvsT/WCesJ/tLTuapSf0oIp73zyZrwID7zQtXaq1QJSna4tWM2T0qeZIYhniH1/mqEr2jZVW1txmYn9ZxUMH1Nmp9zzOGl/q+JlRrJUi6HRUWWhCMz003L90whngyOcGI+T7rHtcVcby4owVsze15SrQqqV74NXI8DYNIbNgQR1Nwmqsrg0QirFPEBaIKDAiKonnRDWKPy2P8vqnN9fLhj00uHLwuvahlKAnWFEbNnFbiRScKifB+Mlo6Pf6r64iikrxS2jBxAgSsvPLkuemWLmaHTeGbJMM82aqh5vGSvgYcExvZi+0RdXeIcBdv/jaivM/xge4aZ+4P+IJoX32ZNCcYFMsqES+a6TztkywMs2k1r5gV6LrTjeXJsINSW+BDFdmrwmkudETc4gelQgkMmEkdCwFHENtZGl65z8HJDQKcu9F8NQlhNU7Z5rwQNLmYccvktSDhwbFSG5eq2kFFfcbVx3ovvn1voRTNnyhhVD2ZnLepSQInAVkZbaLkE90bQ+t9icf8uDdHDn17zOQaAZuecPlSW1y4XUCJnZCi0JPLhdSmQYiF60LHYI6xDneC8pmIz8kCUbk921zu8bJBy7zKHmfHy2vqNlPKuRULRIs5QzY31jf2PVZHzB5zX3KSqx9Dd+3DtgbLX2HLaZnANbkQc0rr1X2kk= 59 | file: build/${APP_NAME} 60 | skip_cleanup: true 61 | on: 62 | repo: facebookincubator/FBX2glTF 63 | tags: true 64 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | project(FBX2glTF) 3 | 4 | set(typical_usage_str 5 | "Example usage:\n\ 6 | > mkdir -p build_debug\n\ 7 | > conan install . -i build_debug -s build_type=Debug -e FBXSDK_SDKS=/home/zell/FBXSDK\n\ 8 | > conan build . -bf build_debug") 9 | 10 | if ("${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}") 11 | message(FATAL_ERROR 12 | "Building from within the source tree is not supported! ${typical_usage_str}") 13 | endif () 14 | 15 | set(CMAKE_CXX_STANDARD 11) 16 | 17 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}") 18 | include(ExternalProject) 19 | 20 | # FBX 21 | foreach (FBXSDK_VERSION "2019.2") 22 | find_package(FBX) 23 | if (FBXSDK_FOUND) 24 | break() 25 | endif() 26 | endforeach(FBXSDK_VERSION) 27 | if (NOT FBXSDK_FOUND) 28 | message(FATAL_ERROR 29 | "Can't find FBX SDK in either:\n" 30 | " - Mac OS X: ${FBXSDK_APPLE_ROOT}\n" 31 | " - Windows: ${FBXSDK_WINDOWS_ROOT}\n" 32 | " - Linux: ${FBXSDK_LINUX_ROOT}" 33 | ) 34 | endif() 35 | 36 | if(NOT EXISTS "${CMAKE_BINARY_DIR}/conan_paths.cmake") 37 | message(FATAL_ERROR 38 | "The Conan package manager must run ('install') first. ${typical_usage_str}") 39 | endif() 40 | include("${CMAKE_BINARY_DIR}/conan_paths.cmake") 41 | 42 | set(CMAKE_THREAD_PREFER_PTHREAD TRUE) 43 | find_package(Threads REQUIRED) 44 | 45 | list(INSERT CMAKE_MODULE_PATH 0 "${CMAKE_BINARY_DIR}") 46 | 47 | # stuff we get from Conan 48 | find_package(boost_filesystem MODULE REQUIRED) 49 | find_package(boost_optional MODULE REQUIRED) 50 | find_package(libxml2 MODULE REQUIRED) 51 | find_package(zlib MODULE REQUIRED) 52 | find_package(fmt MODULE REQUIRED) 53 | 54 | # create a compilation database for e.g. clang-tidy 55 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 56 | 57 | # DRACO 58 | ExternalProject_Add(Draco 59 | GIT_REPOSITORY https://github.com/google/draco 60 | GIT_TAG 1.3.4 61 | PREFIX draco 62 | INSTALL_DIR 63 | CMAKE_ARGS 64 | -DCMAKE_INSTALL_PREFIX= 65 | -DBUILD_FOR_GLTF=1 66 | ) 67 | set(DRACO_INCLUDE_DIR "${CMAKE_BINARY_DIR}/draco/include") 68 | if (WIN32) 69 | set(DRACO_LIB "${CMAKE_BINARY_DIR}/draco/lib/dracoenc.lib") 70 | else() 71 | set(DRACO_LIB "${CMAKE_BINARY_DIR}/draco/lib/libdracoenc.a") 72 | endif() 73 | 74 | # MATHFU 75 | set(mathfu_build_benchmarks OFF CACHE BOOL "") 76 | set(mathfu_build_tests OFF CACHE BOOL "") 77 | ExternalProject_Add(MathFu 78 | PREFIX mathfu 79 | GIT_REPOSITORY https://github.com/google/mathfu 80 | GIT_TAG v1.1.0 81 | CONFIGURE_COMMAND ${CMAKE_COMMAND} -E echo "Skipping MathFu configure step." 82 | BUILD_COMMAND ${CMAKE_COMMAND} -E echo "Skipping MathFu build step." 83 | INSTALL_COMMAND ${CMAKE_COMMAND} -E echo "Skipping MathFu install step." 84 | ) 85 | set(MATHFU_INCLUDE_DIRS 86 | "${CMAKE_BINARY_DIR}/mathfu/src/MathFu/include/" 87 | "${CMAKE_BINARY_DIR}/mathfu/src/MathFu/dependencies/vectorial/include") 88 | 89 | # OrderedMap 90 | ExternalProject_Add(FiFoMap 91 | PREFIX fifo_map 92 | GIT_REPOSITORY https://github.com/nlohmann/fifo_map 93 | CONFIGURE_COMMAND ${CMAKE_COMMAND} -E echo "Skipping FiFoMap configure step." 94 | BUILD_COMMAND ${CMAKE_COMMAND} -E echo "Skipping FiFoMap build step." 95 | INSTALL_COMMAND ${CMAKE_COMMAND} -E echo "Skipping FiFoMap install step." 96 | ) 97 | set(FIFO_MAP_INCLUDE_DIR "${CMAKE_BINARY_DIR}/fifo_map/src/FiFoMap/src") 98 | 99 | 100 | # cppcodec 101 | ExternalProject_Add(CPPCodec 102 | PREFIX cppcodec 103 | GIT_REPOSITORY https://github.com/tplgy/cppcodec 104 | CONFIGURE_COMMAND ${CMAKE_COMMAND} -E echo "Skipping CPPCodec configure step." 105 | BUILD_COMMAND ${CMAKE_COMMAND} -E echo "Skipping CPPCodec build step." 106 | INSTALL_COMMAND ${CMAKE_COMMAND} -E echo "Skipping CPPCodec install step." 107 | ) 108 | set(CPPCODEC_INCLUDE_DIR "${CMAKE_BINARY_DIR}/cppcodec/src/CPPCodec") 109 | 110 | if (APPLE) 111 | find_library(CF_FRAMEWORK CoreFoundation) 112 | message("CoreFoundation Framework: ${CF_FRAMEWORK}") 113 | set(FRAMEWORKS ${CF_FRAMEWORK}) 114 | endif() 115 | 116 | set(LIB_SOURCE_FILES 117 | src/FBX2glTF.h 118 | src/fbx/materials/3dsMaxPhysicalMaterial.cpp 119 | src/fbx/materials/FbxMaterials.cpp 120 | src/fbx/materials/FbxMaterials.hpp 121 | src/fbx/materials/RoughnessMetallicMaterials.hpp 122 | src/fbx/materials/StingrayPBSMaterial.cpp 123 | src/fbx/materials/TraditionalMaterials.cpp 124 | src/fbx/materials/TraditionalMaterials.hpp 125 | src/fbx/Fbx2Raw.cpp 126 | src/fbx/Fbx2Raw.hpp 127 | src/fbx/FbxBlendShapesAccess.cpp 128 | src/fbx/FbxBlendShapesAccess.hpp 129 | src/fbx/FbxLayerElementAccess.hpp 130 | src/fbx/FbxSkinningAccess.cpp 131 | src/fbx/FbxSkinningAccess.hpp 132 | src/gltf/Raw2Gltf.cpp 133 | src/gltf/Raw2Gltf.hpp 134 | src/gltf/GltfModel.cpp 135 | src/gltf/GltfModel.hpp 136 | src/gltf/TextureBuilder.cpp 137 | src/gltf/TextureBuilder.hpp 138 | src/gltf/properties/AccessorData.cpp 139 | src/gltf/properties/AccessorData.hpp 140 | src/gltf/properties/AnimationData.cpp 141 | src/gltf/properties/AnimationData.hpp 142 | src/gltf/properties/BufferData.cpp 143 | src/gltf/properties/BufferData.hpp 144 | src/gltf/properties/BufferViewData.cpp 145 | src/gltf/properties/BufferViewData.hpp 146 | src/gltf/properties/CameraData.cpp 147 | src/gltf/properties/CameraData.hpp 148 | src/gltf/properties/ImageData.cpp 149 | src/gltf/properties/ImageData.hpp 150 | src/gltf/properties/LightData.cpp 151 | src/gltf/properties/LightData.hpp 152 | src/gltf/properties/MaterialData.cpp 153 | src/gltf/properties/MaterialData.hpp 154 | src/gltf/properties/MeshData.cpp 155 | src/gltf/properties/MeshData.hpp 156 | src/gltf/properties/NodeData.cpp 157 | src/gltf/properties/NodeData.hpp 158 | src/gltf/properties/PrimitiveData.cpp 159 | src/gltf/properties/PrimitiveData.hpp 160 | src/gltf/properties/SamplerData.hpp 161 | src/gltf/properties/SceneData.cpp 162 | src/gltf/properties/SceneData.hpp 163 | src/gltf/properties/SkinData.cpp 164 | src/gltf/properties/SkinData.hpp 165 | src/gltf/properties/TextureData.cpp 166 | src/gltf/properties/TextureData.hpp 167 | src/mathfu.hpp 168 | src/raw/RawModel.cpp 169 | src/raw/RawModel.hpp 170 | src/utils/File_Utils.cpp 171 | src/utils/File_Utils.hpp 172 | src/utils/Image_Utils.cpp 173 | src/utils/Image_Utils.hpp 174 | src/utils/String_Utils.hpp 175 | third_party/CLI11/CLI11.hpp 176 | ) 177 | 178 | add_library(libFBX2glTF STATIC ${LIB_SOURCE_FILES}) 179 | set_target_properties(libFBX2glTF PROPERTIES OUTPUT_NAME "FBX2glTF") 180 | add_executable(appFBX2glTF src/FBX2glTF.cpp) 181 | set_target_properties(appFBX2glTF PROPERTIES OUTPUT_NAME "FBX2glTF") 182 | 183 | add_dependencies(libFBX2glTF 184 | Draco 185 | MathFu 186 | FiFoMap 187 | CPPCodec 188 | ) 189 | 190 | if (NOT MSVC) 191 | # Disable annoying & spammy warning from FBX SDK header file 192 | target_compile_options(libFBX2glTF PRIVATE 193 | "-Wno-null-dereference" 194 | "-Wunused" 195 | ) 196 | target_compile_options(appFBX2glTF PRIVATE 197 | "-Wno-null-dereference" 198 | "-Wunused" 199 | ) 200 | endif() 201 | 202 | target_link_libraries(libFBX2glTF 203 | ${FRAMEWORKS} 204 | boost_filesystem::boost_filesystem 205 | boost_optional::boost_optional 206 | ${DRACO_LIB} 207 | optimized ${FBXSDK_LIBRARY} 208 | debug ${FBXSDK_LIBRARY_DEBUG} 209 | fmt::fmt 210 | libxml2::libxml2 211 | zlib::zlib 212 | ${CMAKE_DL_LIBS} 213 | ${CMAKE_THREAD_LIBS_INIT} 214 | ) 215 | 216 | if (APPLE) 217 | find_package(Iconv MODULE REQUIRED) 218 | target_link_libraries(libFBX2glTF Iconv) 219 | else() 220 | find_package(libiconv MODULE REQUIRED) 221 | target_link_libraries(libFBX2glTF libiconv::libiconv) 222 | endif() 223 | 224 | target_include_directories(libFBX2glTF PUBLIC 225 | ${CMAKE_CURRENT_SOURCE_DIR}/src 226 | ) 227 | 228 | target_include_directories(libFBX2glTF SYSTEM PUBLIC 229 | "third_party/stb" 230 | "third_party/json" 231 | ${FBXSDK_INCLUDE_DIR} 232 | ${DRACO_INCLUDE_DIR} 233 | ${MATHFU_INCLUDE_DIRS} 234 | ${FIFO_MAP_INCLUDE_DIR} 235 | ${CPPCODEC_INCLUDE_DIR} 236 | ) 237 | 238 | target_include_directories(appFBX2glTF PUBLIC 239 | "third_party/CLI11" 240 | ) 241 | target_link_libraries(appFBX2glTF libFBX2glTF) 242 | 243 | install (TARGETS libFBX2glTF appFBX2glTF 244 | RUNTIME DESTINATION bin 245 | ARCHIVE DESTINATION lib 246 | ) 247 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | Facebook has adopted a Code of Conduct that we expect project participants to adhere to. 4 | Please read the [full text](https://code.fb.com/codeofconduct/) 5 | so that you can understand what actions will and will not be tolerated. 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to FBX2glTF 2 | We want to make contributing to this project as easy and transparent as 3 | possible. 4 | 5 | ## Pull Requests 6 | We actively welcome your pull requests. 7 | 8 | 1. Fork the repo and create your branch from `master`. 9 | 2. Ensure your code matches the style of existing source. 10 | 3. In case of behavioural changes, update this documentation. 11 | 4. If you haven't already, complete the Contributor License Agreement ("CLA"). 12 | 13 | ## Contributor License Agreement ("CLA") 14 | In order to accept your pull request, we need you to submit a CLA. You only need 15 | to do this once to work on any of Facebook's open source projects. 16 | 17 | Complete your CLA here: 18 | 19 | ## Issues 20 | We use GitHub issues to track public bugs. Please ensure your description is 21 | clear and has sufficient instructions to be able to reproduce the issue. 22 | 23 | Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe 24 | disclosure of security bugs. In those cases, please go through the process 25 | outlined on that page and do not file a public issue. 26 | 27 | ## License 28 | By contributing to FBX2glTF, you agree that your contributions will be licensed 29 | under the LICENSE file in the root directory of this source tree. 30 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:16.04 2 | 3 | RUN apt-get update && \ 4 | apt-get install -y software-properties-common && \ 5 | add-apt-repository ppa:jonathonf/python-3.6 && \ 6 | add-apt-repository ppa:git-core/ppa && \ 7 | apt-get update && \ 8 | apt-get install -y python3.6 curl build-essential cmake libxml2-dev zlib1g-dev git && \ 9 | curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py && python3 get-pip.py && \ 10 | pip install conan && \ 11 | conan remote add bincrafters https://api.bintray.com/conan/bincrafters/public-conan 12 | 13 | # Install FBX SDK 14 | RUN mkdir -p /fbx2gltf/sdk/Linux/2019.2 && \ 15 | curl -L https://www.autodesk.com/content/dam/autodesk/www/adn/fbx/20192/fbx20192_fbxsdk_linux.tar.gz -o fbx20192_fbxsdk_linux.tar.gz && \ 16 | tar -xvf fbx20192_fbxsdk_linux.tar.gz && \ 17 | echo "yes\nn" | ./fbx20192_fbxsdk_linux /fbx2gltf/sdk/Linux/2019.2 && \ 18 | rm -rf /fbxsdktemp 19 | 20 | COPY . /fbx2gltf 21 | 22 | WORKDIR /fbx2gltf 23 | 24 | # Build and install 25 | RUN conan install . -i docker-build -s build_type=Release -s compiler=gcc -s compiler.version=5 -s compiler.libcxx=libstdc++11 && \ 26 | conan build -bf docker-build . && \ 27 | cp docker-build/FBX2glTF /usr/bin && \ 28 | cd / && \ 29 | rm -rf /fbx2gltf /root/.conan 30 | -------------------------------------------------------------------------------- /FindFBX.cmake: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2014-present, Facebook, Inc. 2 | # All rights reserved. 3 | # 4 | # Helper function for finding the FBX SDK. 5 | # Cribbed & tweaked from https://github.com/floooh/fbxc/ 6 | # 7 | # params: FBXSDK_VERSION 8 | # FBXSDK_SDKS 9 | # 10 | # sets: FBXSDK_FOUND 11 | # FBXSDK_DIR 12 | # FBXSDK_LIBRARY 13 | # FBXSDK_LIBRARY_DEBUG 14 | # FBXSDK_INCLUDE_DIR 15 | # 16 | 17 | # semi-hack to detect architecture 18 | if( CMAKE_SIZEOF_VOID_P MATCHES 8 ) 19 | # void ptr = 8 byte --> x86_64 20 | set(ARCH_32 OFF) 21 | else() 22 | # void ptr != 8 byte --> x86 23 | set(ARCH_32 OFF) 24 | endif() 25 | 26 | if (NOT DEFINED FBXSDK_VERSION) 27 | set(FBXSDK_VERSION "2019.2") 28 | endif() 29 | 30 | set(_fbxsdk_vstudio_version "vs2017") 31 | 32 | message("Looking for FBX SDK version: ${FBXSDK_VERSION}") 33 | 34 | if (NOT DEFINED FBXSDK_SDKS) 35 | set(FBXSDK_SDKS "${CMAKE_CURRENT_SOURCE_DIR}/sdk") 36 | endif() 37 | 38 | get_filename_component(FBXSDK_SDKS_ABS ${FBXSDK_SDKS} ABSOLUTE) 39 | 40 | set(FBXSDK_APPLE_ROOT "${FBXSDK_SDKS_ABS}/Darwin/${FBXSDK_VERSION}") 41 | set(FBXSDK_LINUX_ROOT "${FBXSDK_SDKS_ABS}/Linux/${FBXSDK_VERSION}") 42 | set(FBXSDK_WINDOWS_ROOT "${FBXSDK_SDKS_ABS}/Windows/${FBXSDK_VERSION}") 43 | 44 | if (APPLE) 45 | set(_fbxsdk_root "${FBXSDK_APPLE_ROOT}") 46 | set(_fbxsdk_libdir_debug "lib/clang/debug") 47 | set(_fbxsdk_libdir_release "lib/clang/release") 48 | set(_fbxsdk_libname_debug "libfbxsdk.a") 49 | set(_fbxsdk_libname_release "libfbxsdk.a") 50 | elseif (WIN32) 51 | set(_fbxsdk_root "${FBXSDK_WINDOWS_ROOT}") 52 | if (ARCH_32) 53 | set(_fbxsdk_libdir_debug "lib/${_fbxsdk_vstudio_version}/x86/debug") 54 | set(_fbxsdk_libdir_release "lib/${_fbxsdk_vstudio_version}/x86/release") 55 | else() 56 | set(_fbxsdk_libdir_debug "lib/${_fbxsdk_vstudio_version}/x64/debug") 57 | set(_fbxsdk_libdir_release "lib/${_fbxsdk_vstudio_version}/x64/release") 58 | endif() 59 | set(_fbxsdk_libname_debug "libfbxsdk-md.lib") 60 | set(_fbxsdk_libname_release "libfbxsdk-md.lib") 61 | elseif (UNIX) 62 | set(_fbxsdk_root "${FBXSDK_LINUX_ROOT}") 63 | if (ARCH_32) 64 | set(_fbxsdk_libdir_debug "lib/gcc/x86/debug") 65 | set(_fbxsdk_libdir_release "lib/gcc/x86/release") 66 | else() 67 | set(_fbxsdk_libdir_debug "lib/gcc/x64/debug") 68 | set(_fbxsdk_libdir_release "lib/gcc/x64/release") 69 | endif() 70 | set(_fbxsdk_libname_debug "libfbxsdk.a") 71 | set(_fbxsdk_libname_release "libfbxsdk.a") 72 | else() 73 | message(FATAL_ERROR, "Unknown platform. Can't find FBX SDK.") 74 | endif() 75 | 76 | # should point the the FBX SDK installation dir 77 | set(FBXSDK_ROOT "${_fbxsdk_root}") 78 | message("FBXSDK_ROOT: ${FBXSDK_ROOT}") 79 | 80 | # find header dir and libs 81 | find_path(FBXSDK_INCLUDE_DIR "fbxsdk.h" 82 | NO_CMAKE_FIND_ROOT_PATH 83 | PATHS ${FBXSDK_ROOT} 84 | PATH_SUFFIXES "include") 85 | message("FBXSDK_INCLUDE_DIR: ${FBXSDK_INCLUDE_DIR}") 86 | 87 | find_library(FBXSDK_LIBRARY ${_fbxsdk_libname_release} 88 | NO_CMAKE_FIND_ROOT_PATH 89 | PATHS "${FBXSDK_ROOT}/${_fbxsdk_libdir_release}") 90 | message("FBXSDK_LIBRARY: ${FBXSDK_LIBRARY}") 91 | 92 | find_library(FBXSDK_LIBRARY_DEBUG ${_fbxsdk_libname_debug} 93 | NO_CMAKE_FIND_ROOT_PATH 94 | PATHS "${FBXSDK_ROOT}/${_fbxsdk_libdir_debug}") 95 | message("FBXSDK_LIBRARY_DEBUG: ${FBXSDK_LIBRARY_DEBUG}") 96 | 97 | if (FBXSDK_INCLUDE_DIR AND FBXSDK_LIBRARY AND FBXSDK_LIBRARY_DEBUG) 98 | set(FBXSDK_FOUND YES) 99 | else() 100 | set(FBXSDK_FOUND NO) 101 | endif() 102 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD License 2 | 3 | For FBX2glTF software 4 | 5 | Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, 8 | are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | * Neither the name Facebook nor the names of its contributors may be used to 18 | endorse or promote products derived from this software without specific 19 | prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 22 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 25 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 26 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 28 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FBX2glTF 2 | 3 | [![License](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause) 4 | 5 | This is a command line tool for converting 3D model assets on Autodesk's 6 | venerable [FBX](https://www.autodesk.com/products/fbx/overview) format to 7 | [glTF 2.0](https://github.com/KhronosGroup/glTF/tree/master/specification/2.0), 8 | a modern runtime asset delivery format. 9 | 10 | Precompiled binaries releases for Windows, Mac OS X and Linux may be 11 | found [here](https://github.com/facebookincubator/FBX2glTF/releases). 12 | 13 | Bleeding-edge binaries for Windows may be found [here](https://ci.appveyor.com/project/Facebook/fbx2gltf/build/artifacts). Linux and Mac OS X to come; meanwhile, you can [build your own](#building-it-on-your-own). 14 | 15 | [![Build Status](https://travis-ci.com/facebookincubator/FBX2glTF.svg?branch=master)](https://travis-ci.com/facebookincubator/FBX2glTF) 16 | [![Build status](https://ci.appveyor.com/api/projects/status/5mq4vbc44vmyec4w?svg=true)](https://ci.appveyor.com/project/Facebook/fbx2gltf) 17 | 18 | ## Running 19 | 20 | The tool can be invoked like so: 21 | 22 | ``` 23 | > FBX2glTF ~/models/butterfly.fbx 24 | ``` 25 | 26 | Or perhaps, as part of a more complex pipeline: 27 | 28 | ``` 29 | > FBX2glTF --binary --draco --verbose \ 30 | --input ~/models/source/butterfly.fbx \ 31 | --output ~/models/target/butterfly.glb 32 | ``` 33 | 34 | There are also some friendly & hands-on instructions available [over at Facebook](https://developers.facebook.com/docs/sharing/3d-posts/glb-tutorials/#convert-from-fbx). 35 | 36 | ### CLI Switches 37 | 38 | You can always run the binary with --help to see what options it takes: 39 | 40 | ``` 41 | FBX2glTF 0.9.7: Generate a glTF 2.0 representation of an FBX model. 42 | Usage: FBX2glTF [OPTIONS] [FBX Model] 43 | 44 | Positionals: 45 | FBX Model FILE The FBX model to convert. 46 | 47 | Options: 48 | -h,--help Print this help message and exit 49 | -v,--verbose Include blend shape tangents, if reported present by the FBX SDK. 50 | -V,--version 51 | -i,--input FILE The FBX model to convert. 52 | -o,--output TEXT Where to generate the output, without suffix. 53 | -e,--embed Inline buffers as data:// URIs within generated non-binary glTF. 54 | -b,--binary Output a single binary format .glb file. 55 | --long-indices (never|auto|always) 56 | Whether to use 32-bit indices. 57 | --compute-normals (never|broken|missing|always) 58 | When to compute vertex normals from mesh geometry. 59 | --anim-framerate (bake24|bake30|bake60) 60 | Select baked animation framerate. 61 | --flip-u Flip all U texture coordinates. 62 | --no-flip-u Don't flip U texture coordinates. 63 | --flip-v Flip all V texture coordinates. 64 | --no-flip-v Don't flip V texture coordinates. 65 | --no-khr-lights-punctual Don't use KHR_lights_punctual extension to export FBX lights. 66 | --user-properties Transcribe FBX User Properties into glTF node and material 'extras'. 67 | --blend-shape-normals Include blend shape normals, if reported present by the FBX SDK. 68 | --blend-shape-tangents Include blend shape tangents, if reported present by the FBX SDK. 69 | -k,--keep-attribute (position|normal|tangent|binormial|color|uv0|uv1|auto) ... 70 | Used repeatedly to build a limiting set of vertex attributes to keep. 71 | --fbx-temp-dir DIR Temporary directory to be used by FBX SDK. 72 | 73 | 74 | Materials: 75 | --pbr-metallic-roughness Try to glean glTF 2.0 native PBR attributes from the FBX. 76 | --khr-materials-unlit Use KHR_materials_unlit extension to request an unlit shader. 77 | 78 | 79 | Draco: 80 | -d,--draco Apply Draco mesh compression to geometries. 81 | --draco-compression-level INT in [0 - 10]=7 82 | The compression level to tune Draco to. 83 | --draco-bits-for-position INT in [1 - 32]=14 84 | How many bits to quantize position to. 85 | --draco-bits-for-uv INT in [1 - 32]=10 86 | How many bits to quantize UV coordinates to. 87 | --draco-bits-for-normals INT in [1 - 32]=10 88 | How many bits to quantize nornals to. 89 | --draco-bits-for-colors INT in [1 - 32]=8 90 | How many bits to quantize colors to. 91 | --draco-bits-for-other INT in [1 - 32]=8 92 | How many bits to quantize all other vertex attributes to. 93 | ``` 94 | 95 | Some of these switches are not obvious: 96 | 97 | - `--embed` is the way to get a single distributable file without using the 98 | binary format. It encodes the binary buffer(s) as a single base64-encoded 99 | `data://` URI. This is a very slow and space-consuming way to accomplish what 100 | the binary format was invented to do simply and efficiently, but it can be 101 | useful e.g. for loaders that don't understand the .glb format. 102 | - `--flip-u` and `--flip-v`, when enabled, will apply a `x -> (1.0 - x)` 103 | function to all `u` or `v` texture coordinates respectively. The `u` version 104 | is perhaps not commonly used, but flipping `v` is **the default behaviour**. 105 | Your FBX is likely constructed with the assumption that `(0, 0)` is bottom 106 | left, whereas glTF has `(0, 0)` as top left. To produce spec-compliant glTF, 107 | we must flip the texcoords. To request unflipped coordinates: 108 | - `--long-indices` lets you force the use of either 16-bit or 32-bit indices. 109 | The default option is auto, which make the choice on a per-mesh-size basis. 110 | - `--compute-normals` controls when automatic vertex normals should be computed 111 | from the mesh. By default, empty normals (which are forbidden by glTF) are 112 | replaced. A choice of 'missing' implies 'broken', but additionally creates 113 | normals for models that lack them completely. 114 | - `--no-flip-v` will actively disable v coordinat flipping. This can be useful 115 | if your textures are pre-flipped, or if for some other reason you were already 116 | in a glTF-centric texture coordinate system. 117 | - All three material options are, in their own way, works in progress, but the 118 | `--pbr-metallic-roughness` switch is at least compliant with the core spec; 119 | unlike the others, it does not depend on an unratified extension. That option 120 | will be chosen by default if you supply none of the others. Material switches 121 | are documented further below. 122 | - If you supply any `-keep-attribute` option, you enable a mode wherein you must 123 | supply it repeatedly to list _all_ the vertex attributes you wish to keep in 124 | the conversion process. This is a way to trim the size of the resulting glTF 125 | if you know the FBX contains superfluous attributes. The supported arguments 126 | are `position`, `normal`, `tangent`, `color`, `uv0`, and `uv1`. 127 | - When **blend shapes** are present, you may use `--blend-shape-normals` and 128 | `--blend-shape-tangents` to include normal and tangent attributes in the glTF 129 | morph targets. They are not included by default because they rarely or never 130 | seem to be correctly present in the actual FBX source, which means the SDK 131 | must be computing them from geometry, unasked? In any case, they are beyond 132 | the control of the artist, and can yield strange crinkly behaviour. Since 133 | they also take up significant space in the output file, we made them opt-in. 134 | 135 | ## Building it on your own 136 | 137 | We currently depend on the open source projects 138 | [Draco](https://github.com/google/draco), 139 | [MathFu](https://github.com/google/mathfu), 140 | [Json](https://github.com/nlohmann/json), 141 | [cppcodec](https://github.com/tplgy/cppcodec), 142 | [CLI11](https://github.com/CLIUtils/CLI11), 143 | [stb](https://github.com/nothings/stb), 144 | and [fmt](https://github.com/fmtlib/fmt); 145 | all of which are automatically downloaded and/or built. 146 | 147 | **At present, only version 2019.2 of the FBX SDK is supported**. The 148 | build system will not successfully locate any other version. 149 | 150 | ### Linux and MacOS X 151 | 152 | Your development environment will need to have: 153 | 154 | - build essentials (gcc for Linux, clang for Mac) 155 | - cmake 156 | - python 3.\* and associated pip3/pip command 157 | - zstd 158 | 159 | Then, compilation on Unix machines will look something like: 160 | 161 | ``` 162 | # Determine SDK location & build settings for Linux vs (Recent) Mac OS X 163 | > if [[ "$OSTYPE" == "darwin"* ]]; then 164 | export CONAN_CONFIG="-s compiler=apple-clang -s compiler.version=10.0 -s compiler.libcxx=libc++" 165 | export FBXSDK_TARBALL="https://github.com/zellski/FBXSDK-Darwin/archive/2019.2.tar.gz" 166 | elif [[ "$OSTYPE" == "linux"* ]]; then 167 | export CONAN_CONFIG="-s compiler.libcxx=libstdc++11" 168 | export FBXSDK_TARBALL="https://github.com/zellski/FBXSDK-Linux/archive/2019.2.tar.gz" 169 | else 170 | echo "This snippet only handles Mac OS X and Linux." 171 | fi 172 | 173 | # Fetch Project 174 | > git clone https://github.com/facebookincubator/FBX2glTF.git 175 | > cd FBX2glTF 176 | 177 | # Fetch and unpack FBX SDK 178 | > curl -sL "${FBXSDK_TARBALL}" | tar xz --strip-components=1 --include */sdk/ 179 | # Then decompress the contents 180 | > zstd -d -r --rm sdk 181 | 182 | # Install and configure Conan, if needed 183 | > pip3 install conan # or sometimes just "pip"; you may need to install Python/PIP 184 | > conan remote add --force bincrafters https://api.bintray.com/conan/bincrafters/public-conan 185 | 186 | # Initialize & run build 187 | > conan install . -i build -s build_type=Release ${CONAN_CONFIG} 188 | > conan build . -bf build 189 | ``` 190 | 191 | If all goes well, you will end up with a statically linked executable in `./build/FBX2glTF`. 192 | 193 | ### Windows 194 | 195 | the below is out of date 196 | 197 | Windows users may [download](https://cmake.org/download) CMake for Windows, 198 | install it and [run it](https://cmake.org/runningcmake/) on the FBX2glTF 199 | checkout (choose a build directory distinct from the source). 200 | 201 | As part of this process, you will be asked to choose which generator 202 | to use. **At present, only Visual Studio 2017 or 2019 is supported.** Older 203 | versions of the IDE are unlikely to successfully build the tool. 204 | 205 | Note that the `CMAKE_BUILD_TYPE` variable from the Unix Makefile system is 206 | entirely ignored here; it is when you open the generated solution that 207 | you will be choose one of the canonical build types — _Debug_, 208 | _Release_, _MinSizeRel_, and so on. 209 | 210 | ## Conversion Process 211 | 212 | The actual translation begins with the FBX SDK parsing the input file, and ends 213 | with the generation of the descriptive `JSON` that forms the core of glTF, along 214 | with binary buffers that hold geometry and animations (and optionally also 215 | emedded resources such as textures.) 216 | 217 | In the process, each mesh is ripped apart into a long list of triangles and 218 | their associated vertices, with a material assigned to each one. A similar 219 | process happens in reverse when we construct meshes and materials that conform 220 | to the expectations of the glTF format. 221 | 222 | ### Animations 223 | 224 | Every animation in the FBX file becomes an animation in the glTF file. The 225 | method used is one of "baking": we step through the interval of time spanned by 226 | the animation, keyframe by keyframe, calculate the local transform of each 227 | node, and whenever we find any node that's rotated, translated or scaled, we 228 | record that fact in the output. 229 | 230 | Beyond skeleton-based animation, _Blend Shapes_ are also supported; they are 231 | read from the FBX file on a per-mesh basis, and clips can use them by varying 232 | the weights associated with each one. 233 | 234 | The baking method has the benefit of being simple and precise. It has the 235 | drawback of creating potentially very large files. The more complex the 236 | animation rig, the less avoidable this data explosion is. 237 | 238 | There are three future enhancements we hope to see for animations: 239 | 240 | - Version 2.0 of glTF brought us support for expressing quadratic animation 241 | curves, where previously we had only had linear. Not coincidentally, quadratic 242 | splines are one of the key ways animations are expressed inside the FBX. When 243 | we find such a curve, it would be more efficient to output it without baking 244 | it into a long sequence of linear approximations. 245 | - We do not yet ever generate 246 | [sparse accessors](https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#sparse-accessors), 247 | but many animations (especially morph targets) would benefit from this 248 | storage optimisation. 249 | - Perhaps most useful in practice is the idea of compressing animation curves 250 | the same way we use Draco to compress meshes (see below). Like geometry, 251 | animations are highly redundant — each new value is highly predictable from 252 | preceding values. If Draco extends its support for animations (it's on their 253 | roadmap), or if someone else develops a glTF extension for animation 254 | compression, we will likely add support in this tool. 255 | 256 | ### Materials 257 | 258 | With glTF 2.0, we leaped headlong into physically-based rendering (PBR), where 259 | the canonical way of expressing what a mesh looks like is by describing its 260 | visible material in fundamental attributes like "how rough is this surface". 261 | 262 | By contrast, FBX's material support remains largely in the older world of 263 | Lambert and Phong, with simpler and more direct illumination and shading 264 | models. These modes are inherently incompatible — for example, textures in the 265 | old workflow often contain baked lighting of the type that would arise naturally 266 | in a PBR environment. 267 | 268 | Some material settings remain well supported and transfer automatically: 269 | 270 | - Emissive constants and textures 271 | - Occlusion maps 272 | - Normal maps 273 | 274 | This leaves the other traditional settings, first of Lambert: 275 | 276 | - Ambient — this is anathema in the PBR world, where such effects should 277 | emerge naturally from the fundamental colour of the material and any ambient 278 | lighting present. 279 | - Diffuse — the material's direction-agnostic, non-specular reflection, 280 | and additionally, with Blinn/Phong: 281 | - Specular — a more polished material's direction-sensitive reflection, 282 | - Shininess — just how polished the material is; a higher value here yields a 283 | more mirror-like surface. 284 | 285 | (All these can be either constants or textures.) 286 | 287 | #### Exporting as Unlit 288 | 289 | If you have a model was constructed using an unlit workflow, e.g. a photogrammetry 290 | capture or a landscape with careful baked-in lighting, you may choose to export 291 | it using the --khr-materials-common switch. This incurs a dependency on the glTF 292 | extension 'KHR_materials_unlit; a client that accepts that extension is making 293 | a promise it'll do its best to render pixel values without lighting calculations. 294 | 295 | **Note that at the time of writing, this glTF extension is still undergoing the 296 | ratification process** 297 | 298 | #### Exporting as Metallic-Roughness PBR 299 | 300 | Given the command line flag --pbr-metallic-roughness, we throw ourselves into 301 | the warm embrace of glTF 2.0's PBR preference. 302 | 303 | As mentioned above, there is little consensus in the world on how PBR should be 304 | represented in FBX. At present, we support only one format: Stingray PBS. This 305 | is a feature that comes bundled with Maya, and any PBR model exported through 306 | that route should be digested propertly by FBX2glTF. 307 | 308 | (A happy note: Allegorithmic's Substance Painter also exports Stingray PBS, 309 | when hooked up to Maya.) 310 | 311 | ## Draco Compression 312 | 313 | The tool will optionally apply [Draco](https://github.com/google/draco) 314 | compression to the geometric data of each mesh (vertex indices, positions, 315 | normals, per-vertex color, and so on). This can be dramatically effective 316 | in reducing the size of the output file, especially for static models. 317 | 318 | Enabling this feature adds an expressed required dependency in the glTF on the 319 | `KHR_draco_geometry_compression` extension, and can thus only be loaded by a 320 | viewer that is willing and able to decompress the data. 321 | 322 | **Note that at the time of writing, this glTF extension is still undergoing the 323 | ratification process.** 324 | 325 | ## Future Improvements 326 | 327 | This tool is under continuous development. We do not have a development roadmap 328 | per se, but some aspirations have been noted above. The canonical list of active 329 | TODO items can be found 330 | [on GitHub](https://github.com/facebookincubator/FBX2glTF/labels/enhancement). 331 | 332 | ## Authors 333 | 334 | - Pär Winzell 335 | - J.M.P. van Waveren 336 | - Amanda Watson 337 | 338 | ## License 339 | 340 | FBX2glTF is licensed under the [3-clause BSD license](LICENSE). 341 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # C/C++ with GCC 2 | # Build your C/C++ project with GCC using make. 3 | # Add steps that publish test results, save build artifacts, deploy, and more: 4 | # https://docs.microsoft.com/azure/devops/pipelines/apps/c-cpp/gcc 5 | 6 | jobs: 7 | - job: Linux 8 | pool: 9 | vmImage: 'Ubuntu 16.04' 10 | 11 | steps: 12 | - task: UsePythonVersion@0 13 | inputs: 14 | versionSpec: '3.6' 15 | architecture: 'x64' 16 | 17 | - script: python -m pip install --upgrade pip setuptools wheel 18 | displayName: 'Install Python tools' 19 | 20 | - script: | 21 | pip install conan 22 | conan remote add bincrafters https://api.bintray.com/conan/bincrafters/public-conan 23 | displayName: 'Install & configure Conan' 24 | 25 | - script: | 26 | conan install . -i build -s build_type=Release -e FBXSDK_SDKS=sdk 27 | displayName: 'Resolve binary dependencies and build CMake files.' 28 | 29 | - script: | 30 | conan build -bf build . 31 | mv build/FBX2glTF build/FBX2glTF-linux-x64 32 | displayName: 'Build FBX2glTF' 33 | 34 | - task: PublishBuildArtifacts@1 35 | inputs: 36 | pathtoPublish: 'build/FBX2glTF-linux-x64' 37 | artifactName: 'binaries' 38 | 39 | - job: Mac 40 | pool: 41 | vmImage: 'macOS-10.14' 42 | 43 | steps: 44 | - task: UsePythonVersion@0 45 | inputs: 46 | versionSpec: '3.6' 47 | architecture: 'x64' 48 | 49 | - script: python -m pip install --upgrade pip setuptools wheel 50 | displayName: 'Install Python tools' 51 | 52 | - script: | 53 | pip install conan 54 | conan remote add bincrafters https://api.bintray.com/conan/bincrafters/public-conan 55 | displayName: 'Install Conan' 56 | 57 | - script: | 58 | conan install . -i build -s compiler=apple-clang -s compiler=apple-clang -s compiler.version=10.0 -s compiler.libcxx=libc++ -s build_type=Release -e FBXSDK_SDKS=sdk 59 | displayName: 'Resolve binary dependencies and build CMake files.' 60 | 61 | - script: | 62 | conan build -bf build . 63 | mv build/FBX2glTF build/FBX2glTF-darwin-x64 64 | displayName: 'Build FBX2glTF' 65 | 66 | - task: PublishBuildArtifacts@1 67 | inputs: 68 | pathtoPublish: 'build/FBX2glTF-darwin-x64' 69 | artifactName: 'binaries' 70 | 71 | - job: Windows 72 | pool: 73 | vmImage: 'vs2017-win2016' 74 | 75 | steps: 76 | - task: UsePythonVersion@0 77 | inputs: 78 | versionSpec: '3.6' 79 | architecture: 'x64' 80 | 81 | - script: python -m pip install --upgrade pip setuptools wheel 82 | displayName: 'Install Python tools' 83 | 84 | - script: | 85 | pip install conan 86 | conan remote add bincrafters https://api.bintray.com/conan/bincrafters/public-conan 87 | displayName: 'Install Conan' 88 | 89 | - script: | 90 | conan install . -i build -s build_type=Release -e FBXSDK_SDKS=sdk 91 | displayName: 'Resolve binary dependencies and build CMake files.' 92 | 93 | - script: | 94 | conan build -bf build . 95 | move build\Release\FBX2glTF.exe build\Release\FBX2glTF-windows-x64.exe 96 | displayName: 'Build FBX2glTF' 97 | 98 | - task: PublishBuildArtifacts@1 99 | inputs: 100 | pathtoPublish: 'build/Release/FBX2glTF-windows-x64.exe' 101 | artifactName: 'binaries' 102 | -------------------------------------------------------------------------------- /conanfile.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved 2 | # 3 | 4 | import os 5 | 6 | from conans import ConanFile, CMake 7 | 8 | 9 | class FBX2glTFConan(ConanFile): 10 | settings = "os", "compiler", "build_type", "arch" 11 | requires = ( 12 | ("boost_filesystem/1.69.0@bincrafters/stable"), 13 | ("libiconv/1.15@bincrafters/stable"), 14 | ("zlib/1.2.11@conan/stable"), 15 | ("libxml2/2.9.9@bincrafters/stable"), 16 | ("fmt/5.3.0@bincrafters/stable"), 17 | ) 18 | generators = "cmake_find_package", "cmake_paths" 19 | 20 | def configure(self): 21 | if ( 22 | self.settings.compiler == "gcc" 23 | and self.settings.compiler.libcxx == "libstdc++" 24 | ): 25 | raise Exception( 26 | "Rerun 'conan install' with argument: '-s compiler.libcxx=libstdc++11'" 27 | ) 28 | 29 | def build(self): 30 | cmake = CMake(self) 31 | cmake.definitions["FBXSDK_SDKS"] = os.getenv("FBXSDK_SDKS", "sdk") 32 | cmake.configure() 33 | cmake.build() 34 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | services: 3 | fbx2gltf: 4 | build: 5 | context: . 6 | -------------------------------------------------------------------------------- /npm/fbx2gltf/LICENSE: -------------------------------------------------------------------------------- 1 | BSD License 2 | 3 | For FBX2glTF software 4 | 5 | Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, 8 | are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | * Neither the name Facebook nor the names of its contributors may be used to 18 | endorse or promote products derived from this software without specific 19 | prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 22 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 25 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 26 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 28 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | 32 | -------------------------------------------------------------------------------- 33 | 34 | This software contains Autodesk® FBX® code developed by Autodesk, Inc. Copyright 35 | 2017 Autodesk, Inc. All rights, reserved. Such code is provided “as is” and 36 | Autodesk, Inc. disclaims any and all warranties, whether express or implied, 37 | including without limitation the implied warranties of merchantability, fitness 38 | for a particular purpose or non-infringement of third party rights. In no event 39 | shall Autodesk, Inc. be liable for any direct, indirect, incidental, special, 40 | exemplary, or consequential damages (including, but not limited to, procurement 41 | of substitute goods or services; loss of use, data, or profits; or business 42 | interruption) however caused and on any theory of liability, whether in 43 | contract, strict liability, or tort (including negligence or otherwise) arising 44 | in any way out of such code. 45 | -------------------------------------------------------------------------------- /npm/fbx2gltf/README.md: -------------------------------------------------------------------------------- 1 | # FBX2glTF 2 | 3 | [![License](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause) 4 | 5 | 6 | This is a command line tool for converting 3D model assets on the 7 | well-established [FBX](https://www.autodesk.com/products/fbx/overview) format to 8 | [glTF 2.0](https://github.com/KhronosGroup/glTF/tree/master/specification/2.0), 9 | a modern runtime asset delivery format. 10 | 11 | # Platform Binaries 12 | 13 | This package contains three versions of `FBX2glTF`, compiled for three platforms 14 | and located in three eponymous directories: 15 | - bin/Darwin/FBX2glTF 16 | - bin/Linux/FBX2glTF 17 | - bin/Windows_NT/FBX2glTF.exe 18 | 19 | # Usage 20 | 21 | ```js 22 | /** 23 | * Converts an FBX to a GTLF or GLB file. 24 | * @param string srcFile path to the source file. 25 | * @param string destFile path to the destination file. 26 | * This must end in `.glb` or `.gltf` (case matters). 27 | * @param string[] [opts] options to pass to the converter tool. 28 | * @return Promise a promise that yields the full path to the converted 29 | * file, an error on conversion failure. 30 | */ 31 | convert(srcPath :string, destPath :string, args :?string[]) :Promise 32 | ``` 33 | 34 | For example: 35 | 36 | ```js 37 | const convert = require('fbx2gltf'); 38 | convert('path/to/some.fbx', 'path/to/target.glb', ['--khr-materials-unlit']).then( 39 | destPath => { 40 | // yay, do what we will with our shiny new GLB file! 41 | }, 42 | error => { 43 | // ack, conversion failed: inspect 'error' for details 44 | } 45 | ); 46 | ``` 47 | 48 | # Further Reading 49 | 50 | The home of this tool is [here](https://github.com/facebookincubator/FBX2glTF). 51 | 52 | # Authors 53 | - Pär Winzell 54 | - J.M.P. van Waveren 55 | - Amanda Watson 56 | 57 | # Legal 58 | 59 | FBX2glTF is licensed under the [3-clause BSD license](LICENSE). 60 | 61 | ``` 62 | This software contains Autodesk® FBX® code developed by Autodesk, Inc. Copyright 63 | 2017 Autodesk, Inc. All rights, reserved. Such code is provided “as is” and 64 | Autodesk, Inc. disclaims any and all warranties, whether express or implied, 65 | including without limitation the implied warranties of merchantability, fitness 66 | for a particular purpose or non-infringement of third party rights. In no event 67 | shall Autodesk, Inc. be liable for any direct, indirect, incidental, special, 68 | exemplary, or consequential damages (including, but not limited to, procurement 69 | of substitute goods or services; loss of use, data, or profits; or business 70 | interruption) however caused and on any theory of liability, whether in 71 | contract, strict liability, or tort (including negligence or otherwise) arising 72 | in any way out of such code. 73 | ``` 74 | -------------------------------------------------------------------------------- /npm/fbx2gltf/bin/Darwin/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facebookincubator/FBX2glTF/739ee5db94b0ca9eabe2b6ed92f32d13175072a3/npm/fbx2gltf/bin/Darwin/.keep -------------------------------------------------------------------------------- /npm/fbx2gltf/bin/Linux/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facebookincubator/FBX2glTF/739ee5db94b0ca9eabe2b6ed92f32d13175072a3/npm/fbx2gltf/bin/Linux/.keep -------------------------------------------------------------------------------- /npm/fbx2gltf/bin/README: -------------------------------------------------------------------------------- 1 | This directory must be populated with the following files prior to building the 2 | NPM package: 3 | 4 | Darwin/FBX2glTF 5 | Linux/FBX2glTF 6 | Windows/FBX2glTF.exe 7 | -------------------------------------------------------------------------------- /npm/fbx2gltf/bin/Windows_NT/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/facebookincubator/FBX2glTF/739ee5db94b0ca9eabe2b6ed92f32d13175072a3/npm/fbx2gltf/bin/Windows_NT/.keep -------------------------------------------------------------------------------- /npm/fbx2gltf/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014-present, Facebook, Inc. 3 | * All rights reserved. 4 | */ 5 | 6 | const childProcess = require('child_process'); 7 | const fs = require('fs'); 8 | const os = require('os'); 9 | const path = require('path'); 10 | const rimraf = require('rimraf'); 11 | 12 | const binaries = { 13 | 'darwin': `bin/darwin/Fbx2Gtlf`, 14 | 'linux': `bin/linux/Fbx2Gtlf`, 15 | 'win32': `bin\windows\Fbx2Gtlf.exe`, 16 | }; 17 | 18 | /** 19 | * Converts an FBX to a GTLF or GLB file. 20 | * @param string srcFile path to the source file. 21 | * @param string destFile path to the destination file or destination path. 22 | * This must end in `.glb` or `.gltf` (case matters). 23 | * @param string[] [opts] options to pass to the converter tool. 24 | * @return Promise a promise that yields the full path to the converted 25 | * file, an error on conversion failure. 26 | */ 27 | function convert(srcFile, destFile, opts = []) { 28 | return new Promise((resolve, reject) => { 29 | try { 30 | let binExt = os.type() === 'Windows_NT' ? '.exe' : ''; 31 | let tool = path.join(__dirname, 'bin', os.type(), 'FBX2glTF' + binExt); 32 | if (!fs.existsSync(tool)) { 33 | throw new Error(`Unsupported OS: ${os.type()}`); 34 | } 35 | 36 | let destExt = path.extname(destFile).toLowerCase(); 37 | 38 | if (!destExt) { 39 | destExt = '.gltf' 40 | 41 | const srcFilename = path.basename(srcFile, path.extname(srcFile)) 42 | destFile = path.join(destFile, srcFilename + destExt) 43 | } 44 | 45 | if (destExt !== '.glb' && destExt !== '.gltf') { 46 | throw new Error(`Unsupported file extension: ${destFile}`); 47 | } 48 | 49 | const binary = opts.includes('--binary') || opts.includes('-b'); 50 | 51 | if (binary && destExt !== '.glb') { 52 | destExt = '.glb'; 53 | } else if (!binary && destExt === 'glb') { 54 | opts.push('--binary'); 55 | } 56 | 57 | let srcPath = fs.realpathSync(srcFile); 58 | let destDir = fs.realpathSync(path.dirname(destFile)); 59 | let destFilename = path.basename(destFile, path.extname(destFile)) + destExt; 60 | let destPath = path.join(destDir, destFilename); 61 | 62 | let args = opts.slice(0); 63 | args.push('--input', srcPath, '--output', destPath); 64 | let child = childProcess.spawn(tool, args); 65 | 66 | let output = ''; 67 | child.stdout.on('data', (data) => output += data); 68 | child.stderr.on('data', (data) => output += data); 69 | child.on('error', reject); 70 | child.on('close', code => { 71 | // the FBX SDK may create an .fbm dir during conversion; delete! 72 | let fbmCruft = srcPath.replace(/.fbx$/i, '.fbm'); 73 | // don't stick a fork in things if this fails, just log a warning 74 | const onError = error => 75 | error && console.warn(`Failed to delete ${fbmCruft}: ${error}`); 76 | try { 77 | fs.existsSync(fbmCruft) && rimraf(fbmCruft, {}, onError); 78 | } catch (error) { 79 | onError(error); 80 | } 81 | 82 | // non-zero exit code is failure 83 | if (code != 0) { 84 | reject(new Error(`Converter output:\n` + 85 | (output.length ? output : ""))); 86 | } else { 87 | resolve(destPath); 88 | } 89 | }); 90 | 91 | } catch (error) { 92 | reject(error); 93 | } 94 | }); 95 | } 96 | 97 | module.exports = convert; 98 | -------------------------------------------------------------------------------- /npm/fbx2gltf/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fbx2gltf", 3 | "version": "0.9.7-p1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "balanced-match": { 8 | "version": "1.0.0", 9 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 10 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" 11 | }, 12 | "brace-expansion": { 13 | "version": "1.1.11", 14 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 15 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 16 | "requires": { 17 | "balanced-match": "^1.0.0", 18 | "concat-map": "0.0.1" 19 | } 20 | }, 21 | "concat-map": { 22 | "version": "0.0.1", 23 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 24 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 25 | }, 26 | "fs.realpath": { 27 | "version": "1.0.0", 28 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 29 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 30 | }, 31 | "glob": { 32 | "version": "7.1.4", 33 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", 34 | "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", 35 | "requires": { 36 | "fs.realpath": "^1.0.0", 37 | "inflight": "^1.0.4", 38 | "inherits": "2", 39 | "minimatch": "^3.0.4", 40 | "once": "^1.3.0", 41 | "path-is-absolute": "^1.0.0" 42 | } 43 | }, 44 | "inflight": { 45 | "version": "1.0.6", 46 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 47 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 48 | "requires": { 49 | "once": "^1.3.0", 50 | "wrappy": "1" 51 | } 52 | }, 53 | "inherits": { 54 | "version": "2.0.4", 55 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 56 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 57 | }, 58 | "minimatch": { 59 | "version": "3.0.4", 60 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 61 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 62 | "requires": { 63 | "brace-expansion": "^1.1.7" 64 | } 65 | }, 66 | "once": { 67 | "version": "1.4.0", 68 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 69 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 70 | "requires": { 71 | "wrappy": "1" 72 | } 73 | }, 74 | "path-is-absolute": { 75 | "version": "1.0.1", 76 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 77 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 78 | }, 79 | "rimraf": { 80 | "version": "2.7.1", 81 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", 82 | "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", 83 | "requires": { 84 | "glob": "^7.1.3" 85 | } 86 | }, 87 | "wrappy": { 88 | "version": "1.0.2", 89 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 90 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /npm/fbx2gltf/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fbx2gltf", 3 | "version": "0.9.7-p1", 4 | "description": "Node wrapper around FBX2glTF tools.", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/facebookincubator/FBX2glTF.git" 9 | }, 10 | "contributors": [ 11 | "Pär Winzell ", 12 | "J.M.P. van Waveren", 13 | "Amanda Watson" 14 | ], 15 | "license": "BSD-3-Clause", 16 | "bugs": { 17 | "url": "https://github.com/facebookincubator/FBX2glTF/issues" 18 | }, 19 | "homepage": "https://github.com/facebookincubator/FBX2glTF", 20 | "files": [ 21 | "LICENSE", 22 | "README.md", 23 | "bin", 24 | "index.js" 25 | ], 26 | "dependencies": { 27 | "rimraf": "^2.6.2" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /npm/fbx2gltf/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | balanced-match@^1.0.0: 6 | version "1.0.0" 7 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 8 | integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= 9 | 10 | brace-expansion@^1.1.7: 11 | version "1.1.11" 12 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 13 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 14 | dependencies: 15 | balanced-match "^1.0.0" 16 | concat-map "0.0.1" 17 | 18 | concat-map@0.0.1: 19 | version "0.0.1" 20 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 21 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 22 | 23 | fs.realpath@^1.0.0: 24 | version "1.0.0" 25 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 26 | integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= 27 | 28 | glob@^7.1.3: 29 | version "7.1.4" 30 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" 31 | integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A== 32 | dependencies: 33 | fs.realpath "^1.0.0" 34 | inflight "^1.0.4" 35 | inherits "2" 36 | minimatch "^3.0.4" 37 | once "^1.3.0" 38 | path-is-absolute "^1.0.0" 39 | 40 | inflight@^1.0.4: 41 | version "1.0.6" 42 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 43 | integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= 44 | dependencies: 45 | once "^1.3.0" 46 | wrappy "1" 47 | 48 | inherits@2: 49 | version "2.0.4" 50 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 51 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 52 | 53 | minimatch@^3.0.4: 54 | version "3.0.4" 55 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 56 | integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== 57 | dependencies: 58 | brace-expansion "^1.1.7" 59 | 60 | once@^1.3.0: 61 | version "1.4.0" 62 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 63 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= 64 | dependencies: 65 | wrappy "1" 66 | 67 | path-is-absolute@^1.0.0: 68 | version "1.0.1" 69 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 70 | integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= 71 | 72 | rimraf@^2.6.2: 73 | version "2.6.3" 74 | resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" 75 | integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== 76 | dependencies: 77 | glob "^7.1.3" 78 | 79 | wrappy@1: 80 | version "1.0.2" 81 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 82 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= 83 | -------------------------------------------------------------------------------- /src/FBX2glTF.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | #include "FBX2glTF.h" 18 | #include "fbx/Fbx2Raw.hpp" 19 | #include "gltf/Raw2Gltf.hpp" 20 | #include "utils/File_Utils.hpp" 21 | #include "utils/String_Utils.hpp" 22 | 23 | bool verboseOutput = false; 24 | 25 | int main(int argc, char* argv[]) { 26 | GltfOptions gltfOptions; 27 | 28 | CLI::App app{ 29 | fmt::sprintf( 30 | "FBX2glTF %s: Generate a glTF 2.0 representation of an FBX model.", FBX2GLTF_VERSION), 31 | "FBX2glTF"}; 32 | 33 | app.add_flag( 34 | "-v,--verbose", 35 | verboseOutput, 36 | "Include blend shape tangents, if reported present by the FBX SDK."); 37 | 38 | app.add_flag_function("-V,--version", [&](size_t count) { 39 | fmt::printf("FBX2glTF version %s\nCopyright (c) 2016-2018 Oculus VR, LLC.\n", FBX2GLTF_VERSION); 40 | exit(0); 41 | }); 42 | 43 | std::string inputPath; 44 | app.add_option("FBX Model", inputPath, "The FBX model to convert.")->check(CLI::ExistingFile); 45 | app.add_option("-i,--input", inputPath, "The FBX model to convert.")->check(CLI::ExistingFile); 46 | 47 | std::string outputPath; 48 | app.add_option("-o,--output", outputPath, "Where to generate the output, without suffix."); 49 | 50 | app.add_flag( 51 | "-e,--embed", 52 | gltfOptions.embedResources, 53 | "Inline buffers as data:// URIs within generated non-binary glTF."); 54 | app.add_flag("-b,--binary", gltfOptions.outputBinary, "Output a single binary format .glb file."); 55 | 56 | app.add_option( 57 | "--long-indices", 58 | [&](std::vector choices) -> bool { 59 | for (const std::string choice : choices) { 60 | if (choice == "never") { 61 | gltfOptions.useLongIndices = UseLongIndicesOptions::NEVER; 62 | } else if (choice == "auto") { 63 | gltfOptions.useLongIndices = UseLongIndicesOptions::AUTO; 64 | } else if (choice == "always") { 65 | gltfOptions.useLongIndices = UseLongIndicesOptions::ALWAYS; 66 | } else { 67 | fmt::printf("Unknown --long-indices: %s\n", choice); 68 | throw CLI::RuntimeError(1); 69 | } 70 | } 71 | return true; 72 | }, 73 | "Whether to use 32-bit indices.") 74 | ->type_name("(never|auto|always)"); 75 | 76 | app.add_option( 77 | "--compute-normals", 78 | [&](std::vector choices) -> bool { 79 | for (const std::string choice : choices) { 80 | if (choice == "never") { 81 | gltfOptions.computeNormals = ComputeNormalsOption::NEVER; 82 | } else if (choice == "broken") { 83 | gltfOptions.computeNormals = ComputeNormalsOption::BROKEN; 84 | } else if (choice == "missing") { 85 | gltfOptions.computeNormals = ComputeNormalsOption::MISSING; 86 | } else if (choice == "always") { 87 | gltfOptions.computeNormals = ComputeNormalsOption::ALWAYS; 88 | } else { 89 | fmt::printf("Unknown --compute-normals option: %s\n", choice); 90 | throw CLI::RuntimeError(1); 91 | } 92 | } 93 | return true; 94 | }, 95 | "When to compute vertex normals from mesh geometry.") 96 | ->type_name("(never|broken|missing|always)"); 97 | 98 | app.add_option( 99 | "--anim-framerate", 100 | [&](std::vector choices) -> bool { 101 | for (const std::string choice : choices) { 102 | if (choice == "bake24") { 103 | gltfOptions.animationFramerate = AnimationFramerateOptions::BAKE24; 104 | } else if (choice == "bake30") { 105 | gltfOptions.animationFramerate = AnimationFramerateOptions::BAKE30; 106 | } else if (choice == "bake60") { 107 | gltfOptions.animationFramerate = AnimationFramerateOptions::BAKE60; 108 | } else { 109 | fmt::printf("Unknown --anim-framerate: %s\n", choice); 110 | throw CLI::RuntimeError(1); 111 | } 112 | } 113 | return true; 114 | }, 115 | "Select baked animation framerate.") 116 | ->type_name("(bake24|bake30|bake60)"); 117 | 118 | const auto opt_flip_u = app.add_flag("--flip-u", "Flip all U texture coordinates."); 119 | const auto opt_no_flip_u = app.add_flag("--no-flip-u", "Don't flip U texture coordinates."); 120 | const auto opt_flip_v = app.add_flag("--flip-v", "Flip all V texture coordinates."); 121 | const auto opt_no_flip_v = app.add_flag("--no-flip-v", "Don't flip V texture coordinates."); 122 | 123 | app.add_flag( 124 | "--pbr-metallic-roughness", 125 | gltfOptions.usePBRMetRough, 126 | "Try to glean glTF 2.0 native PBR attributes from the FBX.") 127 | ->group("Materials"); 128 | 129 | app.add_flag( 130 | "--khr-materials-unlit", 131 | gltfOptions.useKHRMatUnlit, 132 | "Use KHR_materials_unlit extension to request an unlit shader.") 133 | ->group("Materials"); 134 | 135 | app.add_flag_function( 136 | "--no-khr-lights-punctual", 137 | [&](size_t count) { gltfOptions.useKHRLightsPunctual = (count == 0); }, 138 | "Don't use KHR_lights_punctual extension to export FBX lights."); 139 | 140 | app.add_flag( 141 | "--user-properties", 142 | gltfOptions.enableUserProperties, 143 | "Transcribe FBX User Properties into glTF node and material 'extras'."); 144 | 145 | app.add_flag( 146 | "--blend-shape-normals", 147 | gltfOptions.useBlendShapeNormals, 148 | "Include blend shape normals, if reported present by the FBX SDK."); 149 | 150 | app.add_flag( 151 | "--blend-shape-tangents", 152 | gltfOptions.useBlendShapeTangents, 153 | "Include blend shape tangents, if reported present by the FBX SDK."); 154 | 155 | app.add_option( 156 | "-k,--keep-attribute", 157 | [&](std::vector attributes) -> bool { 158 | gltfOptions.keepAttribs = 159 | RAW_VERTEX_ATTRIBUTE_JOINT_INDICES | RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS; 160 | for (std::string attribute : attributes) { 161 | if (attribute == "position") { 162 | gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_POSITION; 163 | } else if (attribute == "normal") { 164 | gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_NORMAL; 165 | } else if (attribute == "tangent") { 166 | gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_TANGENT; 167 | } else if (attribute == "binormal") { 168 | gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_BINORMAL; 169 | } else if (attribute == "color") { 170 | gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_COLOR; 171 | } else if (attribute == "uv0") { 172 | gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_UV0; 173 | } else if (attribute == "uv1") { 174 | gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_UV1; 175 | } else if (attribute == "auto") { 176 | gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_AUTO; 177 | } else { 178 | fmt::printf("Unknown --keep-attribute option: %s\n", attribute); 179 | throw CLI::RuntimeError(1); 180 | } 181 | } 182 | return true; 183 | }, 184 | "Used repeatedly to build a limiting set of vertex attributes to keep.") 185 | ->type_size(-1) 186 | ->type_name("(position|normal|tangent|binormial|color|uv0|uv1|auto)"); 187 | 188 | app.add_flag( 189 | "-d,--draco", gltfOptions.draco.enabled, "Apply Draco mesh compression to geometries.") 190 | ->group("Draco"); 191 | 192 | app.add_option( 193 | "--draco-compression-level", 194 | gltfOptions.draco.compressionLevel, 195 | "The compression level to tune Draco to.", 196 | true) 197 | ->check(CLI::Range(0, 10)) 198 | ->group("Draco"); 199 | 200 | app.add_option( 201 | "--draco-bits-for-position", 202 | gltfOptions.draco.quantBitsPosition, 203 | "How many bits to quantize position to.", 204 | true) 205 | ->check(CLI::Range(1, 32)) 206 | ->group("Draco"); 207 | 208 | app.add_option( 209 | "--draco-bits-for-uv", 210 | gltfOptions.draco.quantBitsTexCoord, 211 | "How many bits to quantize UV coordinates to.", 212 | true) 213 | ->check(CLI::Range(1, 32)) 214 | ->group("Draco"); 215 | 216 | app.add_option( 217 | "--draco-bits-for-normals", 218 | gltfOptions.draco.quantBitsNormal, 219 | "How many bits to quantize nornals to.", 220 | true) 221 | ->check(CLI::Range(1, 32)) 222 | ->group("Draco"); 223 | 224 | app.add_option( 225 | "--draco-bits-for-colors", 226 | gltfOptions.draco.quantBitsColor, 227 | "How many bits to quantize colors to.", 228 | true) 229 | ->check(CLI::Range(1, 32)) 230 | ->group("Draco"); 231 | 232 | app.add_option( 233 | "--draco-bits-for-other", 234 | gltfOptions.draco.quantBitsGeneric, 235 | "How many bits to quantize all other vertex attributes to.", 236 | true) 237 | ->check(CLI::Range(1, 32)) 238 | ->group("Draco"); 239 | 240 | app.add_option("--fbx-temp-dir", gltfOptions.fbxTempDir, "Temporary directory to be used by FBX SDK.")->check(CLI::ExistingDirectory); 241 | 242 | CLI11_PARSE(app, argc, argv); 243 | 244 | bool do_flip_u = false; 245 | bool do_flip_v = true; 246 | // somewhat tedious way to resolve --flag vs --no-flag in order provided 247 | for (const auto opt : app.parse_order()) { 248 | do_flip_u = (do_flip_u || (opt == opt_flip_u)) && (opt != opt_no_flip_u); 249 | do_flip_v = (do_flip_v || (opt == opt_flip_v)) && (opt != opt_no_flip_v); 250 | } 251 | std::vector> texturesTransforms; 252 | if (do_flip_u || do_flip_v) { 253 | if (do_flip_u && do_flip_v) { 254 | texturesTransforms.emplace_back([](Vec2f uv) { return Vec2f(1.0 - uv[0], 1.0 - uv[1]); }); 255 | } else if (do_flip_u) { 256 | texturesTransforms.emplace_back([](Vec2f uv) { return Vec2f(1.0 - uv[0], uv[1]); }); 257 | } else { 258 | texturesTransforms.emplace_back([](Vec2f uv) { return Vec2f(uv[0], 1.0 - uv[1]); }); 259 | } 260 | } 261 | if (verboseOutput) { 262 | if (do_flip_u) { 263 | fmt::printf("Flipping texture coordinates in the 'U' dimension.\n"); 264 | } 265 | if (!do_flip_v) { 266 | fmt::printf("NOT flipping texture coordinates in the 'V' dimension.\n"); 267 | } 268 | } 269 | 270 | if (inputPath.empty()) { 271 | fmt::printf("You must supply a FBX file to convert.\n"); 272 | exit(1); 273 | } 274 | 275 | if (!gltfOptions.useKHRMatUnlit && !gltfOptions.usePBRMetRough) { 276 | if (verboseOutput) { 277 | fmt::printf("Defaulting to --pbr-metallic-roughness material support.\n"); 278 | } 279 | gltfOptions.usePBRMetRough = true; 280 | } 281 | 282 | if (gltfOptions.embedResources && gltfOptions.outputBinary) { 283 | fmt::printf("Note: Ignoring --embed; it's meaningless with --binary.\n"); 284 | } 285 | 286 | if (outputPath.empty()) { 287 | // if -o is not given, default to the basename of the .fbx 288 | outputPath = "./" + FileUtils::GetFileBase(inputPath); 289 | } 290 | // the output folder in .gltf mode, not used for .glb 291 | std::string outputFolder; 292 | 293 | // the path of the actual .glb or .gltf file 294 | std::string modelPath; 295 | const auto& suffix = FileUtils::GetFileSuffix(outputPath); 296 | 297 | // Assume binary output if extension is glb 298 | if (suffix.has_value() && suffix.value() == "glb") { 299 | gltfOptions.outputBinary = true; 300 | } 301 | 302 | if (gltfOptions.outputBinary) { 303 | // add .glb to output path, unless it already ends in exactly that 304 | if (suffix.has_value() && suffix.value() == "glb") { 305 | modelPath = outputPath; 306 | } else { 307 | modelPath = outputPath + ".glb"; 308 | } 309 | // if the extension is gltf set the output folder to the parent directory 310 | } else if (suffix.has_value() && suffix.value() == "gltf") { 311 | outputFolder = FileUtils::getFolder(outputPath) + "/"; 312 | modelPath = outputPath; 313 | } else { 314 | // in gltf mode, we create a folder and write into that 315 | outputFolder = fmt::format("{}_out/", outputPath.c_str()); 316 | modelPath = outputFolder + FileUtils::GetFileName(outputPath) + ".gltf"; 317 | } 318 | if (!FileUtils::CreatePath(modelPath.c_str())) { 319 | fmt::fprintf(stderr, "ERROR: Failed to create folder: %s'\n", outputFolder.c_str()); 320 | return 1; 321 | } 322 | 323 | ModelData* data_render_model = nullptr; 324 | RawModel raw; 325 | 326 | if (verboseOutput) { 327 | fmt::printf("Loading FBX File: %s\n", inputPath); 328 | } 329 | if (!LoadFBXFile(raw, inputPath, {"png", "jpg", "jpeg"}, gltfOptions)) { 330 | fmt::fprintf(stderr, "ERROR:: Failed to parse FBX: %s\n", inputPath); 331 | return 1; 332 | } 333 | 334 | if (!texturesTransforms.empty()) { 335 | raw.TransformTextures(texturesTransforms); 336 | } 337 | raw.Condense(); 338 | raw.TransformGeometry(gltfOptions.computeNormals); 339 | 340 | std::ofstream outStream; // note: auto-flushes in destructor 341 | const auto streamStart = outStream.tellp(); 342 | 343 | outStream.open(modelPath, std::ios::trunc | std::ios::ate | std::ios::out | std::ios::binary); 344 | if (outStream.fail()) { 345 | fmt::fprintf(stderr, "ERROR:: Couldn't open file for writing: %s\n", modelPath.c_str()); 346 | return 1; 347 | } 348 | data_render_model = Raw2Gltf(outStream, outputFolder, raw, gltfOptions); 349 | 350 | if (gltfOptions.outputBinary) { 351 | fmt::printf( 352 | "Wrote %lu bytes of binary glTF to %s.\n", 353 | (unsigned long)(outStream.tellp() - streamStart), 354 | modelPath); 355 | delete data_render_model; 356 | return 0; 357 | } 358 | 359 | fmt::printf( 360 | "Wrote %lu bytes of glTF to %s.\n", 361 | (unsigned long)(outStream.tellp() - streamStart), 362 | modelPath); 363 | 364 | if (gltfOptions.embedResources) { 365 | // we're done: everything was inlined into the glTF JSON 366 | delete data_render_model; 367 | return 0; 368 | } 369 | 370 | assert(!outputFolder.empty()); 371 | 372 | const std::string binaryPath = outputFolder + extBufferFilename; 373 | FILE* fp = fopen(binaryPath.c_str(), "wb"); 374 | if (fp == nullptr) { 375 | fmt::fprintf(stderr, "ERROR:: Couldn't open file '%s' for writing.\n", binaryPath); 376 | return 1; 377 | } 378 | 379 | if (data_render_model->binary->empty() == false) { 380 | const unsigned char* binaryData = &(*data_render_model->binary)[0]; 381 | unsigned long binarySize = data_render_model->binary->size(); 382 | if (fwrite(binaryData, binarySize, 1, fp) != 1) { 383 | fmt::fprintf( 384 | stderr, "ERROR: Failed to write %lu bytes to file '%s'.\n", binarySize, binaryPath); 385 | fclose(fp); 386 | return 1; 387 | } 388 | fclose(fp); 389 | fmt::printf("Wrote %lu bytes of binary data to %s.\n", binarySize, binaryPath); 390 | } 391 | 392 | delete data_render_model; 393 | return 0; 394 | } 395 | -------------------------------------------------------------------------------- /src/FBX2glTF.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | 14 | #if defined(_WIN32) 15 | // Tell Windows not to define min() and max() macros 16 | #define NOMINMAX 17 | #include 18 | #endif 19 | 20 | #define FBX2GLTF_VERSION std::string("0.9.7") 21 | 22 | #include 23 | 24 | #include 25 | 26 | #if defined(_WIN32) 27 | // this is defined in fbxmath.h 28 | #undef isnan 29 | #undef snprintf 30 | #endif 31 | 32 | #include "mathfu.hpp" 33 | 34 | // give all modules access to our tweaked JSON 35 | #include 36 | #include 37 | 38 | template 39 | using workaround_fifo_map = nlohmann::fifo_map, A>; 40 | 41 | using json = nlohmann::basic_json; 42 | 43 | extern bool verboseOutput; 44 | 45 | /** 46 | * Centralises all the laborious downcasting from your OS' 64-bit 47 | * index variables down to the uint32s that glTF is built out of. 48 | */ 49 | inline uint32_t to_uint32(size_t n) { 50 | assert(n < UINT_MAX); 51 | return static_cast(n); 52 | } 53 | 54 | /** 55 | * The variuos situations in which the user may wish for us to (re-)compute normals for our 56 | * vertices. 57 | */ 58 | enum class ComputeNormalsOption { 59 | NEVER, // do not ever compute any normals (results in broken glTF for some sources) 60 | BROKEN, // replace zero-length normals in any mesh that has a normal layer 61 | MISSING, // if a mesh lacks normals, compute them all 62 | ALWAYS // compute a new normal for every vertex, obliterating whatever may have been there before 63 | }; 64 | 65 | enum class UseLongIndicesOptions { 66 | NEVER, // only ever use 16-bit indices 67 | AUTO, // use shorts or longs depending on vertex count 68 | ALWAYS, // only ever use 32-bit indices 69 | }; 70 | 71 | enum class AnimationFramerateOptions { 72 | BAKE24, // bake animations at 24 fps 73 | BAKE30, // bake animations at 30 fps 74 | BAKE60, // bake animations at 60 fps 75 | }; 76 | 77 | /** 78 | * User-supplied options that dictate the nature of the glTF being generated. 79 | */ 80 | struct GltfOptions { 81 | /** 82 | * If negative, disabled. Otherwise, a bitfield of RawVertexAttributes that 83 | * specify the largest set of attributes that'll ever be kept for a vertex. 84 | * The special bit RAW_VERTEX_ATTRIBUTE_AUTO triggers smart mode, where the 85 | * attributes to keep are inferred from which textures are supplied. 86 | */ 87 | int keepAttribs{-1}; 88 | /** Whether to output a .glb file, the binary format of glTF. */ 89 | bool outputBinary{false}; 90 | /** If non-binary, whether to inline all resources, for a single (large) .glTF file. */ 91 | bool embedResources{false}; 92 | 93 | /** Whether and how to use KHR_draco_mesh_compression to minimize static geometry size. */ 94 | struct { 95 | bool enabled = false; 96 | int compressionLevel = 7; 97 | int quantBitsPosition = 14; 98 | int quantBitsTexCoord = 10; 99 | int quantBitsNormal = 10; 100 | int quantBitsColor = 8; 101 | int quantBitsGeneric = 8; 102 | } draco; 103 | 104 | /** Whether to include FBX User Properties as 'extras' metadata in glTF nodes. */ 105 | bool enableUserProperties{false}; 106 | 107 | /** Whether to use KHR_materials_unlit to extend materials definitions. */ 108 | bool useKHRMatUnlit{false}; 109 | /** Whether to populate the pbrMetallicRoughness substruct in materials. */ 110 | bool usePBRMetRough{false}; 111 | 112 | /** Whether to include lights through the KHR_punctual_lights extension. */ 113 | bool useKHRLightsPunctual{true}; 114 | 115 | /** Whether to include blend shape normals, if present according to the SDK. */ 116 | bool useBlendShapeNormals{false}; 117 | /** Whether to include blend shape tangents, if present according to the SDK. */ 118 | bool useBlendShapeTangents{false}; 119 | /** When to compute vertex normals from geometry. */ 120 | ComputeNormalsOption computeNormals = ComputeNormalsOption::BROKEN; 121 | /** When to use 32-bit indices. */ 122 | UseLongIndicesOptions useLongIndices = UseLongIndicesOptions::AUTO; 123 | /** Select baked animation framerate. */ 124 | AnimationFramerateOptions animationFramerate = AnimationFramerateOptions::BAKE24; 125 | 126 | /** Temporary directory used by FBX SDK. */ 127 | std::string fbxTempDir; 128 | }; 129 | -------------------------------------------------------------------------------- /src/fbx/Fbx2Raw.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "raw/RawModel.hpp" 12 | 13 | bool LoadFBXFile( 14 | RawModel& raw, 15 | const std::string fbxFileName, 16 | const std::set& textureExtensions, 17 | const GltfOptions& options); 18 | 19 | json TranscribeProperty(FbxProperty& prop); -------------------------------------------------------------------------------- /src/fbx/FbxBlendShapesAccess.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #include "FbxBlendShapesAccess.hpp" 10 | 11 | FbxBlendShapesAccess::TargetShape::TargetShape(const FbxShape* shape, FbxDouble fullWeight) 12 | : shape(shape), 13 | fullWeight(fullWeight), 14 | count(shape->GetControlPointsCount()), 15 | positions(shape->GetControlPoints()), 16 | normals(FbxLayerElementAccess( 17 | shape->GetElementNormal(), 18 | shape->GetElementNormalCount())), 19 | tangents(FbxLayerElementAccess( 20 | shape->GetElementTangent(), 21 | shape->GetElementTangentCount())) {} 22 | 23 | FbxAnimCurve* FbxBlendShapesAccess::BlendChannel::ExtractAnimation(unsigned int animIx) const { 24 | FbxAnimStack* stack = mesh->GetScene()->GetSrcObject(animIx); 25 | FbxAnimLayer* layer = stack->GetMember(0); 26 | return mesh->GetShapeChannel(blendShapeIx, channelIx, layer, true); 27 | } 28 | 29 | FbxBlendShapesAccess::BlendChannel::BlendChannel( 30 | FbxMesh* mesh, 31 | const unsigned int blendShapeIx, 32 | const unsigned int channelIx, 33 | const FbxDouble deformPercent, 34 | const std::vector& targetShapes, 35 | std::string name) 36 | : mesh(mesh), 37 | blendShapeIx(blendShapeIx), 38 | channelIx(channelIx), 39 | deformPercent(deformPercent), 40 | targetShapes(targetShapes), 41 | name(name) {} 42 | 43 | std::vector FbxBlendShapesAccess::extractChannels( 44 | FbxMesh* mesh) const { 45 | std::vector channels; 46 | for (int shapeIx = 0; shapeIx < mesh->GetDeformerCount(FbxDeformer::eBlendShape); shapeIx++) { 47 | auto* fbxBlendShape = 48 | static_cast(mesh->GetDeformer(shapeIx, FbxDeformer::eBlendShape)); 49 | 50 | for (int channelIx = 0; channelIx < fbxBlendShape->GetBlendShapeChannelCount(); ++channelIx) { 51 | FbxBlendShapeChannel* fbxChannel = fbxBlendShape->GetBlendShapeChannel(channelIx); 52 | 53 | if (fbxChannel->GetTargetShapeCount() > 0) { 54 | std::vector targetShapes; 55 | const double* fullWeights = fbxChannel->GetTargetShapeFullWeights(); 56 | std::string name = std::string(fbxChannel->GetName()); 57 | 58 | if (verboseOutput) { 59 | fmt::printf("\rblendshape channel: %s\n", name); 60 | } 61 | 62 | for (int targetIx = 0; targetIx < fbxChannel->GetTargetShapeCount(); targetIx++) { 63 | FbxShape* fbxShape = fbxChannel->GetTargetShape(targetIx); 64 | targetShapes.emplace_back(fbxShape, fullWeights[targetIx]); 65 | } 66 | channels.emplace_back( 67 | mesh, shapeIx, channelIx, fbxChannel->DeformPercent * 0.01, targetShapes, name); 68 | } 69 | } 70 | } 71 | return channels; 72 | } 73 | -------------------------------------------------------------------------------- /src/fbx/FbxBlendShapesAccess.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include "FBX2glTF.h" 17 | #include "FbxLayerElementAccess.hpp" 18 | 19 | /** 20 | * At the FBX level, each Mesh can have a set of FbxBlendShape deformers; organisational units that 21 | * contain no data of their own. The actual deformation is determined by one or more 22 | * FbxBlendShapeChannels, whose influences are all additively applied to the mesh. In a simpler 23 | * world, each such channel would extend each base vertex with alternate position, and optionally 24 | * normal and tangent. 25 | * 26 | * It's not quite so simple, though. We also have progressive morphing, where one logical morph 27 | * actually consists of several concrete ones, each applied in sequence. For us, this means each 28 | * channel contains a sequence of FbxShapes (aka target shape); these are the actual data-holding 29 | * entities that provide the alternate vertex attributes. As such a channel is given more weight, it 30 | * moves from one target shape to another. 31 | * 32 | * The total number of alternate sets of attributes, then, is the total number of target shapes 33 | * across all the channels of all the blend shapes of the mesh. 34 | * 35 | * Each animation in the scene stack can yield one or zero FbxAnimCurves per channel (not target 36 | * shape). We evaluate these curves to get the weight of the channel: this weight is further 37 | * introspected on to figure out which target shapes we're currently interpolation between. 38 | */ 39 | class FbxBlendShapesAccess { 40 | public: 41 | /** 42 | * A target shape is on a 1:1 basis with the eventual glTF morph target, and is the object which 43 | * contains the actual morphed vertex data. 44 | */ 45 | struct TargetShape { 46 | explicit TargetShape(const FbxShape* shape, FbxDouble fullWeight); 47 | 48 | const FbxShape* shape; 49 | const FbxDouble fullWeight; 50 | const unsigned int count; 51 | const FbxVector4* positions; 52 | const FbxLayerElementAccess normals; 53 | const FbxLayerElementAccess tangents; 54 | }; 55 | 56 | /** 57 | * A channel collects a sequence (often of length 1) of target shapes. 58 | */ 59 | struct BlendChannel { 60 | BlendChannel( 61 | FbxMesh* mesh, 62 | const unsigned int blendShapeIx, 63 | const unsigned int channelIx, 64 | const FbxDouble deformPercent, 65 | const std::vector& targetShapes, 66 | const std::string name); 67 | 68 | FbxAnimCurve* ExtractAnimation(unsigned int animIx) const; 69 | 70 | FbxMesh* const mesh; 71 | 72 | const unsigned int blendShapeIx; 73 | const unsigned int channelIx; 74 | const std::vector targetShapes; 75 | const std::string name; 76 | 77 | const FbxDouble deformPercent; 78 | }; 79 | 80 | explicit FbxBlendShapesAccess(FbxMesh* mesh) : channels(extractChannels(mesh)) {} 81 | 82 | size_t GetChannelCount() const { 83 | return channels.size(); 84 | } 85 | const BlendChannel& GetBlendChannel(size_t channelIx) const { 86 | return channels.at(channelIx); 87 | } 88 | 89 | size_t GetTargetShapeCount(size_t channelIx) const { 90 | return channels[channelIx].targetShapes.size(); 91 | } 92 | const TargetShape& GetTargetShape(size_t channelIx, size_t targetShapeIx) const { 93 | return channels.at(channelIx).targetShapes[targetShapeIx]; 94 | } 95 | 96 | FbxAnimCurve* GetAnimation(size_t channelIx, size_t animIx) const { 97 | return channels.at(channelIx).ExtractAnimation(to_uint32(animIx)); 98 | } 99 | 100 | private: 101 | std::vector extractChannels(FbxMesh* mesh) const; 102 | 103 | const std::vector channels; 104 | }; 105 | -------------------------------------------------------------------------------- /src/fbx/FbxLayerElementAccess.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | #pragma once 9 | #include "FBX2glTF.h" 10 | 11 | template 12 | class FbxLayerElementAccess { 13 | public: 14 | FbxLayerElementAccess(const FbxLayerElementTemplate<_type_>* layer, int count); 15 | 16 | bool LayerPresent() const { 17 | return (mappingMode != FbxLayerElement::eNone); 18 | } 19 | 20 | _type_ GetElement( 21 | const int polygonIndex, 22 | const int polygonVertexIndex, 23 | const int controlPointIndex, 24 | const _type_ defaultValue) const; 25 | _type_ GetElement( 26 | const int polygonIndex, 27 | const int polygonVertexIndex, 28 | const int controlPointIndex, 29 | const _type_ defaultValue, 30 | const FbxMatrix& transform, 31 | const bool normalize) const; 32 | 33 | private: 34 | FbxLayerElement::EMappingMode mappingMode; 35 | const FbxLayerElementArrayTemplate<_type_>* elements; 36 | const FbxLayerElementArrayTemplate* indices; 37 | }; 38 | 39 | template 40 | FbxLayerElementAccess<_type_>::FbxLayerElementAccess( 41 | const FbxLayerElementTemplate<_type_>* layer, 42 | int count) 43 | : mappingMode(FbxLayerElement::eNone), elements(nullptr), indices(nullptr) { 44 | if (count <= 0 || layer == nullptr) { 45 | return; 46 | } 47 | const FbxLayerElement::EMappingMode newMappingMode = layer->GetMappingMode(); 48 | if (newMappingMode == FbxLayerElement::eByControlPoint || 49 | newMappingMode == FbxLayerElement::eByPolygonVertex || 50 | newMappingMode == FbxLayerElement::eByPolygon) { 51 | mappingMode = newMappingMode; 52 | elements = &layer->GetDirectArray(); 53 | indices = (layer->GetReferenceMode() == FbxLayerElement::eIndexToDirect || 54 | layer->GetReferenceMode() == FbxLayerElement::eIndex) 55 | ? &layer->GetIndexArray() 56 | : nullptr; 57 | } 58 | } 59 | 60 | template 61 | _type_ FbxLayerElementAccess<_type_>::GetElement( 62 | const int polygonIndex, 63 | const int polygonVertexIndex, 64 | const int controlPointIndex, 65 | const _type_ defaultValue) const { 66 | if (mappingMode != FbxLayerElement::eNone) { 67 | int index = (mappingMode == FbxLayerElement::eByControlPoint) 68 | ? controlPointIndex 69 | : ((mappingMode == FbxLayerElement::eByPolygonVertex) ? polygonVertexIndex : polygonIndex); 70 | index = (indices != nullptr) ? (*indices)[index] : index; 71 | _type_ element = elements->GetAt(index); 72 | return element; 73 | } 74 | return defaultValue; 75 | } 76 | 77 | template 78 | _type_ FbxLayerElementAccess<_type_>::GetElement( 79 | const int polygonIndex, 80 | const int polygonVertexIndex, 81 | const int controlPointIndex, 82 | const _type_ defaultValue, 83 | const FbxMatrix& transform, 84 | const bool normalize) const { 85 | if (mappingMode != FbxLayerElement::eNone) { 86 | _type_ element = transform.MultNormalize( 87 | GetElement(polygonIndex, polygonVertexIndex, controlPointIndex, defaultValue)); 88 | if (normalize) { 89 | element.Normalize(); 90 | } 91 | return element; 92 | } 93 | return defaultValue; 94 | } 95 | -------------------------------------------------------------------------------- /src/fbx/FbxSkinningAccess.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #include "FbxSkinningAccess.hpp" 10 | 11 | FbxSkinningAccess::FbxSkinningAccess(const FbxMesh* pMesh, FbxScene* pScene, FbxNode* pNode) 12 | : rootIndex(-1) { 13 | for (int deformerIndex = 0; deformerIndex < pMesh->GetDeformerCount(); deformerIndex++) { 14 | FbxSkin* skin = 15 | reinterpret_cast(pMesh->GetDeformer(deformerIndex, FbxDeformer::eSkin)); 16 | if (skin != nullptr) { 17 | const int clusterCount = skin->GetClusterCount(); 18 | if (clusterCount == 0) { 19 | continue; 20 | } 21 | int controlPointCount = pMesh->GetControlPointsCount(); 22 | vertexJointIndices.resize(controlPointCount, Vec4i(0, 0, 0, 0)); 23 | vertexJointWeights.resize(controlPointCount, Vec4f(0.0f, 0.0f, 0.0f, 0.0f)); 24 | 25 | for (int clusterIndex = 0; clusterIndex < clusterCount; clusterIndex++) { 26 | FbxCluster* cluster = skin->GetCluster(clusterIndex); 27 | const int indexCount = cluster->GetControlPointIndicesCount(); 28 | const int* clusterIndices = cluster->GetControlPointIndices(); 29 | const double* clusterWeights = cluster->GetControlPointWeights(); 30 | 31 | assert( 32 | cluster->GetLinkMode() == FbxCluster::eNormalize || 33 | cluster->GetLinkMode() == FbxCluster::eTotalOne); 34 | 35 | // Transform link matrix. 36 | FbxAMatrix transformLinkMatrix; 37 | cluster->GetTransformLinkMatrix(transformLinkMatrix); 38 | 39 | // The transformation of the mesh at binding time 40 | FbxAMatrix transformMatrix; 41 | cluster->GetTransformMatrix(transformMatrix); 42 | 43 | // Inverse bind matrix. 44 | FbxAMatrix globalBindposeInverseMatrix = transformLinkMatrix.Inverse() * transformMatrix; 45 | inverseBindMatrices.emplace_back(globalBindposeInverseMatrix); 46 | 47 | jointNodes.push_back(cluster->GetLink()); 48 | jointIds.push_back(cluster->GetLink()->GetUniqueID()); 49 | 50 | const FbxAMatrix globalNodeTransform = cluster->GetLink()->EvaluateGlobalTransform(); 51 | jointSkinningTransforms.push_back( 52 | FbxMatrix(globalNodeTransform * globalBindposeInverseMatrix)); 53 | jointInverseGlobalTransforms.push_back(FbxMatrix(globalNodeTransform.Inverse())); 54 | 55 | for (int i = 0; i < indexCount; i++) { 56 | if (clusterIndices[i] < 0 || clusterIndices[i] >= controlPointCount) { 57 | continue; 58 | } 59 | if (clusterWeights[i] <= vertexJointWeights[clusterIndices[i]][MAX_WEIGHTS - 1]) { 60 | continue; 61 | } 62 | vertexJointIndices[clusterIndices[i]][MAX_WEIGHTS - 1] = clusterIndex; 63 | vertexJointWeights[clusterIndices[i]][MAX_WEIGHTS - 1] = (float)clusterWeights[i]; 64 | for (int j = MAX_WEIGHTS - 1; j > 0; j--) { 65 | if (vertexJointWeights[clusterIndices[i]][j - 1] >= 66 | vertexJointWeights[clusterIndices[i]][j]) { 67 | break; 68 | } 69 | std::swap( 70 | vertexJointIndices[clusterIndices[i]][j - 1], 71 | vertexJointIndices[clusterIndices[i]][j]); 72 | std::swap( 73 | vertexJointWeights[clusterIndices[i]][j - 1], 74 | vertexJointWeights[clusterIndices[i]][j]); 75 | } 76 | } 77 | } 78 | for (int i = 0; i < controlPointCount; i++) { 79 | const float weightSumRcp = 1.0 / 80 | (vertexJointWeights[i][0] + vertexJointWeights[i][1] + vertexJointWeights[i][2] + 81 | vertexJointWeights[i][3]); 82 | vertexJointWeights[i] *= weightSumRcp; 83 | } 84 | } 85 | } 86 | 87 | rootIndex = -1; 88 | for (size_t i = 0; i < jointNodes.size() && rootIndex == -1; i++) { 89 | rootIndex = (int)i; 90 | FbxNode* parent = jointNodes[i]->GetParent(); 91 | if (parent == nullptr) { 92 | break; 93 | } 94 | for (size_t j = 0; j < jointNodes.size(); j++) { 95 | if (jointNodes[j] == parent) { 96 | rootIndex = -1; 97 | break; 98 | } 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/fbx/FbxSkinningAccess.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include "FBX2glTF.h" 20 | 21 | class FbxSkinningAccess { 22 | public: 23 | static const int MAX_WEIGHTS = 4; 24 | 25 | FbxSkinningAccess(const FbxMesh* pMesh, FbxScene* pScene, FbxNode* pNode); 26 | 27 | bool IsSkinned() const { 28 | return (vertexJointWeights.size() > 0); 29 | } 30 | 31 | int GetNodeCount() const { 32 | return (int)jointNodes.size(); 33 | } 34 | 35 | FbxNode* GetJointNode(const int jointIndex) const { 36 | return jointNodes[jointIndex]; 37 | } 38 | 39 | const uint64_t GetJointId(const int jointIndex) const { 40 | return jointIds[jointIndex]; 41 | } 42 | 43 | const FbxMatrix& GetJointSkinningTransform(const int jointIndex) const { 44 | return jointSkinningTransforms[jointIndex]; 45 | } 46 | 47 | const FbxMatrix& GetJointInverseGlobalTransforms(const int jointIndex) const { 48 | return jointInverseGlobalTransforms[jointIndex]; 49 | } 50 | 51 | const uint64_t GetRootNode() const { 52 | assert(rootIndex != -1); 53 | return jointIds[rootIndex]; 54 | } 55 | 56 | const FbxAMatrix& GetInverseBindMatrix(const int jointIndex) const { 57 | return inverseBindMatrices[jointIndex]; 58 | } 59 | 60 | const Vec4i GetVertexIndices(const int controlPointIndex) const { 61 | return (!vertexJointIndices.empty()) ? vertexJointIndices[controlPointIndex] 62 | : Vec4i(0, 0, 0, 0); 63 | } 64 | 65 | const Vec4f GetVertexWeights(const int controlPointIndex) const { 66 | return (!vertexJointWeights.empty()) ? vertexJointWeights[controlPointIndex] 67 | : Vec4f(0, 0, 0, 0); 68 | } 69 | 70 | private: 71 | int rootIndex; 72 | std::vector jointIds; 73 | std::vector jointNodes; 74 | std::vector jointSkinningTransforms; 75 | std::vector jointInverseGlobalTransforms; 76 | std::vector inverseBindMatrices; 77 | std::vector vertexJointIndices; 78 | std::vector vertexJointWeights; 79 | }; 80 | -------------------------------------------------------------------------------- /src/fbx/materials/3dsMaxPhysicalMaterial.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #include "RoughnessMetallicMaterials.hpp" 10 | 11 | std::unique_ptr Fbx3dsMaxPhysicalMaterialResolver::resolve() const { 12 | const FbxProperty topProp = fbxMaterial->FindProperty("3dsMax", false); 13 | if (topProp.GetPropertyDataType() != FbxCompoundDT) { 14 | return nullptr; 15 | } 16 | const FbxProperty props = topProp.Find("Parameters", false); 17 | if (!props.IsValid()) { 18 | return nullptr; 19 | } 20 | 21 | FbxString shadingModel = fbxMaterial->ShadingModel.Get(); 22 | if (!shadingModel.IsEmpty() && shadingModel != "unknown") { 23 | ::fmt::printf( 24 | "Warning: Material %s has surprising shading model: %s\n", 25 | fbxMaterial->GetName(), 26 | shadingModel); 27 | } 28 | 29 | auto getTex = [&](std::string propName) -> const FbxFileTexture* { 30 | const FbxFileTexture* ptr = nullptr; 31 | const FbxProperty texProp = props.Find((propName + "_map").c_str(), false); 32 | if (texProp.IsValid()) { 33 | const FbxProperty useProp = props.Find((propName + "_map_on").c_str(), false); 34 | if (useProp.IsValid() && !useProp.Get()) { 35 | // skip this texture if the _on property exists *and* is explicitly false 36 | return nullptr; 37 | } 38 | ptr = texProp.GetSrcObject(); 39 | if (ptr != nullptr && textureLocations.find(ptr) == textureLocations.end()) { 40 | ptr = nullptr; 41 | } 42 | } 43 | return ptr; 44 | }; 45 | 46 | FbxDouble baseWeight = getValue(props, "base_weight", 1.0); 47 | const auto* baseWeightMap = getTex("base_weight"); 48 | FbxDouble4 baseCol = getValue(props, "base_color", FbxDouble4(0.5, 0.5, 0.5, 1.0)); 49 | const auto* baseTex = getTex("base_color"); 50 | 51 | double emissiveWeight = getValue(props, "emission", 0.0); 52 | const auto* emissiveWeightMap = getTex("emission"); 53 | FbxDouble4 emissiveColor = getValue(props, "emit_color", FbxDouble4(1, 1, 1, 1)); 54 | const auto* emissiveColorMap = getTex("emit_color"); 55 | 56 | double roughness = getValue(props, "roughness", 0.0); 57 | const auto* roughnessMap = getTex("roughness"); 58 | double metalness = getValue(props, "metalness", 0.0); 59 | const auto* metalnessMap = getTex("metalness"); 60 | 61 | // TODO: we need this to affect roughness map, too. 62 | bool invertRoughness = getValue(props, "inv_roughness", false); 63 | if (invertRoughness) { 64 | roughness = 1.0f - roughness; 65 | } 66 | 67 | std::string unsupported; 68 | const auto addUnsupported = [&](const std::string bit) { 69 | if (!unsupported.empty()) { 70 | unsupported += ", "; 71 | } 72 | unsupported += bit; 73 | }; 74 | 75 | // TODO: turn this into a normal map through simple numerial differentiation 76 | const auto* bumpMap = getTex("bump"); 77 | if (bumpMap != nullptr) { 78 | addUnsupported("bump map"); 79 | } 80 | 81 | // TODO: bake transparency > 0.0f into the alpha of baseColor? 82 | double transparency = getValue(props, "transparency", 0.0); 83 | const auto* transparencyMap = getTex("transparency"); 84 | if (transparency != 0.0 || transparencyMap != nullptr) { 85 | addUnsupported("transparency"); 86 | } 87 | 88 | // TODO: if/when we bake transparency, we'll need this 89 | // double transparencyDepth = getValue(props, "trans_depth", 0.0); 90 | // if (transparencyDepth != 0.0) { 91 | // addUnsupported("transparency depth"); 92 | // } 93 | // double transparencyColor = getValue(props, "trans_color", 0.0); 94 | // const auto* transparencyColorMap = getTex("trans_color"); 95 | // if (transparencyColor != 0.0 || transparencyColorMap != nullptr) { 96 | // addUnsupported("transparency color"); 97 | // } 98 | // double thinWalledTransparency = getValue(props, "thin_walled", false); 99 | // if (thinWalledTransparency) { 100 | // addUnsupported("thin-walled transparency"); 101 | // } 102 | 103 | const auto* displacementMap = getTex("displacement"); 104 | if (displacementMap != nullptr) { 105 | addUnsupported("displacement"); 106 | } 107 | 108 | double reflectivityWeight = getValue(props, "reflectivity", 1.0); 109 | const auto* reflectivityWeightMap = getTex("reflectivity"); 110 | FbxDouble4 reflectivityColor = getValue(props, "refl_color", FbxDouble4(1, 1, 1, 1)); 111 | const auto* reflectivityColorMap = getTex("refl_color"); 112 | if (reflectivityWeight != 1.0 || reflectivityWeightMap != nullptr || 113 | reflectivityColor != FbxDouble4(1, 1, 1, 1) || reflectivityColorMap != nullptr) { 114 | addUnsupported("reflectivity"); 115 | } 116 | 117 | double scattering = getValue(props, "scattering", 0.0); 118 | const auto* scatteringMap = getTex("scattering"); 119 | if (scattering != 0.0 || scatteringMap != nullptr) { 120 | addUnsupported("sub-surface scattering"); 121 | } 122 | 123 | double coating = getValue(props, "coating", 0.0); 124 | if (coating != 0.0) { 125 | addUnsupported("coating"); 126 | } 127 | 128 | double diffuseRoughness = getValue(props, "diff_roughness", 0.0); 129 | if (diffuseRoughness != 0.0) { 130 | addUnsupported("diffuse roughness"); 131 | } 132 | 133 | bool isBrdfMode = getValue(props, "brdf_mode", false); 134 | if (isBrdfMode) { 135 | addUnsupported("advanced reflectance custom curve"); 136 | } 137 | 138 | double anisotropy = getValue(props, "anisotropy", 1.0); 139 | if (anisotropy != 1.0) { 140 | addUnsupported("anisotropy"); 141 | } 142 | 143 | if (verboseOutput && !unsupported.empty()) { 144 | fmt::printf( 145 | "Warning: 3dsMax Physical Material %s uses features glTF cannot express:\n %s\n", 146 | fbxMaterial->GetName(), 147 | unsupported); 148 | } 149 | 150 | std::unique_ptr res(new FbxRoughMetMaterialInfo( 151 | fbxMaterial->GetUniqueID(), 152 | fbxMaterial->GetName(), 153 | FbxRoughMetMaterialInfo::FBX_SHADER_METROUGH, 154 | baseCol, 155 | metalness, 156 | roughness)); 157 | res->texBaseColor = baseTex; 158 | res->baseWeight = baseWeight; 159 | res->texBaseWeight = baseWeightMap; 160 | 161 | res->texMetallic = metalnessMap; 162 | res->texRoughness = roughnessMap; 163 | res->invertRoughnessMap = invertRoughness; 164 | 165 | res->emissive = emissiveColor; 166 | res->emissiveIntensity = emissiveWeight; 167 | res->texEmissive = emissiveColorMap; 168 | res->texEmissiveWeight = emissiveWeightMap; 169 | 170 | return res; 171 | } 172 | -------------------------------------------------------------------------------- /src/fbx/materials/FbxMaterials.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #include "fbx/Fbx2Raw.hpp" 10 | 11 | #include "FbxMaterials.hpp" 12 | #include "RoughnessMetallicMaterials.hpp" 13 | #include "TraditionalMaterials.hpp" 14 | 15 | static int warnMtrCount = 0; 16 | 17 | FbxMaterialsAccess::FbxMaterialsAccess( 18 | const FbxMesh* pMesh, 19 | const std::map& textureLocations) 20 | : mappingMode(FbxGeometryElement::eNone), mesh(nullptr), indices(nullptr) { 21 | if (pMesh->GetElementMaterialCount() <= 0) { 22 | return; 23 | } 24 | 25 | const FbxGeometryElement::EMappingMode materialMappingMode = 26 | pMesh->GetElementMaterial()->GetMappingMode(); 27 | if (materialMappingMode != FbxGeometryElement::eByPolygon && 28 | materialMappingMode != FbxGeometryElement::eAllSame) { 29 | return; 30 | } 31 | 32 | const FbxGeometryElement::EReferenceMode materialReferenceMode = 33 | pMesh->GetElementMaterial()->GetReferenceMode(); 34 | if (materialReferenceMode != FbxGeometryElement::eIndexToDirect) { 35 | return; 36 | } 37 | 38 | mappingMode = materialMappingMode; 39 | mesh = pMesh; 40 | indices = &pMesh->GetElementMaterial()->GetIndexArray(); 41 | 42 | for (int ii = 0; ii < indices->GetCount(); ii++) { 43 | int materialNum = indices->GetAt(ii); 44 | if (materialNum < 0) { 45 | continue; 46 | } 47 | 48 | auto* surfaceMaterial = 49 | mesh->GetNode()->GetSrcObject(materialNum); 50 | 51 | if (!surfaceMaterial) { 52 | if (++warnMtrCount == 1) { 53 | fmt::printf("Warning: Reference to missing surface material.\n"); 54 | fmt::printf(" (Further warnings of this type squelched.)\n"); 55 | } 56 | } 57 | 58 | if (materialNum >= summaries.size()) { 59 | summaries.resize(materialNum + 1); 60 | } 61 | auto summary = summaries[materialNum]; 62 | if (summary == nullptr) { 63 | summary = summaries[materialNum] = GetMaterialInfo(surfaceMaterial, textureLocations); 64 | } 65 | 66 | if (materialNum >= userProperties.size()) { 67 | userProperties.resize(materialNum + 1); 68 | } 69 | if (surfaceMaterial && userProperties[materialNum].empty()) { 70 | 71 | FbxProperty objectProperty = surfaceMaterial->GetFirstProperty(); 72 | while (objectProperty.IsValid()) { 73 | if (objectProperty.GetFlag(FbxPropertyFlags::eUserDefined)) { 74 | userProperties[materialNum].push_back(TranscribeProperty(objectProperty).dump()); 75 | } 76 | objectProperty = surfaceMaterial->GetNextProperty(objectProperty); 77 | } 78 | } 79 | } 80 | } 81 | 82 | const std::shared_ptr FbxMaterialsAccess::GetMaterial( 83 | const int polygonIndex) const { 84 | if (mappingMode != FbxGeometryElement::eNone) { 85 | const int materialNum = 86 | indices->GetAt((mappingMode == FbxGeometryElement::eByPolygon) ? polygonIndex : 0); 87 | if (materialNum < 0) { 88 | return nullptr; 89 | } 90 | return summaries.at((unsigned long)materialNum); 91 | } 92 | return nullptr; 93 | } 94 | 95 | const std::vector FbxMaterialsAccess::GetUserProperties(const int polygonIndex) const { 96 | if (mappingMode != FbxGeometryElement::eNone) { 97 | const int materialNum = 98 | indices->GetAt((mappingMode == FbxGeometryElement::eByPolygon) ? polygonIndex : 0); 99 | if (materialNum < 0) { 100 | return std::vector(); 101 | } 102 | return userProperties.at((unsigned long)materialNum); 103 | } 104 | return std::vector(); 105 | } 106 | 107 | std::unique_ptr FbxMaterialsAccess::GetMaterialInfo( 108 | FbxSurfaceMaterial* material, 109 | const std::map& textureLocations) { 110 | if (!material) { 111 | return nullptr; 112 | } 113 | std::unique_ptr res = 114 | FbxStingrayPBSMaterialResolver(material, textureLocations).resolve(); 115 | if (res == nullptr) { 116 | res = Fbx3dsMaxPhysicalMaterialResolver(material, textureLocations).resolve(); 117 | if (res == nullptr) { 118 | res = FbxTraditionalMaterialResolver(material, textureLocations).resolve(); 119 | } 120 | } 121 | return res; 122 | } 123 | -------------------------------------------------------------------------------- /src/fbx/materials/FbxMaterials.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | 14 | #include "FBX2glTF.h" 15 | 16 | class FbxMaterialInfo { 17 | public: 18 | FbxMaterialInfo(const FbxUInt64 id, const FbxString& name, const FbxString& shadingModel) 19 | : id(id), name(name), shadingModel(shadingModel) {} 20 | 21 | const FbxUInt64 id; 22 | const FbxString name; 23 | const FbxString shadingModel; 24 | }; 25 | 26 | template 27 | class FbxMaterialResolver { 28 | public: 29 | FbxMaterialResolver( 30 | FbxSurfaceMaterial* fbxMaterial, 31 | const std::map& textureLocations) 32 | : fbxMaterial(fbxMaterial), textureLocations(textureLocations) {} 33 | virtual std::unique_ptr resolve() const = 0; 34 | 35 | protected: 36 | const FbxSurfaceMaterial* fbxMaterial; 37 | const std::map textureLocations; 38 | }; 39 | 40 | class FbxMaterialsAccess { 41 | public: 42 | FbxMaterialsAccess( 43 | const FbxMesh* pMesh, 44 | const std::map& textureLocations); 45 | 46 | const std::shared_ptr GetMaterial(const int polygonIndex) const; 47 | 48 | const std::vector GetUserProperties(const int polygonIndex) const; 49 | 50 | std::unique_ptr GetMaterialInfo( 51 | FbxSurfaceMaterial* material, 52 | const std::map& textureLocations); 53 | 54 | private: 55 | FbxGeometryElement::EMappingMode mappingMode; 56 | std::vector> summaries{}; 57 | std::vector> userProperties; 58 | const FbxMesh* mesh; 59 | const FbxLayerElementArrayTemplate* indices; 60 | }; 61 | -------------------------------------------------------------------------------- /src/fbx/materials/RoughnessMetallicMaterials.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | 13 | #include "FbxMaterials.hpp" 14 | 15 | struct FbxRoughMetMaterialInfo : FbxMaterialInfo { 16 | static constexpr const char* FBX_SHADER_METROUGH = "MetallicRoughness"; 17 | 18 | static std::unique_ptr From( 19 | FbxSurfaceMaterial* fbxMaterial, 20 | const std::map& textureLocations); 21 | 22 | FbxRoughMetMaterialInfo( 23 | const FbxUInt64 id, 24 | const FbxString& name, 25 | const FbxString& shadingModel, 26 | FbxDouble4 baseColor, 27 | FbxDouble metallic, 28 | FbxDouble roughness) 29 | : FbxMaterialInfo(id, name, shadingModel), 30 | baseColor(baseColor), 31 | metallic(metallic), 32 | roughness(roughness) {} 33 | 34 | const FbxVector4 baseColor; 35 | const FbxDouble metallic; 36 | const FbxDouble roughness; 37 | 38 | FbxBool invertRoughnessMap = false; 39 | FbxDouble baseWeight = 1; 40 | FbxVector4 emissive = FbxVector4(0, 0, 0, 1); 41 | FbxDouble emissiveIntensity = 1; 42 | 43 | const FbxFileTexture* texNormal = nullptr; 44 | const FbxFileTexture* texBaseColor = nullptr; 45 | const FbxFileTexture* texBaseWeight = nullptr; 46 | const FbxFileTexture* texMetallic = nullptr; 47 | const FbxFileTexture* texRoughness = nullptr; 48 | const FbxFileTexture* texEmissive = nullptr; 49 | const FbxFileTexture* texEmissiveWeight = nullptr; 50 | const FbxFileTexture* texAmbientOcclusion = nullptr; 51 | }; 52 | 53 | class FbxStingrayPBSMaterialResolver : FbxMaterialResolver { 54 | public: 55 | FbxStingrayPBSMaterialResolver( 56 | FbxSurfaceMaterial* fbxMaterial, 57 | const std::map& textureLocations) 58 | : FbxMaterialResolver(fbxMaterial, textureLocations) {} 59 | 60 | virtual std::unique_ptr resolve() const; 61 | }; 62 | 63 | class Fbx3dsMaxPhysicalMaterialResolver : FbxMaterialResolver { 64 | public: 65 | Fbx3dsMaxPhysicalMaterialResolver( 66 | FbxSurfaceMaterial* fbxMaterial, 67 | const std::map& textureLocations) 68 | : FbxMaterialResolver(fbxMaterial, textureLocations) {} 69 | 70 | virtual std::unique_ptr resolve() const; 71 | 72 | private: 73 | template 74 | T getValue(const FbxProperty& props, std::string propName, const T& def) const { 75 | const FbxProperty prop = props.FindHierarchical(propName.c_str()); 76 | return prop.IsValid() ? prop.Get() : def; 77 | } 78 | }; 79 | -------------------------------------------------------------------------------- /src/fbx/materials/StingrayPBSMaterial.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #include "RoughnessMetallicMaterials.hpp" 10 | 11 | std::unique_ptr FbxStingrayPBSMaterialResolver::resolve() const { 12 | const FbxProperty mayaProp = fbxMaterial->FindProperty("Maya"); 13 | if (mayaProp.GetPropertyDataType() != FbxCompoundDT) { 14 | return nullptr; 15 | } 16 | if (!fbxMaterial->ShadingModel.Get().IsEmpty()) { 17 | ::fmt::printf( 18 | "Warning: Material %s has surprising shading model: %s\n", 19 | fbxMaterial->GetName(), 20 | fbxMaterial->ShadingModel.Get()); 21 | } 22 | 23 | auto getTex = [&](std::string propName) { 24 | const FbxFileTexture* ptr = nullptr; 25 | 26 | const FbxProperty useProp = mayaProp.FindHierarchical(("use_" + propName + "_map").c_str()); 27 | if (useProp.IsValid() && useProp.Get()) { 28 | const FbxProperty texProp = mayaProp.FindHierarchical(("TEX_" + propName + "_map").c_str()); 29 | if (texProp.IsValid()) { 30 | ptr = texProp.GetSrcObject(); 31 | if (ptr != nullptr && textureLocations.find(ptr) == textureLocations.end()) { 32 | ptr = nullptr; 33 | } 34 | } 35 | } 36 | return ptr; 37 | }; 38 | 39 | auto getVec = [&](std::string propName) -> FbxDouble3 { 40 | const FbxProperty vecProp = mayaProp.FindHierarchical(propName.c_str()); 41 | return vecProp.IsValid() ? vecProp.Get() : FbxDouble3(1, 1, 1); 42 | }; 43 | 44 | auto getVal = [&](std::string propName) -> FbxDouble { 45 | const FbxProperty vecProp = mayaProp.FindHierarchical(propName.c_str()); 46 | return vecProp.IsValid() ? vecProp.Get() : 0; 47 | }; 48 | 49 | FbxDouble3 baseColor = getVec("base_color"); 50 | std::unique_ptr res(new FbxRoughMetMaterialInfo( 51 | fbxMaterial->GetUniqueID(), 52 | fbxMaterial->GetName(), 53 | FbxRoughMetMaterialInfo::FBX_SHADER_METROUGH, 54 | FbxDouble4(baseColor[0], baseColor[1], baseColor[2], 1), 55 | getVal("metallic"), 56 | getVal("roughness"))); 57 | res->texNormal = getTex("normal"); 58 | res->texBaseColor = getTex("color"); 59 | res->texAmbientOcclusion = getTex("ao"); 60 | res->texEmissive = getTex("emissive"); 61 | res->emissive = getVec("emissive"); 62 | res->emissiveIntensity = getVal("emissive_intensity"); 63 | res->texMetallic = getTex("metallic"); 64 | res->texRoughness = getTex("roughness"); 65 | 66 | return res; 67 | }; 68 | -------------------------------------------------------------------------------- /src/fbx/materials/TraditionalMaterials.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #include "TraditionalMaterials.hpp" 10 | 11 | std::unique_ptr FbxTraditionalMaterialResolver::resolve() const { 12 | auto getSurfaceScalar = [&](const char* propName) -> std::tuple { 13 | const FbxProperty prop = fbxMaterial->FindProperty(propName); 14 | 15 | FbxDouble val(0); 16 | FbxFileTexture* tex = prop.GetSrcObject(); 17 | if (tex != nullptr && textureLocations.find(tex) == textureLocations.end()) { 18 | tex = nullptr; 19 | } 20 | if (tex == nullptr && prop.IsValid()) { 21 | val = prop.Get(); 22 | } 23 | return std::make_tuple(val, tex); 24 | }; 25 | 26 | auto getSurfaceVector = [&](const char* propName) -> std::tuple { 27 | const FbxProperty prop = fbxMaterial->FindProperty(propName); 28 | 29 | FbxDouble3 val(1, 1, 1); 30 | FbxFileTexture* tex = prop.GetSrcObject(); 31 | if (tex != nullptr && textureLocations.find(tex) == textureLocations.end()) { 32 | tex = nullptr; 33 | } 34 | if (tex == nullptr && prop.IsValid()) { 35 | val = prop.Get(); 36 | } 37 | return std::make_tuple(val, tex); 38 | }; 39 | 40 | auto getSurfaceValues = 41 | [&](const char* colName, 42 | const char* facName) -> std::tuple { 43 | const FbxProperty colProp = fbxMaterial->FindProperty(colName); 44 | const FbxProperty facProp = fbxMaterial->FindProperty(facName); 45 | 46 | FbxDouble3 colorVal(1, 1, 1); 47 | FbxDouble factorVal(1); 48 | 49 | FbxFileTexture* colTex = colProp.GetSrcObject(); 50 | if (colTex != nullptr && textureLocations.find(colTex) == textureLocations.end()) { 51 | colTex = nullptr; 52 | } 53 | if (colTex == nullptr && colProp.IsValid()) { 54 | colorVal = colProp.Get(); 55 | } 56 | FbxFileTexture* facTex = facProp.GetSrcObject(); 57 | if (facTex != nullptr && textureLocations.find(facTex) == textureLocations.end()) { 58 | facTex = nullptr; 59 | } 60 | if (facTex == nullptr && facProp.IsValid()) { 61 | factorVal = facProp.Get(); 62 | } 63 | 64 | auto val = FbxVector4( 65 | colorVal[0] * factorVal, colorVal[1] * factorVal, colorVal[2] * factorVal, factorVal); 66 | return std::make_tuple(val, colTex, facTex); 67 | }; 68 | 69 | std::string name = fbxMaterial->GetName(); 70 | std::unique_ptr res(new FbxTraditionalMaterialInfo( 71 | fbxMaterial->GetUniqueID(), name.c_str(), fbxMaterial->ShadingModel.Get())); 72 | 73 | // four properties are on the same structure and follow the same rules 74 | auto handleBasicProperty = [&](const char* colName, 75 | const char* facName) -> std::tuple { 76 | FbxFileTexture *colTex, *facTex; 77 | FbxVector4 vec; 78 | 79 | std::tie(vec, colTex, facTex) = getSurfaceValues(colName, facName); 80 | if (colTex) { 81 | if (facTex) { 82 | fmt::printf( 83 | "Warning: Mat [%s]: Can't handle both %s and %s textures; discarding %s.\n", 84 | name, 85 | colName, 86 | facName, 87 | facName); 88 | } 89 | return std::make_tuple(vec, colTex); 90 | } 91 | return std::make_tuple(vec, facTex); 92 | }; 93 | 94 | std::tie(res->colAmbient, res->texAmbient) = 95 | handleBasicProperty(FbxSurfaceMaterial::sAmbient, FbxSurfaceMaterial::sAmbientFactor); 96 | std::tie(res->colSpecular, res->texSpecular) = 97 | handleBasicProperty(FbxSurfaceMaterial::sSpecular, FbxSurfaceMaterial::sSpecularFactor); 98 | std::tie(res->colDiffuse, res->texDiffuse) = 99 | handleBasicProperty(FbxSurfaceMaterial::sDiffuse, FbxSurfaceMaterial::sDiffuseFactor); 100 | std::tie(res->colEmissive, res->texEmissive) = 101 | handleBasicProperty(FbxSurfaceMaterial::sEmissive, FbxSurfaceMaterial::sEmissiveFactor); 102 | 103 | // the normal map can only ever be a map, ignore everything else 104 | tie(std::ignore, res->texNormal) = getSurfaceVector(FbxSurfaceMaterial::sNormalMap); 105 | 106 | // shininess can be a map or a factor; afaict the map is always 'ShininessExponent' and the 107 | // value is always found in 'Shininess' but only sometimes in 'ShininessExponent'. 108 | tie(std::ignore, res->texShininess) = getSurfaceScalar("ShininessExponent"); 109 | tie(res->shininess, std::ignore) = getSurfaceScalar("Shininess"); 110 | 111 | // for transparency we just want a constant vector value; 112 | FbxVector4 transparency; 113 | // extract any existing textures only so we can warn that we're throwing them away 114 | FbxFileTexture *colTex, *facTex; 115 | std::tie(transparency, colTex, facTex) = getSurfaceValues( 116 | FbxSurfaceMaterial::sTransparentColor, FbxSurfaceMaterial::sTransparencyFactor); 117 | if (colTex) { 118 | fmt::printf( 119 | "Warning: Mat [%s]: Can't handle texture for %s; discarding.\n", 120 | name, 121 | FbxSurfaceMaterial::sTransparentColor); 122 | } 123 | if (facTex) { 124 | fmt::printf( 125 | "Warning: Mat [%s]: Can't handle texture for %s; discarding.\n", 126 | name, 127 | FbxSurfaceMaterial::sTransparencyFactor); 128 | } 129 | // FBX color is RGB, so we calculate the A channel as the average of the FBX transparency color 130 | // vector 131 | res->colDiffuse[3] = 1.0 - (transparency[0] + transparency[1] + transparency[2]) / 3.0; 132 | 133 | return res; 134 | } 135 | -------------------------------------------------------------------------------- /src/fbx/materials/TraditionalMaterials.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #include "FbxMaterials.hpp" 10 | 11 | struct FbxTraditionalMaterialInfo : FbxMaterialInfo { 12 | static constexpr const char* FBX_SHADER_LAMBERT = "Lambert"; 13 | static constexpr const char* FBX_SHADER_BLINN = "Blinn"; 14 | static constexpr const char* FBX_SHADER_PHONG = "Phong"; 15 | 16 | FbxTraditionalMaterialInfo( 17 | const FbxUInt64 id, 18 | const FbxString& name, 19 | const FbxString& shadingModel) 20 | : FbxMaterialInfo(id, name, shadingModel) {} 21 | 22 | FbxFileTexture* texAmbient{}; 23 | FbxVector4 colAmbient{}; 24 | FbxFileTexture* texSpecular{}; 25 | FbxVector4 colSpecular{}; 26 | FbxFileTexture* texDiffuse{}; 27 | FbxVector4 colDiffuse{}; 28 | FbxFileTexture* texEmissive{}; 29 | FbxVector4 colEmissive{}; 30 | FbxFileTexture* texNormal{}; 31 | FbxFileTexture* texShininess{}; 32 | FbxDouble shininess{}; 33 | }; 34 | 35 | class FbxTraditionalMaterialResolver : FbxMaterialResolver { 36 | public: 37 | FbxTraditionalMaterialResolver( 38 | FbxSurfaceMaterial* fbxMaterial, 39 | const std::map& textureLocations) 40 | : FbxMaterialResolver(fbxMaterial, textureLocations) {} 41 | 42 | virtual std::unique_ptr resolve() const; 43 | }; 44 | -------------------------------------------------------------------------------- /src/gltf/GltfModel.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #include "GltfModel.hpp" 10 | 11 | std::shared_ptr GltfModel::GetAlignedBufferView( 12 | BufferData& buffer, 13 | const BufferViewData::GL_ArrayType target) { 14 | uint32_t bufferSize = to_uint32(this->binary->size()); 15 | if ((bufferSize % 4) > 0) { 16 | bufferSize += (4 - (bufferSize % 4)); 17 | this->binary->resize(bufferSize); 18 | } 19 | return this->bufferViews.hold(new BufferViewData(buffer, bufferSize, target)); 20 | } 21 | 22 | // add a bufferview on the fly and copy data into it 23 | std::shared_ptr 24 | GltfModel::AddRawBufferView(BufferData& buffer, const char* source, uint32_t bytes) { 25 | auto bufferView = GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_NONE); 26 | bufferView->byteLength = bytes; 27 | 28 | // make space for the new bytes (possibly moving the underlying data) 29 | uint32_t bufferSize = to_uint32(this->binary->size()); 30 | this->binary->resize(bufferSize + bytes); 31 | 32 | // and copy them into place 33 | memcpy(&(*this->binary)[bufferSize], source, bytes); 34 | return bufferView; 35 | } 36 | 37 | std::shared_ptr GltfModel::AddBufferViewForFile( 38 | BufferData& buffer, 39 | const std::string& filename) { 40 | // see if we've already created a BufferViewData for this precise file 41 | auto iter = filenameToBufferView.find(filename); 42 | if (iter != filenameToBufferView.end()) { 43 | return iter->second; 44 | } 45 | 46 | std::shared_ptr result; 47 | std::ifstream file(filename, std::ios::binary | std::ios::ate); 48 | if (file) { 49 | std::streamsize size = file.tellg(); 50 | file.seekg(0, std::ios::beg); 51 | 52 | std::vector fileBuffer(size); 53 | if (file.read(fileBuffer.data(), size)) { 54 | result = AddRawBufferView(buffer, fileBuffer.data(), to_uint32(size)); 55 | } else { 56 | fmt::printf("Warning: Couldn't read %lu bytes from %s, skipping file.\n", size, filename); 57 | } 58 | } else { 59 | fmt::printf("Warning: Couldn't open file %s, skipping file.\n", filename); 60 | } 61 | // note that we persist here not only success, but also failure, as nullptr 62 | filenameToBufferView[filename] = result; 63 | return result; 64 | } 65 | 66 | void GltfModel::serializeHolders(json& glTFJson) { 67 | serializeHolder(glTFJson, "buffers", buffers); 68 | serializeHolder(glTFJson, "bufferViews", bufferViews); 69 | serializeHolder(glTFJson, "scenes", scenes); 70 | serializeHolder(glTFJson, "accessors", accessors); 71 | serializeHolder(glTFJson, "images", images); 72 | serializeHolder(glTFJson, "samplers", samplers); 73 | serializeHolder(glTFJson, "textures", textures); 74 | serializeHolder(glTFJson, "materials", materials); 75 | serializeHolder(glTFJson, "meshes", meshes); 76 | serializeHolder(glTFJson, "skins", skins); 77 | serializeHolder(glTFJson, "animations", animations); 78 | serializeHolder(glTFJson, "cameras", cameras); 79 | serializeHolder(glTFJson, "nodes", nodes); 80 | if (!lights.ptrs.empty()) { 81 | json lightsJson = json::object(); 82 | serializeHolder(lightsJson, "lights", lights); 83 | glTFJson["extensions"][KHR_LIGHTS_PUNCTUAL] = lightsJson; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/gltf/GltfModel.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | 13 | #include "FBX2glTF.h" 14 | 15 | #include "gltf/properties/AccessorData.hpp" 16 | #include "gltf/properties/AnimationData.hpp" 17 | #include "gltf/properties/BufferData.hpp" 18 | #include "gltf/properties/BufferViewData.hpp" 19 | #include "gltf/properties/CameraData.hpp" 20 | #include "gltf/properties/ImageData.hpp" 21 | #include "gltf/properties/LightData.hpp" 22 | #include "gltf/properties/MaterialData.hpp" 23 | #include "gltf/properties/MeshData.hpp" 24 | #include "gltf/properties/NodeData.hpp" 25 | #include "gltf/properties/PrimitiveData.hpp" 26 | #include "gltf/properties/SamplerData.hpp" 27 | #include "gltf/properties/SceneData.hpp" 28 | #include "gltf/properties/SkinData.hpp" 29 | #include "gltf/properties/TextureData.hpp" 30 | 31 | /** 32 | * glTF 2.0 is based on the idea that data structs within a file are referenced by index; an 33 | * accessor will point to the n:th buffer view, and so on. The Holder class takes a freshly 34 | * instantiated class, and then creates, stored, and returns a shared_ptr for it. 35 | * 36 | * The idea is that every glTF resource in the file will live as long as the Holder does, and the 37 | * Holders are all kept in the GLTFData struct. Clients may certainly cnhoose to perpetuate the full 38 | * shared_ptr reference counting type, but generally speaking we pass around simple T& and T* 39 | * types because the GLTFData struct will, by design, outlive all other activity that takes place 40 | * during in a single conversion run. 41 | */ 42 | template 43 | class Holder { 44 | public: 45 | std::shared_ptr hold(T* ptr) { 46 | ptr->ix = to_uint32(ptrs.size()); 47 | ptrs.emplace_back(ptr); 48 | return ptrs.back(); 49 | } 50 | std::vector> ptrs; 51 | }; 52 | 53 | class GltfModel { 54 | public: 55 | explicit GltfModel(const GltfOptions& options) 56 | : binary(new std::vector), 57 | isGlb(options.outputBinary), 58 | defaultSampler(nullptr), 59 | defaultBuffer(buffers.hold(buildDefaultBuffer(options))) { 60 | defaultSampler = samplers.hold(buildDefaultSampler()); 61 | } 62 | 63 | std::shared_ptr GetAlignedBufferView( 64 | BufferData& buffer, 65 | const BufferViewData::GL_ArrayType target); 66 | std::shared_ptr 67 | AddRawBufferView(BufferData& buffer, const char* source, uint32_t bytes); 68 | std::shared_ptr AddBufferViewForFile( 69 | BufferData& buffer, 70 | const std::string& filename); 71 | 72 | template 73 | std::shared_ptr AddAccessorWithView( 74 | BufferViewData& bufferView, 75 | const GLType& type, 76 | const std::vector& source, 77 | std::string name) { 78 | auto accessor = accessors.hold(new AccessorData(bufferView, type, name)); 79 | accessor->appendAsBinaryArray(source, *binary); 80 | bufferView.byteLength = accessor->byteLength(); 81 | return accessor; 82 | } 83 | 84 | template 85 | std::shared_ptr 86 | AddAccessorAndView(BufferData& buffer, const GLType& type, const std::vector& source) { 87 | auto bufferView = GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_NONE); 88 | return AddAccessorWithView(*bufferView, type, source, std::string("")); 89 | } 90 | 91 | template 92 | std::shared_ptr AddAccessorAndView( 93 | BufferData& buffer, 94 | const GLType& type, 95 | const std::vector& source, 96 | std::string name) { 97 | auto bufferView = GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_NONE); 98 | return AddAccessorWithView(*bufferView, type, source, name); 99 | } 100 | 101 | template 102 | std::shared_ptr AddAttributeToPrimitive( 103 | BufferData& buffer, 104 | const RawModel& surfaceModel, 105 | PrimitiveData& primitive, 106 | const AttributeDefinition& attrDef) { 107 | // copy attribute data into vector 108 | std::vector attribArr; 109 | surfaceModel.GetAttributeArray(attribArr, attrDef.rawAttributeIx); 110 | 111 | std::shared_ptr accessor; 112 | if (attrDef.dracoComponentType != draco::DT_INVALID && primitive.dracoMesh != nullptr) { 113 | primitive.AddDracoAttrib(attrDef, attribArr); 114 | 115 | accessor = accessors.hold(new AccessorData(attrDef.glType)); 116 | accessor->count = to_uint32(attribArr.size()); 117 | } else { 118 | auto bufferView = GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_BUFFER); 119 | accessor = AddAccessorWithView(*bufferView, attrDef.glType, attribArr, std::string("")); 120 | } 121 | primitive.AddAttrib(attrDef.gltfName, *accessor); 122 | return accessor; 123 | }; 124 | 125 | template 126 | void serializeHolder(json& glTFJson, std::string key, const Holder holder) { 127 | if (!holder.ptrs.empty()) { 128 | std::vector bits; 129 | for (const auto& ptr : holder.ptrs) { 130 | bits.push_back(ptr->serialize()); 131 | } 132 | glTFJson[key] = bits; 133 | } 134 | } 135 | 136 | void serializeHolders(json& glTFJson); 137 | 138 | const bool isGlb; 139 | 140 | // cache BufferViewData instances that've already been created from a given filename 141 | std::map> filenameToBufferView; 142 | 143 | std::shared_ptr> binary; 144 | 145 | Holder buffers; 146 | Holder bufferViews; 147 | Holder accessors; 148 | Holder images; 149 | Holder samplers; 150 | Holder textures; 151 | Holder materials; 152 | Holder meshes; 153 | Holder skins; 154 | Holder animations; 155 | Holder cameras; 156 | Holder nodes; 157 | Holder scenes; 158 | Holder lights; 159 | 160 | std::shared_ptr defaultSampler; 161 | std::shared_ptr defaultBuffer; 162 | 163 | private: 164 | SamplerData* buildDefaultSampler() { 165 | return new SamplerData(); 166 | } 167 | BufferData* buildDefaultBuffer(const GltfOptions& options) { 168 | return options.outputBinary ? new BufferData(binary) 169 | : new BufferData(extBufferFilename, binary, options.embedResources); 170 | } 171 | }; 172 | -------------------------------------------------------------------------------- /src/gltf/Raw2Gltf.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | 14 | // This can be a macro under Windows, confusing Draco 15 | #undef ERROR 16 | #include 17 | 18 | #include "FBX2glTF.h" 19 | #include "raw/RawModel.hpp" 20 | 21 | const std::string KHR_DRACO_MESH_COMPRESSION = "KHR_draco_mesh_compression"; 22 | const std::string KHR_MATERIALS_CMN_UNLIT = "KHR_materials_unlit"; 23 | const std::string KHR_LIGHTS_PUNCTUAL = "KHR_lights_punctual"; 24 | 25 | const std::string extBufferFilename = "buffer.bin"; 26 | 27 | struct ComponentType { 28 | // OpenGL Datatype enums 29 | enum GL_DataType { 30 | GL_BYTE = 5120, 31 | GL_UNSIGNED_BYTE, 32 | GL_SHORT, 33 | GL_UNSIGNED_SHORT, 34 | GL_INT, 35 | GL_UNSIGNED_INT, 36 | GL_FLOAT 37 | }; 38 | 39 | const GL_DataType glType; 40 | const unsigned int size; 41 | }; 42 | 43 | const ComponentType CT_USHORT = {ComponentType::GL_UNSIGNED_SHORT, 2}; 44 | const ComponentType CT_UINT = {ComponentType::GL_UNSIGNED_INT, 4}; 45 | const ComponentType CT_FLOAT = {ComponentType::GL_FLOAT, 4}; 46 | 47 | // Map our low-level data types for glTF output 48 | struct GLType { 49 | GLType(const ComponentType& componentType, unsigned int count, const std::string dataType) 50 | : componentType(componentType), count(count), dataType(dataType) {} 51 | 52 | unsigned int byteStride() const { 53 | return componentType.size * count; 54 | } 55 | 56 | void write(uint8_t* buf, const float scalar) const { 57 | *((float*)buf) = scalar; 58 | } 59 | void write(uint8_t* buf, const uint32_t scalar) const { 60 | switch (componentType.size) { 61 | case 1: 62 | *buf = (uint8_t)scalar; 63 | break; 64 | case 2: 65 | *((uint16_t*)buf) = (uint16_t)scalar; 66 | break; 67 | case 4: 68 | *((uint32_t*)buf) = scalar; 69 | break; 70 | } 71 | } 72 | 73 | template 74 | void write(uint8_t* buf, const mathfu::Vector& vector) const { 75 | for (int ii = 0; ii < d; ii++) { 76 | ((T*)buf)[ii] = vector(ii); 77 | } 78 | } 79 | template 80 | void write(uint8_t* buf, const mathfu::Matrix& matrix) const { 81 | // three matrix types require special alignment considerations that we don't handle 82 | // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#data-alignment 83 | assert(!(sizeof(T) == 1 && d == 2)); 84 | assert(!(sizeof(T) == 1 && d == 3)); 85 | assert(!(sizeof(T) == 2 && d == 2)); 86 | for (int col = 0; col < d; col++) { 87 | for (int row = 0; row < d; row++) { 88 | // glTF matrices are column-major 89 | ((T*)buf)[col * d + row] = matrix(row, col); 90 | } 91 | } 92 | } 93 | template 94 | void write(uint8_t* buf, const mathfu::Quaternion& quaternion) const { 95 | for (int ii = 0; ii < 3; ii++) { 96 | ((T*)buf)[ii] = quaternion.vector()(ii); 97 | } 98 | ((T*)buf)[3] = quaternion.scalar(); 99 | } 100 | 101 | const ComponentType componentType; 102 | const uint8_t count; 103 | const std::string dataType; 104 | }; 105 | 106 | const GLType GLT_FLOAT = {CT_FLOAT, 1, "SCALAR"}; 107 | const GLType GLT_USHORT = {CT_USHORT, 1, "SCALAR"}; 108 | const GLType GLT_UINT = {CT_UINT, 1, "SCALAR"}; 109 | const GLType GLT_VEC2F = {CT_FLOAT, 2, "VEC2"}; 110 | const GLType GLT_VEC3F = {CT_FLOAT, 3, "VEC3"}; 111 | const GLType GLT_VEC4F = {CT_FLOAT, 4, "VEC4"}; 112 | const GLType GLT_VEC4I = {CT_USHORT, 4, "VEC4"}; 113 | const GLType GLT_MAT2F = {CT_USHORT, 4, "MAT2"}; 114 | const GLType GLT_MAT3F = {CT_USHORT, 9, "MAT3"}; 115 | const GLType GLT_MAT4F = {CT_FLOAT, 16, "MAT4"}; 116 | const GLType GLT_QUATF = {CT_FLOAT, 4, "VEC4"}; 117 | 118 | /** 119 | * The base of any indexed glTF entity. 120 | */ 121 | struct Holdable { 122 | uint32_t ix = UINT_MAX; 123 | 124 | virtual json serialize() const = 0; 125 | }; 126 | 127 | template 128 | struct AttributeDefinition { 129 | const std::string gltfName; 130 | const T RawVertex::*rawAttributeIx; 131 | const GLType glType; 132 | const draco::GeometryAttribute::Type dracoAttribute; 133 | const draco::DataType dracoComponentType; 134 | 135 | AttributeDefinition( 136 | const std::string gltfName, 137 | const T RawVertex::*rawAttributeIx, 138 | const GLType& _glType, 139 | const draco::GeometryAttribute::Type dracoAttribute, 140 | const draco::DataType dracoComponentType) 141 | : gltfName(gltfName), 142 | rawAttributeIx(rawAttributeIx), 143 | glType(_glType), 144 | dracoAttribute(dracoAttribute), 145 | dracoComponentType(dracoComponentType) {} 146 | 147 | AttributeDefinition( 148 | const std::string gltfName, 149 | const T RawVertex::*rawAttributeIx, 150 | const GLType& _glType) 151 | : gltfName(gltfName), 152 | rawAttributeIx(rawAttributeIx), 153 | glType(_glType), 154 | dracoAttribute(draco::GeometryAttribute::INVALID), 155 | dracoComponentType(draco::DataType::DT_INVALID) {} 156 | }; 157 | 158 | struct AccessorData; 159 | struct AnimationData; 160 | struct BufferData; 161 | struct BufferViewData; 162 | struct CameraData; 163 | struct GLTFData; 164 | struct ImageData; 165 | struct MaterialData; 166 | struct MeshData; 167 | struct NodeData; 168 | struct PrimitiveData; 169 | struct SamplerData; 170 | struct SceneData; 171 | struct SkinData; 172 | struct TextureData; 173 | 174 | struct ModelData { 175 | explicit ModelData(std::shared_ptr> const& _binary) 176 | : binary(_binary) {} 177 | 178 | std::shared_ptr> const binary; 179 | }; 180 | 181 | ModelData* Raw2Gltf( 182 | std::ofstream& gltfOutStream, 183 | const std::string& outputFolder, 184 | const RawModel& raw, 185 | const GltfOptions& options); 186 | -------------------------------------------------------------------------------- /src/gltf/TextureBuilder.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #include "TextureBuilder.hpp" 10 | 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | 21 | // keep track of some texture data as we load them 22 | struct TexInfo { 23 | explicit TexInfo(int rawTexIx) : rawTexIx(rawTexIx) {} 24 | 25 | const int rawTexIx; 26 | int width{}; 27 | int height{}; 28 | int channels{}; 29 | uint8_t* pixels{}; 30 | }; 31 | 32 | std::shared_ptr TextureBuilder::combine( 33 | const std::vector& ixVec, 34 | const std::string& tag, 35 | const pixel_merger& computePixel, 36 | bool includeAlphaChannel) { 37 | const std::string key = texIndicesKey(ixVec, tag); 38 | auto iter = textureByIndicesKey.find(key); 39 | if (iter != textureByIndicesKey.end()) { 40 | return iter->second; 41 | } 42 | 43 | int width = -1, height = -1; 44 | std::string mergedFilename = tag; 45 | std::vector texes{}; 46 | for (const int rawTexIx : ixVec) { 47 | TexInfo info(rawTexIx); 48 | if (rawTexIx >= 0) { 49 | const RawTexture& rawTex = raw.GetTexture(rawTexIx); 50 | const std::string& fileLoc = rawTex.fileLocation; 51 | const std::string& name = FileUtils::GetFileBase(FileUtils::GetFileName(fileLoc)); 52 | if (!fileLoc.empty()) { 53 | info.pixels = stbi_load(fileLoc.c_str(), &info.width, &info.height, &info.channels, 0); 54 | if (!info.pixels) { 55 | fmt::printf("Warning: merge texture [%d](%s) could not be loaded.\n", rawTexIx, name); 56 | } else { 57 | if (width < 0) { 58 | width = info.width; 59 | height = info.height; 60 | } else if (width != info.width || height != info.height) { 61 | fmt::printf( 62 | "Warning: texture %s (%d, %d) can't be merged with previous texture(s) of dimension (%d, %d)\n", 63 | name, 64 | info.width, 65 | info.height, 66 | width, 67 | height); 68 | // this is bad enough that we abort the whole merge 69 | return nullptr; 70 | } 71 | mergedFilename += "_" + name; 72 | } 73 | } 74 | } 75 | texes.push_back(info); 76 | } 77 | // at the moment, the best choice of filename is also the best choice of name 78 | const std::string mergedName = mergedFilename; 79 | 80 | if (width < 0) { 81 | // no textures to merge; bail 82 | return nullptr; 83 | } 84 | // TODO: which channel combinations make sense in input files? 85 | 86 | // write 3 or 4 channels depending on whether or not we need transparency 87 | int channels = includeAlphaChannel ? 4 : 3; 88 | 89 | std::vector mergedPixels(static_cast(channels * width * height)); 90 | for (int xx = 0; xx < width; xx++) { 91 | for (int yy = 0; yy < height; yy++) { 92 | std::vector pixels(texes.size()); 93 | std::vector pixelPointers(texes.size(), nullptr); 94 | for (int jj = 0; jj < texes.size(); jj++) { 95 | const TexInfo& tex = texes[jj]; 96 | // each texture's structure will depend on its channel count 97 | int ii = tex.channels * (xx + yy * width); 98 | int kk = 0; 99 | if (tex.pixels != nullptr) { 100 | for (; kk < tex.channels; kk++) { 101 | pixels[jj][kk] = tex.pixels[ii++] / 255.0f; 102 | } 103 | } 104 | for (; kk < pixels[jj].size(); kk++) { 105 | pixels[jj][kk] = 1.0f; 106 | } 107 | pixelPointers[jj] = &pixels[jj]; 108 | } 109 | const pixel merged = computePixel(pixelPointers); 110 | int ii = channels * (xx + yy * width); 111 | for (int jj = 0; jj < channels; jj++) { 112 | mergedPixels[ii + jj] = static_cast(fmax(0, fmin(255.0f, merged[jj] * 255.0f))); 113 | } 114 | } 115 | } 116 | 117 | // write a .png iff we need transparency in the destination texture 118 | bool png = includeAlphaChannel; 119 | 120 | std::vector imgBuffer; 121 | int res; 122 | if (png) { 123 | res = stbi_write_png_to_func( 124 | WriteToVectorContext, 125 | &imgBuffer, 126 | width, 127 | height, 128 | channels, 129 | mergedPixels.data(), 130 | width * channels); 131 | } else { 132 | res = stbi_write_jpg_to_func( 133 | WriteToVectorContext, &imgBuffer, width, height, channels, mergedPixels.data(), 80); 134 | } 135 | if (!res) { 136 | fmt::printf("Warning: failed to generate merge texture '%s'.\n", mergedFilename); 137 | return nullptr; 138 | } 139 | 140 | ImageData* image; 141 | if (options.outputBinary) { 142 | const auto bufferView = 143 | gltf.AddRawBufferView(*gltf.defaultBuffer, imgBuffer.data(), to_uint32(imgBuffer.size())); 144 | image = new ImageData(mergedName, *bufferView, png ? "image/png" : "image/jpeg"); 145 | } else { 146 | const std::string imageFilename = mergedFilename + (png ? ".png" : ".jpg"); 147 | const std::string imagePath = outputFolder + imageFilename; 148 | FILE* fp = fopen(imagePath.c_str(), "wb"); 149 | if (fp == nullptr) { 150 | fmt::printf("Warning:: Couldn't write file '%s' for writing.\n", imagePath); 151 | return nullptr; 152 | } 153 | 154 | if (fwrite(imgBuffer.data(), imgBuffer.size(), 1, fp) != 1) { 155 | fmt::printf( 156 | "Warning: Failed to write %lu bytes to file '%s'.\n", imgBuffer.size(), imagePath); 157 | fclose(fp); 158 | return nullptr; 159 | } 160 | fclose(fp); 161 | if (verboseOutput) { 162 | fmt::printf("Wrote %lu bytes to texture '%s'.\n", imgBuffer.size(), imagePath); 163 | } 164 | image = new ImageData(mergedName, imageFilename); 165 | } 166 | std::shared_ptr texDat = gltf.textures.hold( 167 | new TextureData(mergedName, *gltf.defaultSampler, *gltf.images.hold(image))); 168 | textureByIndicesKey.insert(std::make_pair(key, texDat)); 169 | return texDat; 170 | } 171 | 172 | /** Create a new TextureData for the given RawTexture index, or return a previously created one. */ 173 | std::shared_ptr TextureBuilder::simple(int rawTexIndex, const std::string& tag) { 174 | const std::string key = texIndicesKey({rawTexIndex}, tag); 175 | auto iter = textureByIndicesKey.find(key); 176 | if (iter != textureByIndicesKey.end()) { 177 | return iter->second; 178 | } 179 | 180 | const RawTexture& rawTexture = raw.GetTexture(rawTexIndex); 181 | const std::string textureName = FileUtils::GetFileBase(rawTexture.name); 182 | const std::string relativeFilename = FileUtils::GetFileName(rawTexture.fileLocation); 183 | 184 | ImageData* image = nullptr; 185 | if (options.outputBinary) { 186 | auto bufferView = gltf.AddBufferViewForFile(*gltf.defaultBuffer, rawTexture.fileLocation); 187 | if (bufferView) { 188 | const auto& suffix = FileUtils::GetFileSuffix(rawTexture.fileLocation); 189 | std::string mimeType; 190 | if (suffix) { 191 | mimeType = ImageUtils::suffixToMimeType(suffix.value()); 192 | } else { 193 | mimeType = "image/jpeg"; 194 | fmt::printf( 195 | "Warning: Can't deduce mime type of texture '%s'; using %s.\n", 196 | rawTexture.fileLocation, 197 | mimeType); 198 | } 199 | image = new ImageData(relativeFilename, *bufferView, mimeType); 200 | } 201 | 202 | } else if (!relativeFilename.empty()) { 203 | image = new ImageData(relativeFilename, relativeFilename); 204 | std::string outputPath = outputFolder + "/" + relativeFilename; 205 | if (FileUtils::CopyFile(rawTexture.fileLocation, outputPath, true)) { 206 | if (verboseOutput) { 207 | fmt::printf("Copied texture '%s' to output folder: %s\n", textureName, outputPath); 208 | } 209 | } else { 210 | // no point commenting further on read/write error; CopyFile() does enough of that, and we 211 | // certainly want to to add an image struct to the glTF JSON, with the correct relative path 212 | // reference, even if the copy failed. 213 | } 214 | } 215 | if (!image) { 216 | // fallback is tiny transparent PNG 217 | image = new ImageData( 218 | textureName, 219 | "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg=="); 220 | } 221 | 222 | std::shared_ptr texDat = gltf.textures.hold( 223 | new TextureData(textureName, *gltf.defaultSampler, *gltf.images.hold(image))); 224 | textureByIndicesKey.insert(std::make_pair(key, texDat)); 225 | return texDat; 226 | } 227 | -------------------------------------------------------------------------------- /src/gltf/TextureBuilder.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | 13 | #include "FBX2glTF.h" 14 | 15 | #include 16 | 17 | #include "GltfModel.hpp" 18 | 19 | class TextureBuilder { 20 | public: 21 | using pixel = std::array; // pixel components are floats in [0, 1] 22 | using pixel_merger = std::function)>; 23 | 24 | TextureBuilder( 25 | const RawModel& raw, 26 | const GltfOptions& options, 27 | const std::string& outputFolder, 28 | GltfModel& gltf) 29 | : raw(raw), options(options), outputFolder(outputFolder), gltf(gltf) {} 30 | ~TextureBuilder() {} 31 | 32 | std::shared_ptr combine( 33 | const std::vector& ixVec, 34 | const std::string& tag, 35 | const pixel_merger& mergeFunction, 36 | bool transparency); 37 | 38 | std::shared_ptr simple(int rawTexIndex, const std::string& tag); 39 | 40 | static std::string texIndicesKey(const std::vector& ixVec, const std::string& tag) { 41 | std::string result = tag; 42 | for (int ix : ixVec) { 43 | result += "_" + std::to_string(ix); 44 | } 45 | return result; 46 | }; 47 | 48 | static std::string describeChannel(int channels) { 49 | switch (channels) { 50 | case 1: 51 | return "G"; 52 | case 2: 53 | return "GA"; 54 | case 3: 55 | return "RGB"; 56 | case 4: 57 | return "RGBA"; 58 | default: 59 | return fmt::format("?%d?", channels); 60 | } 61 | }; 62 | 63 | static void WriteToVectorContext(void* context, void* data, int size) { 64 | auto* vec = static_cast*>(context); 65 | for (int ii = 0; ii < size; ii++) { 66 | vec->push_back(((uint8_t*)data)[ii]); 67 | } 68 | } 69 | 70 | private: 71 | const RawModel& raw; 72 | const GltfOptions& options; 73 | const std::string outputFolder; 74 | GltfModel& gltf; 75 | 76 | std::map> textureByIndicesKey; 77 | }; 78 | -------------------------------------------------------------------------------- /src/gltf/properties/AccessorData.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #include "AccessorData.hpp" 10 | #include "BufferViewData.hpp" 11 | 12 | AccessorData::AccessorData(const BufferViewData& bufferView, GLType type, std::string name) 13 | : Holdable(), 14 | bufferView(bufferView.ix), 15 | type(std::move(type)), 16 | byteOffset(0), 17 | count(0), 18 | name(name) {} 19 | 20 | AccessorData::AccessorData(GLType type) 21 | : Holdable(), bufferView(-1), type(std::move(type)), byteOffset(0), count(0) {} 22 | 23 | json AccessorData::serialize() const { 24 | json result{ 25 | {"componentType", type.componentType.glType}, {"type", type.dataType}, {"count", count}}; 26 | if (bufferView >= 0) { 27 | result["bufferView"] = bufferView; 28 | result["byteOffset"] = byteOffset; 29 | } 30 | if (!min.empty()) { 31 | result["min"] = min; 32 | } 33 | if (!max.empty()) { 34 | result["max"] = max; 35 | } 36 | if (name.length() > 0) { 37 | result["name"] = name; 38 | } 39 | return result; 40 | } 41 | -------------------------------------------------------------------------------- /src/gltf/properties/AccessorData.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "gltf/Raw2Gltf.hpp" 12 | 13 | struct AccessorData : Holdable { 14 | AccessorData(const BufferViewData& bufferView, GLType type, std::string name); 15 | explicit AccessorData(GLType type); 16 | 17 | json serialize() const override; 18 | 19 | template 20 | void appendAsBinaryArray(const std::vector& in, std::vector& out) { 21 | const unsigned int stride = type.byteStride(); 22 | const size_t offset = out.size(); 23 | const size_t count = in.size(); 24 | 25 | this->count = (unsigned int)count; 26 | 27 | out.resize(offset + count * stride); 28 | for (int ii = 0; ii < count; ii++) { 29 | type.write(&out[offset + ii * stride], in[ii]); 30 | } 31 | } 32 | 33 | unsigned int byteLength() const { 34 | return type.byteStride() * count; 35 | } 36 | 37 | const int bufferView; 38 | const GLType type; 39 | 40 | unsigned int byteOffset; 41 | unsigned int count; 42 | std::vector min; 43 | std::vector max; 44 | std::string name; 45 | }; 46 | -------------------------------------------------------------------------------- /src/gltf/properties/AnimationData.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #include "AnimationData.hpp" 10 | 11 | #include 12 | 13 | #include "AccessorData.hpp" 14 | #include "NodeData.hpp" 15 | 16 | AnimationData::AnimationData(std::string name, const AccessorData& timeAccessor) 17 | : Holdable(), name(std::move(name)), timeAccessor(timeAccessor.ix) {} 18 | 19 | // assumption: 1-to-1 relationship between channels and samplers; this is a simplification on what 20 | // glTF can express, but it means we can rely on samplerIx == channelIx throughout an animation 21 | void AnimationData::AddNodeChannel( 22 | const NodeData& node, 23 | const AccessorData& accessor, 24 | std::string path) { 25 | assert(channels.size() == samplers.size()); 26 | uint32_t ix = to_uint32(channels.size()); 27 | channels.emplace_back(channel_t(ix, node, std::move(path))); 28 | samplers.emplace_back(sampler_t(timeAccessor, accessor.ix)); 29 | } 30 | 31 | json AnimationData::serialize() const { 32 | return {{"name", name}, {"channels", channels}, {"samplers", samplers}}; 33 | } 34 | 35 | AnimationData::channel_t::channel_t(uint32_t ix, const NodeData& node, std::string path) 36 | : ix(ix), node(node.ix), path(std::move(path)) {} 37 | 38 | AnimationData::sampler_t::sampler_t(uint32_t time, uint32_t output) : time(time), output(output) {} 39 | 40 | void to_json(json& j, const AnimationData::channel_t& data) { 41 | j = json{{"sampler", data.ix}, 42 | { 43 | "target", 44 | {{"node", data.node}, {"path", data.path}}, 45 | }}; 46 | } 47 | 48 | void to_json(json& j, const AnimationData::sampler_t& data) { 49 | j = json{ 50 | {"input", data.time}, 51 | {"interpolation", "LINEAR"}, 52 | {"output", data.output}, 53 | }; 54 | } 55 | -------------------------------------------------------------------------------- /src/gltf/properties/AnimationData.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "gltf/Raw2Gltf.hpp" 12 | 13 | struct AnimationData : Holdable { 14 | AnimationData(std::string name, const AccessorData& timeAccessor); 15 | 16 | // assumption: 1-to-1 relationship between channels and samplers; this is a simplification on what 17 | // glTF can express, but it means we can rely on samplerIx == channelIx throughout an animation 18 | void AddNodeChannel(const NodeData& node, const AccessorData& accessor, std::string path); 19 | 20 | json serialize() const override; 21 | 22 | struct channel_t { 23 | channel_t(uint32_t _ix, const NodeData& node, std::string path); 24 | 25 | const uint32_t ix; 26 | const uint32_t node; 27 | const std::string path; 28 | }; 29 | 30 | struct sampler_t { 31 | sampler_t(uint32_t time, uint32_t output); 32 | 33 | const uint32_t time; 34 | const uint32_t output; 35 | }; 36 | 37 | const std::string name; 38 | const uint32_t timeAccessor; 39 | std::vector channels; 40 | std::vector samplers; 41 | }; 42 | 43 | void to_json(json& j, const AnimationData::channel_t& data); 44 | void to_json(json& j, const AnimationData::sampler_t& data); 45 | -------------------------------------------------------------------------------- /src/gltf/properties/BufferData.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #include 10 | 11 | #include "BufferData.hpp" 12 | 13 | BufferData::BufferData(const std::shared_ptr>& binData) 14 | : Holdable(), isGlb(true), binData(binData) {} 15 | 16 | BufferData::BufferData( 17 | std::string uri, 18 | const std::shared_ptr>& binData, 19 | bool isEmbedded) 20 | : Holdable(), isGlb(false), uri(isEmbedded ? "" : std::move(uri)), binData(binData) {} 21 | 22 | json BufferData::serialize() const { 23 | json result{{"byteLength", binData->size()}}; 24 | if (!isGlb) { 25 | if (!uri.empty()) { 26 | result["uri"] = uri; 27 | } else { 28 | std::string encoded = base64::encode(*binData); 29 | result["uri"] = "data:application/octet-stream;base64," + encoded; 30 | } 31 | } 32 | return result; 33 | } 34 | -------------------------------------------------------------------------------- /src/gltf/properties/BufferData.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "gltf/Raw2Gltf.hpp" 12 | 13 | struct BufferData : Holdable { 14 | explicit BufferData(const std::shared_ptr>& binData); 15 | 16 | BufferData( 17 | std::string uri, 18 | const std::shared_ptr>& binData, 19 | bool isEmbedded = false); 20 | 21 | json serialize() const override; 22 | 23 | const bool isGlb; 24 | const std::string uri; 25 | const std::shared_ptr> binData; // TODO this is just weird 26 | }; 27 | -------------------------------------------------------------------------------- /src/gltf/properties/BufferViewData.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #include "BufferViewData.hpp" 10 | #include "BufferData.hpp" 11 | 12 | BufferViewData::BufferViewData( 13 | const BufferData& _buffer, 14 | const size_t _byteOffset, 15 | const GL_ArrayType _target) 16 | : Holdable(), buffer(_buffer.ix), byteOffset((unsigned int)_byteOffset), target(_target) {} 17 | 18 | json BufferViewData::serialize() const { 19 | json result{{"buffer", buffer}, {"byteLength", byteLength}, {"byteOffset", byteOffset}}; 20 | if (target != GL_ARRAY_NONE) { 21 | result["target"] = target; 22 | } 23 | return result; 24 | } 25 | -------------------------------------------------------------------------------- /src/gltf/properties/BufferViewData.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "gltf/Raw2Gltf.hpp" 12 | 13 | struct BufferViewData : Holdable { 14 | enum GL_ArrayType { 15 | GL_ARRAY_NONE = 0, // no GL buffer is being set 16 | GL_ARRAY_BUFFER = 34962, 17 | GL_ELEMENT_ARRAY_BUFFER = 34963 18 | }; 19 | 20 | BufferViewData(const BufferData& _buffer, const size_t _byteOffset, const GL_ArrayType _target); 21 | 22 | json serialize() const override; 23 | 24 | const unsigned int buffer; 25 | const unsigned int byteOffset; 26 | const GL_ArrayType target; 27 | 28 | unsigned int byteLength = 0; 29 | }; 30 | -------------------------------------------------------------------------------- /src/gltf/properties/CameraData.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #include "CameraData.hpp" 10 | 11 | CameraData::CameraData() 12 | : Holdable(), aspectRatio(0.0f), yfov(0.0f), xmag(0.0f), ymag(0.0f), znear(0.0f), zfar(0.0f) {} 13 | 14 | json CameraData::serialize() const { 15 | json result{ 16 | {"name", name}, 17 | {"type", type}, 18 | }; 19 | json subResult{{"znear", znear}, {"zfar", zfar}}; 20 | if (type == "perspective") { 21 | subResult["aspectRatio"] = aspectRatio; 22 | subResult["yfov"] = yfov; 23 | } else { 24 | subResult["xmag"] = xmag; 25 | subResult["ymag"] = ymag; 26 | } 27 | result[type] = subResult; 28 | return result; 29 | } 30 | -------------------------------------------------------------------------------- /src/gltf/properties/CameraData.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "gltf/Raw2Gltf.hpp" 12 | 13 | // TODO: this class needs some work 14 | struct CameraData : Holdable { 15 | CameraData(); 16 | json serialize() const override; 17 | 18 | std::string name; 19 | std::string type; 20 | float aspectRatio; 21 | float yfov; 22 | float xmag; 23 | float ymag; 24 | float znear; 25 | float zfar; 26 | }; 27 | -------------------------------------------------------------------------------- /src/gltf/properties/ImageData.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #include "ImageData.hpp" 10 | 11 | #include 12 | 13 | #include "BufferViewData.hpp" 14 | 15 | ImageData::ImageData(std::string name, std::string uri) 16 | : Holdable(), name(std::move(name)), uri(std::move(uri)), bufferView(-1) {} 17 | 18 | ImageData::ImageData(std::string name, const BufferViewData& bufferView, std::string mimeType) 19 | : Holdable(), name(std::move(name)), bufferView(bufferView.ix), mimeType(std::move(mimeType)) {} 20 | 21 | json ImageData::serialize() const { 22 | if (bufferView < 0) { 23 | return {{"name", name}, {"uri", uri}}; 24 | } 25 | return {{"name", name}, {"bufferView", bufferView}, {"mimeType", mimeType}}; 26 | } 27 | -------------------------------------------------------------------------------- /src/gltf/properties/ImageData.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "gltf/Raw2Gltf.hpp" 12 | 13 | struct ImageData : Holdable { 14 | ImageData(std::string name, std::string uri); 15 | ImageData(std::string name, const BufferViewData& bufferView, std::string mimeType); 16 | 17 | json serialize() const override; 18 | 19 | const std::string name; 20 | const std::string uri; // non-empty in gltf mode 21 | const int32_t bufferView; // non-negative in glb mode 22 | const std::string mimeType; 23 | }; 24 | -------------------------------------------------------------------------------- /src/gltf/properties/LightData.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #include "LightData.hpp" 10 | 11 | LightData::LightData( 12 | std::string name, 13 | Type type, 14 | Vec3f color, 15 | float intensity, 16 | float innerConeAngle, 17 | float outerConeAngle) 18 | : Holdable(), 19 | type(type), 20 | color(color), 21 | intensity(intensity), 22 | innerConeAngle(innerConeAngle), 23 | outerConeAngle(outerConeAngle) {} 24 | 25 | json LightData::serialize() const { 26 | json result{{"name", name}, {"color", toStdVec(color)}, {"intensity", intensity}}; 27 | switch (type) { 28 | case Directional: 29 | result["type"] = "directional"; 30 | break; 31 | case Point: 32 | result["type"] = "point"; 33 | break; 34 | case Spot: 35 | result["type"] = "spot"; 36 | json spotJson; 37 | if (innerConeAngle != 0) { 38 | spotJson["innerConeAngle"] = innerConeAngle; 39 | } 40 | if (outerConeAngle != M_PI_4) { 41 | spotJson["outerConeAngle"] = outerConeAngle; 42 | } 43 | result["spot"] = spotJson; 44 | break; 45 | } 46 | return result; 47 | } 48 | -------------------------------------------------------------------------------- /src/gltf/properties/LightData.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "gltf/Raw2Gltf.hpp" 12 | 13 | struct LightData : Holdable { 14 | enum Type { 15 | Directional, 16 | Point, 17 | Spot, 18 | }; 19 | 20 | LightData( 21 | std::string name, 22 | Type type, 23 | Vec3f color, 24 | float intensity, 25 | float innerConeAngle, 26 | float outerConeAngle); 27 | 28 | json serialize() const override; 29 | 30 | const std::string name; 31 | const Type type; 32 | const Vec3f color; 33 | const float intensity; 34 | const float innerConeAngle; 35 | const float outerConeAngle; 36 | }; 37 | -------------------------------------------------------------------------------- /src/gltf/properties/MaterialData.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #include "MaterialData.hpp" 10 | #include "TextureData.hpp" 11 | 12 | // TODO: retrieve & pass in correct UV set from FBX 13 | std::unique_ptr Tex::ref(const TextureData* tex, uint32_t texCoord) { 14 | return std::unique_ptr{(tex != nullptr) ? new Tex(tex->ix, texCoord) : nullptr}; 15 | } 16 | 17 | Tex::Tex(uint32_t texRef, uint32_t texCoord) : texRef(texRef), texCoord(texCoord) {} 18 | 19 | void to_json(json& j, const Tex& data) { 20 | j = json{{"index", data.texRef}, {"texCoord", data.texCoord}}; 21 | } 22 | 23 | KHRCmnUnlitMaterial::KHRCmnUnlitMaterial() {} 24 | 25 | void to_json(json& j, const KHRCmnUnlitMaterial& d) { 26 | j = json({}); 27 | } 28 | 29 | inline float clamp(float d, float bottom = 0, float top = 1) { 30 | return std::max(bottom, std::min(top, d)); 31 | } 32 | inline Vec3f 33 | clamp(const Vec3f& vec, const Vec3f& bottom = VEC3F_ZERO, const Vec3f& top = VEC3F_ONE) { 34 | return Vec3f::Max(bottom, Vec3f::Min(top, vec)); 35 | } 36 | inline Vec4f 37 | clamp(const Vec4f& vec, const Vec4f& bottom = VEC4F_ZERO, const Vec4f& top = VEC4F_ONE) { 38 | return Vec4f::Max(bottom, Vec4f::Min(top, vec)); 39 | } 40 | 41 | PBRMetallicRoughness::PBRMetallicRoughness( 42 | const TextureData* baseColorTexture, 43 | const TextureData* metRoughTexture, 44 | const Vec4f& baseColorFactor, 45 | float metallic, 46 | float roughness) 47 | : baseColorTexture(Tex::ref(baseColorTexture)), 48 | metRoughTexture(Tex::ref(metRoughTexture)), 49 | baseColorFactor(clamp(baseColorFactor)), 50 | metallic(clamp(metallic)), 51 | roughness(clamp(roughness)) {} 52 | 53 | void to_json(json& j, const PBRMetallicRoughness& d) { 54 | j = {}; 55 | if (d.baseColorTexture != nullptr) { 56 | j["baseColorTexture"] = *d.baseColorTexture; 57 | } 58 | if (d.baseColorFactor.LengthSquared() > 0) { 59 | j["baseColorFactor"] = toStdVec(d.baseColorFactor); 60 | } 61 | if (d.metRoughTexture != nullptr) { 62 | j["metallicRoughnessTexture"] = *d.metRoughTexture; 63 | // if a texture is provided, throw away metallic/roughness values 64 | j["roughnessFactor"] = 1.0f; 65 | j["metallicFactor"] = 1.0f; 66 | } else { 67 | // without a texture, however, use metallic/roughness as constants 68 | j["metallicFactor"] = d.metallic; 69 | j["roughnessFactor"] = d.roughness; 70 | } 71 | } 72 | 73 | MaterialData::MaterialData( 74 | std::string name, 75 | bool isTransparent, 76 | const RawShadingModel shadingModel, 77 | const TextureData* normalTexture, 78 | const TextureData* occlusionTexture, 79 | const TextureData* emissiveTexture, 80 | const Vec3f& emissiveFactor, 81 | std::shared_ptr const khrCmnConstantMaterial, 82 | std::shared_ptr const pbrMetallicRoughness) 83 | : Holdable(), 84 | name(std::move(name)), 85 | shadingModel(shadingModel), 86 | isTransparent(isTransparent), 87 | normalTexture(Tex::ref(normalTexture)), 88 | occlusionTexture(Tex::ref(occlusionTexture)), 89 | emissiveTexture(Tex::ref(emissiveTexture)), 90 | emissiveFactor(clamp(emissiveFactor)), 91 | khrCmnConstantMaterial(khrCmnConstantMaterial), 92 | pbrMetallicRoughness(pbrMetallicRoughness) {} 93 | 94 | json MaterialData::serialize() const { 95 | json result = {{"name", name}, 96 | {"alphaMode", isTransparent ? "BLEND" : "OPAQUE"}, 97 | {"extras", 98 | {{"fromFBX", 99 | {{"shadingModel", Describe(shadingModel)}, 100 | {"isTruePBR", shadingModel == RAW_SHADING_MODEL_PBR_MET_ROUGH}}}}}}; 101 | 102 | if (normalTexture != nullptr) { 103 | result["normalTexture"] = *normalTexture; 104 | } 105 | if (occlusionTexture != nullptr) { 106 | result["occlusionTexture"] = *occlusionTexture; 107 | } 108 | if (emissiveTexture != nullptr) { 109 | result["emissiveTexture"] = *emissiveTexture; 110 | } 111 | if (emissiveFactor.LengthSquared() > 0) { 112 | result["emissiveFactor"] = toStdVec(emissiveFactor); 113 | } 114 | if (pbrMetallicRoughness != nullptr) { 115 | result["pbrMetallicRoughness"] = *pbrMetallicRoughness; 116 | } 117 | if (khrCmnConstantMaterial != nullptr) { 118 | json extensions = {}; 119 | extensions[KHR_MATERIALS_CMN_UNLIT] = *khrCmnConstantMaterial; 120 | result["extensions"] = extensions; 121 | } 122 | 123 | for (const auto& i : userProperties) { 124 | auto& prop_map = result["extras"]["fromFBX"]["userProperties"]; 125 | 126 | json j = json::parse(i); 127 | for (const auto& k : json::iterator_wrapper(j)) { 128 | prop_map[k.key()] = k.value(); 129 | } 130 | } 131 | 132 | return result; 133 | } 134 | -------------------------------------------------------------------------------- /src/gltf/properties/MaterialData.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | 13 | #include "gltf/Raw2Gltf.hpp" 14 | 15 | struct Tex { 16 | static std::unique_ptr ref(const TextureData* tex, uint32_t texCoord = 0); 17 | explicit Tex(uint32_t texRef, uint32_t texCoord); 18 | 19 | const uint32_t texRef; 20 | const uint32_t texCoord; 21 | }; 22 | 23 | struct KHRCmnUnlitMaterial { 24 | KHRCmnUnlitMaterial(); 25 | }; 26 | 27 | struct PBRMetallicRoughness { 28 | PBRMetallicRoughness( 29 | const TextureData* baseColorTexture, 30 | const TextureData* metRoughTexture, 31 | const Vec4f& baseColorFactor, 32 | float metallic = 0.1f, 33 | float roughness = 0.6f); 34 | 35 | std::unique_ptr baseColorTexture; 36 | std::unique_ptr metRoughTexture; 37 | const Vec4f baseColorFactor; 38 | const float metallic; 39 | const float roughness; 40 | }; 41 | 42 | struct MaterialData : Holdable { 43 | MaterialData( 44 | std::string name, 45 | bool isTransparent, 46 | RawShadingModel shadingModel, 47 | const TextureData* normalTexture, 48 | const TextureData* occlusionTexture, 49 | const TextureData* emissiveTexture, 50 | const Vec3f& emissiveFactor, 51 | std::shared_ptr const khrCmnConstantMaterial, 52 | std::shared_ptr const pbrMetallicRoughness); 53 | 54 | json serialize() const override; 55 | 56 | const std::string name; 57 | const RawShadingModel shadingModel; 58 | const bool isTransparent; 59 | const std::unique_ptr normalTexture; 60 | const std::unique_ptr occlusionTexture; 61 | const std::unique_ptr emissiveTexture; 62 | const Vec3f emissiveFactor; 63 | 64 | const std::shared_ptr khrCmnConstantMaterial; 65 | const std::shared_ptr pbrMetallicRoughness; 66 | 67 | std::vector userProperties; 68 | }; 69 | 70 | void to_json(json& j, const Tex& data); 71 | void to_json(json& j, const KHRCmnUnlitMaterial& d); 72 | void to_json(json& j, const PBRMetallicRoughness& d); 73 | -------------------------------------------------------------------------------- /src/gltf/properties/MeshData.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #include "MeshData.hpp" 10 | #include "PrimitiveData.hpp" 11 | 12 | MeshData::MeshData(const std::string& name, const std::vector& weights) 13 | : Holdable(), name(name), weights(weights) {} 14 | 15 | json MeshData::serialize() const { 16 | json jsonPrimitivesArray = json::array(); 17 | json jsonTargetNamesArray = json::array(); 18 | for (const auto& primitive : primitives) { 19 | jsonPrimitivesArray.push_back(*primitive); 20 | if (!primitive->targetNames.empty()) { 21 | for (auto targetName : primitive->targetNames) { 22 | jsonTargetNamesArray.push_back(targetName); 23 | } 24 | } 25 | } 26 | json result = {{"name", name}, {"primitives", jsonPrimitivesArray}}; 27 | if (!weights.empty()) { 28 | result["weights"] = weights; 29 | } 30 | if (!jsonTargetNamesArray.empty()) { 31 | result["extras"]["targetNames"] = jsonTargetNamesArray; 32 | } 33 | return result; 34 | } 35 | -------------------------------------------------------------------------------- /src/gltf/properties/MeshData.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | 13 | #include 14 | 15 | #include "gltf/Raw2Gltf.hpp" 16 | 17 | #include "PrimitiveData.hpp" 18 | 19 | struct MeshData : Holdable { 20 | MeshData(const std::string& name, const std::vector& weights); 21 | 22 | void AddPrimitive(std::shared_ptr primitive) { 23 | primitives.push_back(std::move(primitive)); 24 | } 25 | 26 | json serialize() const override; 27 | 28 | const std::string name; 29 | const std::vector weights; 30 | std::vector> primitives; 31 | }; 32 | -------------------------------------------------------------------------------- /src/gltf/properties/NodeData.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #include "NodeData.hpp" 10 | 11 | NodeData::NodeData( 12 | std::string name, 13 | const Vec3f& translation, 14 | const Quatf& rotation, 15 | const Vec3f& scale, 16 | bool isJoint) 17 | : Holdable(), 18 | name(std::move(name)), 19 | isJoint(isJoint), 20 | translation(translation), 21 | rotation(rotation), 22 | scale(scale), 23 | children(), 24 | mesh(-1), 25 | camera(-1), 26 | light(-1), 27 | skin(-1) {} 28 | 29 | void NodeData::AddChildNode(uint32_t childIx) { 30 | children.push_back(childIx); 31 | } 32 | 33 | void NodeData::SetMesh(uint32_t meshIx) { 34 | assert(mesh < 0); 35 | assert(!isJoint); 36 | mesh = meshIx; 37 | } 38 | 39 | void NodeData::SetSkin(uint32_t skinIx) { 40 | assert(skin < 0); 41 | assert(!isJoint); 42 | skin = skinIx; 43 | } 44 | 45 | void NodeData::SetCamera(uint32_t cameraIndex) { 46 | assert(!isJoint); 47 | camera = cameraIndex; 48 | } 49 | 50 | void NodeData::SetLight(uint32_t lightIndex) { 51 | assert(!isJoint); 52 | light = lightIndex; 53 | } 54 | 55 | json NodeData::serialize() const { 56 | json result = {{"name", name}}; 57 | 58 | // if any of the T/R/S have NaN components, just leave them out of the glTF 59 | auto maybeAdd = [&](std::string key, std::vector vec) -> void { 60 | if (std::none_of(vec.begin(), vec.end(), [&](float n) { return std::isnan(n); })) { 61 | result[key] = vec; 62 | } 63 | }; 64 | maybeAdd("translation", toStdVec(translation)); 65 | maybeAdd("rotation", toStdVec(rotation)); 66 | maybeAdd("scale", toStdVec(scale)); 67 | 68 | if (!children.empty()) { 69 | result["children"] = children; 70 | } 71 | if (isJoint) { 72 | // sanity-check joint node 73 | assert(mesh < 0 && skin < 0); 74 | } else { 75 | // non-joint node 76 | if (mesh >= 0) { 77 | result["mesh"] = mesh; 78 | } 79 | if (!skeletons.empty()) { 80 | result["skeletons"] = skeletons; 81 | } 82 | if (skin >= 0) { 83 | result["skin"] = skin; 84 | } 85 | if (camera >= 0) { 86 | result["camera"] = camera; 87 | } 88 | if (light >= 0) { 89 | result["extensions"][KHR_LIGHTS_PUNCTUAL]["light"] = light; 90 | } 91 | } 92 | 93 | for (const auto& i : userProperties) { 94 | auto& prop_map = result["extras"]["fromFBX"]["userProperties"]; 95 | 96 | json j = json::parse(i); 97 | for (const auto& k : json::iterator_wrapper(j)) { 98 | prop_map[k.key()] = k.value(); 99 | } 100 | } 101 | 102 | return result; 103 | } 104 | -------------------------------------------------------------------------------- /src/gltf/properties/NodeData.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "gltf/Raw2Gltf.hpp" 12 | 13 | struct NodeData : Holdable { 14 | NodeData( 15 | std::string name, 16 | const Vec3f& translation, 17 | const Quatf& rotation, 18 | const Vec3f& scale, 19 | bool isJoint); 20 | 21 | void AddChildNode(uint32_t childIx); 22 | void SetMesh(uint32_t meshIx); 23 | void SetSkin(uint32_t skinIx); 24 | void SetCamera(uint32_t camera); 25 | void SetLight(uint32_t light); 26 | 27 | json serialize() const override; 28 | 29 | const std::string name; 30 | const bool isJoint; 31 | Vec3f translation; 32 | Quatf rotation; 33 | Vec3f scale; 34 | std::vector children; 35 | int32_t mesh; 36 | int32_t camera; 37 | int32_t light; 38 | int32_t skin; 39 | std::vector skeletons; 40 | std::vector userProperties; 41 | }; 42 | -------------------------------------------------------------------------------- /src/gltf/properties/PrimitiveData.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #include "PrimitiveData.hpp" 10 | 11 | #include "AccessorData.hpp" 12 | #include "BufferViewData.hpp" 13 | #include "MaterialData.hpp" 14 | 15 | PrimitiveData::PrimitiveData( 16 | const AccessorData& indices, 17 | const MaterialData& material, 18 | std::shared_ptr dracoMesh) 19 | : indices(indices.ix), 20 | material(material.ix), 21 | mode(TRIANGLES), 22 | dracoMesh(dracoMesh), 23 | dracoBufferView(-1) {} 24 | 25 | PrimitiveData::PrimitiveData(const AccessorData& indices, const MaterialData& material) 26 | : indices(indices.ix), 27 | material(material.ix), 28 | mode(TRIANGLES), 29 | dracoMesh(nullptr), 30 | dracoBufferView(-1) {} 31 | 32 | void PrimitiveData::AddAttrib(std::string name, const AccessorData& accessor) { 33 | attributes[name] = accessor.ix; 34 | } 35 | 36 | void PrimitiveData::NoteDracoBuffer(const BufferViewData& data) { 37 | dracoBufferView = data.ix; 38 | } 39 | 40 | void PrimitiveData::AddTarget( 41 | const AccessorData* positions, 42 | const AccessorData* normals, 43 | const AccessorData* tangents) { 44 | targetAccessors.push_back(std::make_tuple( 45 | positions->ix, 46 | normals != nullptr ? normals->ix : -1, 47 | tangents != nullptr ? tangents->ix : -1)); 48 | targetNames.push_back(positions->name); 49 | } 50 | 51 | void to_json(json& j, const PrimitiveData& d) { 52 | j = {{"material", d.material}, {"mode", d.mode}, {"attributes", d.attributes}}; 53 | if (d.indices >= 0) { 54 | j["indices"] = d.indices; 55 | } 56 | if (!d.targetAccessors.empty()) { 57 | json targets{}; 58 | int pIx, nIx, tIx; 59 | for (auto accessor : d.targetAccessors) { 60 | std::tie(pIx, nIx, tIx) = accessor; 61 | json target{}; 62 | if (pIx >= 0) { 63 | target["POSITION"] = pIx; 64 | } 65 | if (nIx >= 0) { 66 | target["NORMAL"] = nIx; 67 | } 68 | if (tIx >= 0) { 69 | target["TANGENT"] = tIx; 70 | } 71 | targets.push_back(target); 72 | } 73 | j["targets"] = targets; 74 | } 75 | if (!d.dracoAttributes.empty()) { 76 | j["extensions"] = {{KHR_DRACO_MESH_COMPRESSION, 77 | {{"bufferView", d.dracoBufferView}, {"attributes", d.dracoAttributes}}}}; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/gltf/properties/PrimitiveData.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "gltf/Raw2Gltf.hpp" 12 | 13 | struct PrimitiveData { 14 | enum MeshMode { 15 | POINTS = 0, 16 | LINES, 17 | LINE_LOOP, 18 | LINE_STRIP, 19 | TRIANGLES, 20 | TRIANGLE_STRIP, 21 | TRIANGLE_FAN 22 | }; 23 | 24 | PrimitiveData( 25 | const AccessorData& indices, 26 | const MaterialData& material, 27 | std::shared_ptr dracoMesh); 28 | 29 | PrimitiveData(const AccessorData& indices, const MaterialData& material); 30 | 31 | void AddAttrib(std::string name, const AccessorData& accessor); 32 | 33 | void AddTarget( 34 | const AccessorData* positions, 35 | const AccessorData* normals, 36 | const AccessorData* tangents); 37 | 38 | template 39 | void AddDracoAttrib(const AttributeDefinition attribute, const std::vector& attribArr) { 40 | draco::PointAttribute att; 41 | int8_t componentCount = attribute.glType.count; 42 | att.Init( 43 | attribute.dracoAttribute, 44 | nullptr, 45 | componentCount, 46 | attribute.dracoComponentType, 47 | false, 48 | componentCount * draco::DataTypeLength(attribute.dracoComponentType), 49 | 0); 50 | 51 | const int dracoAttId = dracoMesh->AddAttribute(att, true, to_uint32(attribArr.size())); 52 | draco::PointAttribute* attPtr = dracoMesh->attribute(dracoAttId); 53 | 54 | std::vector buf(sizeof(T)); 55 | for (uint32_t ii = 0; ii < attribArr.size(); ii++) { 56 | uint8_t* ptr = &buf[0]; 57 | attribute.glType.write(ptr, attribArr[ii]); 58 | attPtr->SetAttributeValue(attPtr->mapped_index(draco::PointIndex(ii)), ptr); 59 | } 60 | 61 | dracoAttributes[attribute.gltfName] = dracoAttId; 62 | } 63 | 64 | void NoteDracoBuffer(const BufferViewData& data); 65 | 66 | const int indices; 67 | const unsigned int material; 68 | const MeshMode mode; 69 | 70 | std::vector> targetAccessors{}; 71 | std::vector targetNames{}; 72 | 73 | std::map attributes; 74 | std::map dracoAttributes; 75 | 76 | std::shared_ptr dracoMesh; 77 | int dracoBufferView; 78 | }; 79 | 80 | void to_json(json& j, const PrimitiveData& d); 81 | -------------------------------------------------------------------------------- /src/gltf/properties/SamplerData.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "gltf/Raw2Gltf.hpp" 12 | 13 | struct SamplerData : Holdable { 14 | // this is where magFilter, minFilter, wrapS and wrapT would go, should we want it 15 | SamplerData() : Holdable() {} 16 | 17 | json serialize() const override { 18 | return json::object(); 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /src/gltf/properties/SceneData.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #include "SceneData.hpp" 10 | 11 | #include "NodeData.hpp" 12 | 13 | SceneData::SceneData(std::string name, const NodeData& rootNode) 14 | : Holdable(), name(std::move(name)), nodes({rootNode.ix}) {} 15 | 16 | json SceneData::serialize() const { 17 | assert(nodes.size() <= 1); 18 | return {{"name", name}, {"nodes", nodes}}; 19 | } 20 | -------------------------------------------------------------------------------- /src/gltf/properties/SceneData.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "gltf/Raw2Gltf.hpp" 12 | 13 | struct SceneData : Holdable { 14 | SceneData(std::string name, const NodeData& rootNode); 15 | 16 | json serialize() const override; 17 | 18 | const std::string name; 19 | std::vector nodes; 20 | }; 21 | -------------------------------------------------------------------------------- /src/gltf/properties/SkinData.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #include "SkinData.hpp" 10 | 11 | #include "AccessorData.hpp" 12 | #include "NodeData.hpp" 13 | 14 | SkinData::SkinData( 15 | const std::vector joints, 16 | const AccessorData& inverseBindMatricesAccessor, 17 | const NodeData& skeletonRootNode) 18 | : Holdable(), 19 | joints(joints), 20 | inverseBindMatrices(inverseBindMatricesAccessor.ix), 21 | skeletonRootNode(skeletonRootNode.ix) {} 22 | 23 | json SkinData::serialize() const { 24 | return {{"joints", joints}, 25 | {"inverseBindMatrices", inverseBindMatrices}, 26 | {"skeleton", skeletonRootNode}}; 27 | } 28 | -------------------------------------------------------------------------------- /src/gltf/properties/SkinData.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "gltf/Raw2Gltf.hpp" 12 | 13 | struct SkinData : Holdable { 14 | SkinData( 15 | const std::vector joints, 16 | const AccessorData& inverseBindMatricesAccessor, 17 | const NodeData& skeletonRootNode); 18 | 19 | json serialize() const override; 20 | 21 | const std::vector joints; 22 | const uint32_t skeletonRootNode; 23 | const uint32_t inverseBindMatrices; 24 | }; 25 | -------------------------------------------------------------------------------- /src/gltf/properties/TextureData.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #include "TextureData.hpp" 10 | 11 | #include "ImageData.hpp" 12 | #include "SamplerData.hpp" 13 | 14 | TextureData::TextureData(std::string name, const SamplerData& sampler, const ImageData& source) 15 | : Holdable(), name(std::move(name)), sampler(sampler.ix), source(source.ix) {} 16 | 17 | json TextureData::serialize() const { 18 | return {{"name", name}, {"sampler", sampler}, {"source", source}}; 19 | } 20 | -------------------------------------------------------------------------------- /src/gltf/properties/TextureData.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "gltf/Raw2Gltf.hpp" 12 | 13 | struct TextureData : Holdable { 14 | TextureData(std::string name, const SamplerData& sampler, const ImageData& source); 15 | 16 | json serialize() const override; 17 | 18 | const std::string name; 19 | const uint32_t sampler; 20 | const uint32_t source; 21 | }; 22 | -------------------------------------------------------------------------------- /src/mathfu.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | /** 21 | * All the mathfu:: implementations of our core data types. 22 | */ 23 | 24 | template 25 | struct Bounds { 26 | mathfu::Vector min; 27 | mathfu::Vector max; 28 | bool initialized = false; 29 | 30 | void Clear() { 31 | min = mathfu::Vector(); 32 | max = mathfu::Vector(); 33 | initialized = false; 34 | } 35 | 36 | void AddPoint(const mathfu::Vector& p) { 37 | if (initialized) { 38 | for (int ii = 0; ii < d; ii++) { 39 | min(ii) = std::min(min(ii), p(ii)); 40 | max(ii) = std::max(max(ii), p(ii)); 41 | } 42 | } else { 43 | min = p; 44 | max = p; 45 | initialized = true; 46 | } 47 | } 48 | }; 49 | 50 | typedef mathfu::Vector Vec4i; 51 | typedef mathfu::Matrix Mat4i; 52 | typedef mathfu::Vector Vec2f; 53 | typedef mathfu::Vector Vec3f; 54 | typedef mathfu::Vector Vec4f; 55 | typedef mathfu::Matrix Mat2f; 56 | typedef mathfu::Matrix Mat3f; 57 | typedef mathfu::Matrix Mat4f; 58 | typedef mathfu::Quaternion Quatf; 59 | typedef Bounds Boundsf; 60 | 61 | #define VEC3F_ONE (Vec3f{1.0f}) 62 | #define VEC3F_ZERO (Vec3f{0.0f}) 63 | #define VEC4F_ONE (Vec4f{1.0f}) 64 | #define VEC4F_ZERO (Vec4f{0.0f}) 65 | 66 | template 67 | inline std::vector toStdVec(const mathfu::Vector& vec) { 68 | std::vector result(d); 69 | for (int ii = 0; ii < d; ii++) { 70 | result[ii] = vec[ii]; 71 | } 72 | return result; 73 | } 74 | 75 | template 76 | std::vector toStdVec(const mathfu::Quaternion& quat) { 77 | return std::vector{quat.vector()[0], quat.vector()[1], quat.vector()[2], quat.scalar()}; 78 | } 79 | 80 | inline Vec3f toVec3f(const FbxDouble3& v) { 81 | return Vec3f((float)v[0], (float)v[1], (float)v[2]); 82 | } 83 | 84 | inline Vec3f toVec3f(const FbxVector4& v) { 85 | return Vec3f((float)v[0], (float)v[1], (float)v[2]); 86 | } 87 | 88 | inline Vec4f toVec4f(const FbxVector4& v) { 89 | return Vec4f((float)v[0], (float)v[1], (float)v[2], (float)v[3]); 90 | } 91 | 92 | inline Mat4f toMat4f(const FbxAMatrix& m) { 93 | auto result = Mat4f(); 94 | for (int row = 0; row < 4; row++) { 95 | for (int col = 0; col < 4; col++) { 96 | result(row, col) = (float)m[row][col]; 97 | } 98 | } 99 | return result; 100 | } 101 | 102 | inline Quatf toQuatf(const FbxQuaternion& q) { 103 | return Quatf((float)q[3], (float)q[0], (float)q[1], (float)q[2]); 104 | } 105 | -------------------------------------------------------------------------------- /src/raw/RawModel.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include "FBX2glTF.h" 16 | 17 | enum RawVertexAttribute { 18 | RAW_VERTEX_ATTRIBUTE_POSITION = 1 << 0, 19 | RAW_VERTEX_ATTRIBUTE_NORMAL = 1 << 1, 20 | RAW_VERTEX_ATTRIBUTE_TANGENT = 1 << 2, 21 | RAW_VERTEX_ATTRIBUTE_BINORMAL = 1 << 3, 22 | RAW_VERTEX_ATTRIBUTE_COLOR = 1 << 4, 23 | RAW_VERTEX_ATTRIBUTE_UV0 = 1 << 5, 24 | RAW_VERTEX_ATTRIBUTE_UV1 = 1 << 6, 25 | RAW_VERTEX_ATTRIBUTE_JOINT_INDICES = 1 << 7, 26 | RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS = 1 << 8, 27 | 28 | RAW_VERTEX_ATTRIBUTE_AUTO = 1 << 31 29 | }; 30 | 31 | struct RawBlendVertex { 32 | Vec3f position{0.0f}; 33 | Vec3f normal{0.0f}; 34 | Vec4f tangent{0.0f}; 35 | 36 | bool operator==(const RawBlendVertex& other) const { 37 | return position == other.position && normal == other.normal && tangent == other.tangent; 38 | } 39 | }; 40 | 41 | struct RawVertex { 42 | Vec3f position{0.0f}; 43 | Vec3f normal{0.0f}; 44 | Vec3f binormal{0.0f}; 45 | Vec4f tangent{0.0f}; 46 | Vec4f color{0.0f}; 47 | Vec2f uv0{0.0f}; 48 | Vec2f uv1{0.0f}; 49 | Vec4i jointIndices{0, 0, 0, 0}; 50 | Vec4f jointWeights{0.0f}; 51 | // end of members that directly correspond to vertex attributes 52 | 53 | // if this vertex participates in a blend shape setup, the surfaceIx of its dedicated mesh; 54 | // otherwise, -1 55 | int blendSurfaceIx = -1; 56 | // the size of this vector is always identical to the size of the corresponding 57 | // RawSurface.blendChannels 58 | std::vector blends; 59 | 60 | bool polarityUv0 = false; 61 | bool pad1 = false; 62 | bool pad2 = false; 63 | bool pad3 = false; 64 | 65 | bool operator==(const RawVertex& other) const; 66 | size_t Difference(const RawVertex& other) const; 67 | }; 68 | 69 | class VertexHasher { 70 | public: 71 | size_t operator()(const RawVertex& v) const; 72 | }; 73 | 74 | struct RawTriangle { 75 | int verts[3]; 76 | int materialIndex; 77 | int surfaceIndex; 78 | }; 79 | 80 | enum RawShadingModel { 81 | RAW_SHADING_MODEL_UNKNOWN = -1, 82 | RAW_SHADING_MODEL_CONSTANT, 83 | RAW_SHADING_MODEL_LAMBERT, 84 | RAW_SHADING_MODEL_BLINN, 85 | RAW_SHADING_MODEL_PHONG, 86 | RAW_SHADING_MODEL_PBR_MET_ROUGH, 87 | RAW_SHADING_MODEL_MAX 88 | }; 89 | 90 | inline std::string Describe(RawShadingModel model) { 91 | switch (model) { 92 | case RAW_SHADING_MODEL_UNKNOWN: 93 | return ""; 94 | case RAW_SHADING_MODEL_CONSTANT: 95 | return "Constant"; 96 | case RAW_SHADING_MODEL_LAMBERT: 97 | return "Lambert"; 98 | case RAW_SHADING_MODEL_BLINN: 99 | return "Blinn"; 100 | case RAW_SHADING_MODEL_PHONG: 101 | return "Phong"; 102 | case RAW_SHADING_MODEL_PBR_MET_ROUGH: 103 | return "Metallic/Roughness"; 104 | case RAW_SHADING_MODEL_MAX: 105 | default: 106 | return ""; 107 | } 108 | } 109 | 110 | enum RawTextureUsage { 111 | RAW_TEXTURE_USAGE_NONE = -1, 112 | RAW_TEXTURE_USAGE_AMBIENT, 113 | RAW_TEXTURE_USAGE_DIFFUSE, 114 | RAW_TEXTURE_USAGE_NORMAL, 115 | RAW_TEXTURE_USAGE_SPECULAR, 116 | RAW_TEXTURE_USAGE_SHININESS, 117 | RAW_TEXTURE_USAGE_EMISSIVE, 118 | RAW_TEXTURE_USAGE_REFLECTION, 119 | RAW_TEXTURE_USAGE_ALBEDO, 120 | RAW_TEXTURE_USAGE_OCCLUSION, 121 | RAW_TEXTURE_USAGE_ROUGHNESS, 122 | RAW_TEXTURE_USAGE_METALLIC, 123 | RAW_TEXTURE_USAGE_MAX 124 | }; 125 | 126 | inline std::string Describe(RawTextureUsage usage) { 127 | switch (usage) { 128 | case RAW_TEXTURE_USAGE_NONE: 129 | return ""; 130 | case RAW_TEXTURE_USAGE_AMBIENT: 131 | return "ambient"; 132 | case RAW_TEXTURE_USAGE_DIFFUSE: 133 | return "diffuse"; 134 | case RAW_TEXTURE_USAGE_NORMAL: 135 | return "normal"; 136 | case RAW_TEXTURE_USAGE_SPECULAR: 137 | return "specular"; 138 | case RAW_TEXTURE_USAGE_SHININESS: 139 | return "shininess"; 140 | case RAW_TEXTURE_USAGE_EMISSIVE: 141 | return "emissive"; 142 | case RAW_TEXTURE_USAGE_REFLECTION: 143 | return "reflection"; 144 | case RAW_TEXTURE_USAGE_OCCLUSION: 145 | return "occlusion"; 146 | case RAW_TEXTURE_USAGE_ROUGHNESS: 147 | return "roughness"; 148 | case RAW_TEXTURE_USAGE_METALLIC: 149 | return "metallic"; 150 | case RAW_TEXTURE_USAGE_MAX: 151 | default: 152 | return "unknown"; 153 | } 154 | }; 155 | 156 | enum RawTextureOcclusion { RAW_TEXTURE_OCCLUSION_OPAQUE, RAW_TEXTURE_OCCLUSION_TRANSPARENT }; 157 | 158 | struct RawTexture { 159 | std::string name; // logical name in FBX file 160 | int width; 161 | int height; 162 | int mipLevels; 163 | RawTextureUsage usage; 164 | RawTextureOcclusion occlusion; 165 | std::string fileName; // original filename in FBX file 166 | std::string fileLocation; // inferred path in local filesystem, or "" 167 | }; 168 | 169 | enum RawMaterialType { 170 | RAW_MATERIAL_TYPE_OPAQUE, 171 | RAW_MATERIAL_TYPE_TRANSPARENT, 172 | RAW_MATERIAL_TYPE_SKINNED_OPAQUE, 173 | RAW_MATERIAL_TYPE_SKINNED_TRANSPARENT, 174 | }; 175 | 176 | struct RawMatProps { 177 | explicit RawMatProps(RawShadingModel shadingModel) : shadingModel(shadingModel) {} 178 | const RawShadingModel shadingModel; 179 | 180 | virtual bool operator!=(const RawMatProps& other) const { 181 | return !(*this == other); 182 | } 183 | virtual bool operator==(const RawMatProps& other) const { 184 | return shadingModel == other.shadingModel; 185 | }; 186 | }; 187 | 188 | struct RawTraditionalMatProps : RawMatProps { 189 | RawTraditionalMatProps( 190 | RawShadingModel shadingModel, 191 | const Vec3f&& ambientFactor, 192 | const Vec4f&& diffuseFactor, 193 | const Vec3f&& emissiveFactor, 194 | const Vec3f&& specularFactor, 195 | const float shininess) 196 | : RawMatProps(shadingModel), 197 | ambientFactor(ambientFactor), 198 | diffuseFactor(diffuseFactor), 199 | emissiveFactor(emissiveFactor), 200 | specularFactor(specularFactor), 201 | shininess(shininess) {} 202 | 203 | const Vec3f ambientFactor; 204 | const Vec4f diffuseFactor; 205 | const Vec3f emissiveFactor; 206 | const Vec3f specularFactor; 207 | const float shininess; 208 | 209 | bool operator==(const RawMatProps& other) const override { 210 | if (RawMatProps::operator==(other)) { 211 | const auto& typed = (RawTraditionalMatProps&)other; 212 | return ambientFactor == typed.ambientFactor && diffuseFactor == typed.diffuseFactor && 213 | specularFactor == typed.specularFactor && emissiveFactor == typed.emissiveFactor && 214 | shininess == typed.shininess; 215 | } 216 | return false; 217 | } 218 | }; 219 | 220 | struct RawMetRoughMatProps : RawMatProps { 221 | RawMetRoughMatProps( 222 | RawShadingModel shadingModel, 223 | const Vec4f&& diffuseFactor, 224 | const Vec3f&& emissiveFactor, 225 | float emissiveIntensity, 226 | float metallic, 227 | float roughness, 228 | bool invertRoughnessMap) 229 | : RawMatProps(shadingModel), 230 | diffuseFactor(diffuseFactor), 231 | emissiveFactor(emissiveFactor), 232 | emissiveIntensity(emissiveIntensity), 233 | metallic(metallic), 234 | roughness(roughness), 235 | invertRoughnessMap(invertRoughnessMap) {} 236 | const Vec4f diffuseFactor; 237 | const Vec3f emissiveFactor; 238 | const float emissiveIntensity; 239 | const float metallic; 240 | const float roughness; 241 | const bool invertRoughnessMap; 242 | 243 | bool operator==(const RawMatProps& other) const override { 244 | if (RawMatProps::operator==(other)) { 245 | const auto& typed = (RawMetRoughMatProps&)other; 246 | return diffuseFactor == typed.diffuseFactor && emissiveFactor == typed.emissiveFactor && 247 | emissiveIntensity == typed.emissiveIntensity && metallic == typed.metallic && 248 | roughness == typed.roughness; 249 | } 250 | return false; 251 | } 252 | }; 253 | 254 | struct RawMaterial { 255 | long id; 256 | std::string name; 257 | RawMaterialType type; 258 | std::shared_ptr info; 259 | int textures[RAW_TEXTURE_USAGE_MAX]; 260 | std::vector userProperties; 261 | }; 262 | 263 | enum RawLightType { 264 | RAW_LIGHT_TYPE_DIRECTIONAL, 265 | RAW_LIGHT_TYPE_POINT, 266 | RAW_LIGHT_TYPE_SPOT, 267 | }; 268 | 269 | struct RawLight { 270 | std::string name; 271 | RawLightType type; 272 | Vec3f color; 273 | float intensity; 274 | float innerConeAngle; // only meaningful for spot 275 | float outerConeAngle; // only meaningful for spot 276 | }; 277 | 278 | struct RawBlendChannel { 279 | float defaultDeform; 280 | bool hasNormals; 281 | bool hasTangents; 282 | std::string name; 283 | }; 284 | 285 | struct RawSurface { 286 | long id; 287 | std::string name; // The name of this surface 288 | long skeletonRootId; // The id of the root node of the skeleton. 289 | Bounds bounds; 290 | std::vector jointIds; 291 | std::vector jointGeometryMins; 292 | std::vector jointGeometryMaxs; 293 | std::vector inverseBindMatrices; 294 | std::vector blendChannels; 295 | bool discrete; 296 | }; 297 | 298 | struct RawChannel { 299 | int nodeIndex; 300 | std::vector translations; 301 | std::vector rotations; 302 | std::vector scales; 303 | std::vector weights; 304 | }; 305 | 306 | struct RawAnimation { 307 | std::string name; 308 | std::vector times; 309 | std::vector channels; 310 | }; 311 | 312 | struct RawCamera { 313 | std::string name; 314 | long nodeId; 315 | 316 | enum { CAMERA_MODE_PERSPECTIVE, CAMERA_MODE_ORTHOGRAPHIC } mode; 317 | 318 | struct { 319 | float aspectRatio; 320 | float fovDegreesX; 321 | float fovDegreesY; 322 | float nearZ; 323 | float farZ; 324 | } perspective; 325 | 326 | struct { 327 | float magX; 328 | float magY; 329 | float nearZ; 330 | float farZ; 331 | } orthographic; 332 | }; 333 | 334 | struct RawNode { 335 | bool isJoint; 336 | long id; 337 | std::string name; 338 | long parentId; 339 | std::vector childIds; 340 | Vec3f translation; 341 | Quatf rotation; 342 | Vec3f scale; 343 | long surfaceId; 344 | long lightIx; 345 | std::vector userProperties; 346 | }; 347 | 348 | class RawModel { 349 | public: 350 | RawModel(); 351 | 352 | // Add geometry. 353 | void AddVertexAttribute(const RawVertexAttribute attrib); 354 | int AddVertex(const RawVertex& vertex); 355 | int AddTriangle( 356 | const int v0, 357 | const int v1, 358 | const int v2, 359 | const int materialIndex, 360 | const int surfaceIndex); 361 | int AddTexture( 362 | const std::string& name, 363 | const std::string& fileName, 364 | const std::string& fileLocation, 365 | RawTextureUsage usage); 366 | int AddMaterial(const RawMaterial& material); 367 | int AddMaterial( 368 | const long id, 369 | const char* name, 370 | const RawMaterialType materialType, 371 | const int textures[RAW_TEXTURE_USAGE_MAX], 372 | std::shared_ptr materialInfo, 373 | const std::vector& userProperties); 374 | int AddLight( 375 | const char* name, 376 | RawLightType lightType, 377 | Vec3f color, 378 | float intensity, 379 | float innerConeAngle, 380 | float outerConeAngle); 381 | int AddSurface(const RawSurface& suface); 382 | int AddSurface(const char* name, long surfaceId); 383 | int AddAnimation(const RawAnimation& animation); 384 | int AddCameraPerspective( 385 | const char* name, 386 | const long nodeId, 387 | const float aspectRatio, 388 | const float fovDegreesX, 389 | const float fovDegreesY, 390 | const float nearZ, 391 | const float farZ); 392 | int AddCameraOrthographic( 393 | const char* name, 394 | const long nodeId, 395 | const float magX, 396 | const float magY, 397 | const float nearZ, 398 | const float farZ); 399 | int AddNode(const RawNode& node); 400 | int AddNode(const long id, const char* name, const long parentId); 401 | void SetRootNode(const long nodeId) { 402 | rootNodeId = nodeId; 403 | } 404 | const long GetRootNode() const { 405 | return rootNodeId; 406 | } 407 | 408 | // Remove unused vertices, textures or materials after removing vertex attributes, textures, 409 | // materials or surfaces. 410 | void Condense(); 411 | 412 | void TransformGeometry(ComputeNormalsOption); 413 | 414 | void TransformTextures(const std::vector>& transforms); 415 | 416 | size_t CalculateNormals(bool); 417 | 418 | // Get the attributes stored per vertex. 419 | int GetVertexAttributes() const { 420 | return vertexAttributes; 421 | } 422 | 423 | // Iterate over the vertices. 424 | int GetVertexCount() const { 425 | return (int)vertices.size(); 426 | } 427 | const RawVertex& GetVertex(const int index) const { 428 | return vertices[index]; 429 | } 430 | 431 | // Iterate over the triangles. 432 | int GetTriangleCount() const { 433 | return (int)triangles.size(); 434 | } 435 | const RawTriangle& GetTriangle(const int index) const { 436 | return triangles[index]; 437 | } 438 | 439 | // Iterate over the textures. 440 | int GetTextureCount() const { 441 | return (int)textures.size(); 442 | } 443 | const RawTexture& GetTexture(const int index) const { 444 | return textures[index]; 445 | } 446 | 447 | // Iterate over the materials. 448 | int GetMaterialCount() const { 449 | return (int)materials.size(); 450 | } 451 | const RawMaterial& GetMaterial(const int index) const { 452 | return materials[index]; 453 | } 454 | 455 | // Iterate over the surfaces. 456 | int GetSurfaceCount() const { 457 | return (int)surfaces.size(); 458 | } 459 | const RawSurface& GetSurface(const int index) const { 460 | return surfaces[index]; 461 | } 462 | RawSurface& GetSurface(const int index) { 463 | return surfaces[index]; 464 | } 465 | int GetSurfaceById(const long id) const; 466 | 467 | // Iterate over the animations. 468 | int GetAnimationCount() const { 469 | return (int)animations.size(); 470 | } 471 | const RawAnimation& GetAnimation(const int index) const { 472 | return animations[index]; 473 | } 474 | 475 | // Iterate over the cameras. 476 | int GetCameraCount() const { 477 | return (int)cameras.size(); 478 | } 479 | const RawCamera& GetCamera(const int index) const { 480 | return cameras[index]; 481 | } 482 | 483 | // Iterate over the lights. 484 | int GetLightCount() const { 485 | return (int)lights.size(); 486 | } 487 | const RawLight& GetLight(const int index) const { 488 | return lights[index]; 489 | } 490 | 491 | // Iterate over the nodes. 492 | int GetNodeCount() const { 493 | return (int)nodes.size(); 494 | } 495 | const RawNode& GetNode(const int index) const { 496 | return nodes[index]; 497 | } 498 | RawNode& GetNode(const int index) { 499 | return nodes[index]; 500 | } 501 | int GetNodeById(const long nodeId) const; 502 | 503 | // Create individual attribute arrays. 504 | // Returns true if the vertices store the particular attribute. 505 | template 506 | void GetAttributeArray(std::vector<_attrib_type_>& out, const _attrib_type_ RawVertex::*ptr) 507 | const; 508 | 509 | // Create an array with a raw model for each material. 510 | // Multiple surfaces with the same material will turn into a single model. 511 | // However, surfaces that are marked as 'discrete' will turn into separate models. 512 | void CreateMaterialModels( 513 | std::vector& materialModels, 514 | bool shortIndices, 515 | const int keepAttribs, 516 | const bool forceDiscrete) const; 517 | 518 | private: 519 | Vec3f getFaceNormal(int verts[3]) const; 520 | 521 | long rootNodeId; 522 | int vertexAttributes; 523 | std::unordered_map vertexHash; 524 | std::vector vertices; 525 | std::vector triangles; 526 | std::vector textures; 527 | std::vector materials; 528 | std::vector lights; 529 | std::vector surfaces; 530 | std::vector animations; 531 | std::vector cameras; 532 | std::vector nodes; 533 | }; 534 | 535 | template 536 | void RawModel::GetAttributeArray( 537 | std::vector<_attrib_type_>& out, 538 | const _attrib_type_ RawVertex::*ptr) const { 539 | out.resize(vertices.size()); 540 | for (size_t i = 0; i < vertices.size(); i++) { 541 | out[i] = vertices[i].*ptr; 542 | } 543 | } 544 | -------------------------------------------------------------------------------- /src/utils/File_Utils.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #include "File_Utils.hpp" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | 19 | #include "FBX2glTF.h" 20 | #include "String_Utils.hpp" 21 | 22 | namespace FileUtils { 23 | 24 | std::vector ListFolderFiles( 25 | std::string folder, 26 | const std::set& matchExtensions) { 27 | std::vector fileList; 28 | if (folder.empty()) { 29 | folder = "."; 30 | } 31 | for (const auto& entry : boost::filesystem::directory_iterator(folder)) { 32 | const auto& suffix = FileUtils::GetFileSuffix(entry.path().string()); 33 | if (suffix.has_value()) { 34 | const auto& suffix_str = StringUtils::ToLower(suffix.value()); 35 | if (matchExtensions.find(suffix_str) != matchExtensions.end()) { 36 | fileList.push_back(entry.path().filename().string()); 37 | } 38 | } 39 | } 40 | return fileList; 41 | } 42 | 43 | bool CreatePath(const std::string path) { 44 | const auto& parent = boost::filesystem::path(path).parent_path(); 45 | if (parent.empty()) { 46 | // this is either CWD or boost::filesystem root; either way it exists 47 | return true; 48 | } 49 | if (boost::filesystem::exists(parent)) { 50 | return boost::filesystem::is_directory(parent); 51 | } 52 | return boost::filesystem::create_directory(parent); 53 | } 54 | 55 | bool CopyFile(const std::string& srcFilename, const std::string& dstFilename, bool createPath) { 56 | std::ifstream srcFile(srcFilename, std::ios::binary); 57 | if (!srcFile) { 58 | fmt::printf("Warning: Couldn't open file %s for reading.\n", srcFilename); 59 | return false; 60 | } 61 | // find source file length 62 | srcFile.seekg(0, std::ios::end); 63 | std::streamsize srcSize = srcFile.tellg(); 64 | srcFile.seekg(0, std::ios::beg); 65 | 66 | if (createPath && !CreatePath(dstFilename.c_str())) { 67 | fmt::printf("Warning: Couldn't create directory %s.\n", dstFilename); 68 | return false; 69 | } 70 | 71 | std::ofstream dstFile(dstFilename, std::ios::binary | std::ios::trunc); 72 | if (!dstFile) { 73 | fmt::printf("Warning: Couldn't open file %s for writing.\n", dstFilename); 74 | return false; 75 | } 76 | dstFile << srcFile.rdbuf(); 77 | std::streamsize dstSize = dstFile.tellp(); 78 | if (srcSize == dstSize) { 79 | return true; 80 | } 81 | fmt::printf( 82 | "Warning: Only copied %lu bytes to %s, when %s is %lu bytes long.\n", 83 | dstSize, 84 | dstFilename, 85 | srcFilename, 86 | srcSize); 87 | return false; 88 | } 89 | } // namespace FileUtils 90 | -------------------------------------------------------------------------------- /src/utils/File_Utils.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | 18 | namespace FileUtils { 19 | 20 | std::string GetCurrentFolder(); 21 | 22 | bool FileExists(const std::string& folderPath); 23 | bool FolderExists(const std::string& folderPath); 24 | 25 | std::vector ListFolderFiles( 26 | const std::string folder, 27 | const std::set& matchExtensions); 28 | 29 | bool CreatePath(std::string path); 30 | 31 | bool CopyFile( 32 | const std::string& srcFilename, 33 | const std::string& dstFilename, 34 | bool createPath = false); 35 | 36 | inline std::string GetAbsolutePath(const std::string& filePath) { 37 | return boost::filesystem::absolute(filePath).string(); 38 | } 39 | 40 | inline std::string GetCurrentFolder() { 41 | return boost::filesystem::current_path().string(); 42 | } 43 | 44 | inline bool FileExists(const std::string& filePath) { 45 | return boost::filesystem::exists(filePath) && boost::filesystem::is_regular_file(filePath); 46 | } 47 | 48 | inline bool FolderExists(const std::string& folderPath) { 49 | return boost::filesystem::exists(folderPath) && boost::filesystem::is_directory(folderPath); 50 | } 51 | 52 | inline std::string getFolder(const std::string& path) { 53 | return boost::filesystem::path(path).parent_path().string(); 54 | } 55 | 56 | inline std::string GetFileName(const std::string& path) { 57 | return boost::filesystem::path(path).filename().string(); 58 | } 59 | 60 | inline std::string GetFileBase(const std::string& path) { 61 | return boost::filesystem::path(path).stem().string(); 62 | } 63 | 64 | inline boost::optional GetFileSuffix(const std::string& path) { 65 | const auto& extension = boost::filesystem::path(path).extension(); 66 | if (extension.empty()) { 67 | return boost::none; 68 | } 69 | return extension.string().substr(1); 70 | } 71 | 72 | } // namespace FileUtils 73 | -------------------------------------------------------------------------------- /src/utils/Image_Utils.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #include "Image_Utils.hpp" 10 | 11 | #include 12 | #include 13 | 14 | #define STB_IMAGE_IMPLEMENTATION 15 | 16 | #include 17 | 18 | #define STB_IMAGE_WRITE_IMPLEMENTATION 19 | 20 | #include 21 | 22 | namespace ImageUtils { 23 | 24 | static bool imageHasTransparentPixels(FILE* f) { 25 | int width, height, channels; 26 | // RGBA: we have to load the pixels to figure out if the image is fully opaque 27 | uint8_t* pixels = stbi_load_from_file(f, &width, &height, &channels, 0); 28 | if (pixels != nullptr) { 29 | int pixelCount = width * height; 30 | for (int ix = 0; ix < pixelCount; ix++) { 31 | // test fourth byte (alpha); 255 is 1.0 32 | if (pixels[4 * ix + 3] != 255) { 33 | return true; 34 | } 35 | } 36 | } 37 | return false; 38 | } 39 | 40 | ImageProperties GetImageProperties(char const* filePath) { 41 | ImageProperties result = { 42 | 1, 43 | 1, 44 | IMAGE_OPAQUE, 45 | }; 46 | 47 | FILE* f = fopen(filePath, "rb"); 48 | if (f == nullptr) { 49 | return result; 50 | } 51 | 52 | int channels; 53 | int success = stbi_info_from_file(f, &result.width, &result.height, &channels); 54 | 55 | if (success && channels == 4 && imageHasTransparentPixels(f)) { 56 | result.occlusion = IMAGE_TRANSPARENT; 57 | } 58 | return result; 59 | } 60 | 61 | std::string suffixToMimeType(std::string suffix) { 62 | std::transform(suffix.begin(), suffix.end(), suffix.begin(), ::tolower); 63 | 64 | if (suffix == "jpg" || suffix == "jpeg") { 65 | return "image/jpeg"; 66 | } 67 | if (suffix == "png") { 68 | return "image/png"; 69 | } 70 | return "image/unknown"; 71 | } 72 | 73 | } // namespace ImageUtils 74 | -------------------------------------------------------------------------------- /src/utils/Image_Utils.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | 13 | namespace ImageUtils { 14 | 15 | enum ImageOcclusion { IMAGE_OPAQUE, IMAGE_TRANSPARENT }; 16 | 17 | struct ImageProperties { 18 | int width; 19 | int height; 20 | ImageOcclusion occlusion; 21 | }; 22 | 23 | ImageProperties GetImageProperties(char const* filePath); 24 | 25 | /** 26 | * Very simple method for mapping filename suffix to mime type. The glTF 2.0 spec only accepts 27 | * values "image/jpeg" and "image/png" so we don't need to get too fancy. 28 | */ 29 | std::string suffixToMimeType(std::string suffix); 30 | 31 | } // namespace ImageUtils 32 | -------------------------------------------------------------------------------- /src/utils/String_Utils.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #if defined(_MSC_VER) 19 | #define strncasecmp _strnicmp 20 | #endif 21 | 22 | namespace StringUtils { 23 | 24 | inline std::string ToLower(std::string s) { 25 | std::transform(s.begin(), s.end(), s.begin(), [](uint8_t c) { return std::tolower(c); }); 26 | return s; 27 | } 28 | 29 | inline int CompareNoCase(const std::string& s1, const std::string& s2) { 30 | return strncasecmp(s1.c_str(), s2.c_str(), std::max(s1.length(), s2.length())); 31 | } 32 | 33 | } // namespace StringUtils 34 | --------------------------------------------------------------------------------