├── .clang-format ├── .dockerignore ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── config.yml └── workflows │ └── build.yaml ├── .gitignore ├── CMakeLists.txt ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── FindFBX.cmake ├── LICENSE ├── README.md ├── conanfile.py ├── 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 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Report a bug in Godot's FBX2glTF fork 3 | body: 4 | 5 | - type: markdown 6 | attributes: 7 | value: | 8 | - When reporting bugs, you'll make our life simpler (and the fix will come sooner) if you follow the guidelines in this template. 9 | - Write a descriptive issue title above. 10 | - The golden rule is to **always open *one* issue for *one* bug**. If you notice several bugs and want to report them, make sure to create one new issue for each of them. 11 | - Search [open](https://github.com/godotengine/FBX2glTF/issues) and [closed](https://github.com/godotengine/FBX2glTF/issues?q=is%3Aissue+is%3Aclosed) issues to ensure it has not already been reported. If you don't find a relevant match or if you're unsure, don't hesitate to **open a new issue**. The bugsquad will handle it from there if it's a duplicate. 12 | - Verify that you are using a [supported Godot version](https://docs.godotengine.org/en/stable/about/release_policy.html). 13 | 14 | - type: input 15 | attributes: 16 | label: Godot version 17 | description: > 18 | Specify the Git commit hash if using a development or non-official build. 19 | If you use a custom build, please test if your issue is reproducible in official builds too. 20 | placeholder: 3.3.stable, 4.0.dev (3041becc6) 21 | validations: 22 | required: true 23 | 24 | - type: input 25 | attributes: 26 | label: System information 27 | description: | 28 | - Specify the OS version, and when relevant hardware information. 29 | - For issues that are likely OS-specific, please specify the CPU model and architecture. 30 | - **Bug reports not including the required information may be closed at the maintainers' discretion.** If in doubt, always include all the requested information; it's better to include too much information than not enough information. 31 | placeholder: Windows 10, Intel Core i5-7200U 32 | validations: 33 | required: true 34 | 35 | - type: textarea 36 | attributes: 37 | label: Issue description 38 | description: | 39 | Describe your issue briefly. What doesn't work, and how do you expect it to work instead? 40 | You can include images or videos with drag and drop, and format code blocks or logs with ``` tags. 41 | validations: 42 | required: true 43 | 44 | - type: textarea 45 | attributes: 46 | label: Steps to reproduce 47 | description: | 48 | List of steps or sample code that reproduces the issue. Having reproducible issues is a prerequisite for contributors to be able to solve them. 49 | If you include a minimal reproduction project below, you can detail how to use it here. 50 | validations: 51 | required: true 52 | 53 | - type: textarea 54 | attributes: 55 | label: Minimal reproduction project 56 | description: | 57 | - A small Godot project which reproduces the issue, with no unnecessary files included. Be sure to not include the `.godot` folder in the archive (but keep `project.godot`). 58 | - Required, unless the reproduction steps are trivial and don't require any project files to be followed. In this case, write "N/A" in the field. 59 | - Drag and drop a ZIP archive to upload it. **Do not select another field until the project is done uploading.** 60 | - **Note for C# users:** If your issue is *not* Mono-specific, please upload a minimal reproduction project written in GDScript or VisualScript. This will make it easier for contributors to reproduce the issue locally as not everyone has a Mono setup available. 61 | - **If you've been asked by a maintainer to upload a minimal reproduction project, you *must* do so within 7 days.** Otherwise, your bug report will be closed as it'll be considered too difficult to diagnose. 62 | validations: 63 | required: true 64 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | 3 | contact_links: 4 | - name: Godot proposals 5 | url: https://github.com/godotengine/godot-proposals 6 | about: Please submit feature proposals on the Godot proposals repository, not here. 7 | 8 | - name: Godot documentation repository 9 | url: https://github.com/godotengine/godot-docs 10 | about: Please report issues with documentation on the Godot documentation repository, not here. 11 | 12 | - name: Godot community channels 13 | url: https://godotengine.org/community 14 | about: Please ask for technical support on one of the other community channels, not here. 15 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: "Build FBX2glTF" 2 | on: 3 | pull_request: 4 | branches: 5 | - master 6 | push: 7 | branches: 8 | - master 9 | tags: 10 | - "v*" 11 | 12 | concurrency: 13 | group: ci-${{github.actor}}-${{github.head_ref || github.run_number}}-${{github.ref}}-build 14 | cancel-in-progress: true 15 | 16 | jobs: 17 | build-windows: 18 | runs-on: windows-2022 19 | steps: 20 | - name: Checkout 21 | uses: actions/checkout@v4 22 | 23 | - name: Install conan 24 | run: | 25 | pip install --upgrade conan==1.63.0 26 | shell: bash 27 | 28 | - name: Setup conan profile 29 | run: | 30 | conan profile new default --detect 31 | conan profile show default 32 | shell: bash 33 | 34 | - name: Setup filter.lfs.required 35 | run: | 36 | git config --global filter.lfs.required false 37 | shell: bash 38 | 39 | - name: Setup filter.lfs.smudge 40 | run: | 41 | git config --global filter.lfs.smudge "git-lfs smudge --skip %f" 42 | shell: bash 43 | 44 | - name: Setup filter.lfs.process 45 | run: | 46 | git config --global filter.lfs.process "git-lfs filter-process --skip" 47 | shell: bash 48 | 49 | - name: Fetch sdk 50 | run: | 51 | curl -O -L "https://github.com/V-Sekai/FBXSDK-Windows/archive/refs/tags/2020.2.zip" 52 | shell: cmd 53 | 54 | - name: install 7z extract 55 | run: | 56 | 7z x 2020.2.zip 57 | shell: cmd 58 | 59 | - name: move 60 | run: | 61 | mkdir -p sdk 62 | mv ./FBXSDK-Windows-2020.2/sdk . 63 | shell: bash 64 | 65 | - name: Decompress sdk 66 | run: | 67 | zstd -d -r --rm ./sdk || true 68 | shell: bash 69 | 70 | - name: Conan install 71 | run: | 72 | conan install . -i build -s build_type=Release -s compiler="Visual Studio" --build missing 73 | shell: cmd 74 | 75 | - name: Conan build 76 | run: | 77 | conan build -bf build . 78 | shell: cmd 79 | 80 | - name: Run FBX2glTF help 81 | run: | 82 | ./build/Release/FBX2glTF.exe --help 83 | shell: bash 84 | 85 | - name: Prepare artifacts 86 | run: | 87 | export TARGET=FBX2glTF-windows-x86_64 88 | mkdir $TARGET 89 | cp sdk/Windows/2020.2/License.rtf $TARGET/FBX-SDK-License.rtf 90 | cp LICENSE $TARGET/FBX2glTF-License.txt 91 | cp build/Release/FBX2glTF.exe $TARGET/FBX2glTF-windows-x86_64.exe 92 | 7z a -r $TARGET.zip $TARGET 93 | shell: bash 94 | 95 | - name: Release 96 | uses: softprops/action-gh-release@v1 97 | if: startsWith(github.ref, 'refs/tags/') 98 | with: 99 | files: | 100 | FBX2glTF-windows-x86_64.zip 101 | FBX2glTF-windows-x86_64/FBX2glTF-License.txt 102 | FBX2glTF-windows-x86_64/FBX-SDK-License.rtf 103 | 104 | - name: FBX2glTF-windows-x86_64 105 | uses: actions/upload-artifact@v4 106 | with: 107 | name: FBX2glTF-windows-x86_64 108 | path: FBX2glTF-windows-x86_64/* 109 | 110 | build-linux: 111 | runs-on: ubuntu-20.04 112 | steps: 113 | - name: Checkout 114 | uses: actions/checkout@v4 115 | 116 | - name: Install conan 117 | run: | 118 | pip install --upgrade conan==1.63.0 119 | shell: bash 120 | 121 | - name: Setup conan profile 122 | run: | 123 | conan profile new default --detect 124 | conan profile show default 125 | shell: bash 126 | 127 | - name: Setup filter.lfs.required 128 | run: | 129 | git config --global filter.lfs.required false 130 | shell: bash 131 | 132 | - name: Setup filter.lfs.smudge 133 | run: | 134 | git config --global filter.lfs.smudge "git-lfs smudge --skip %f" 135 | shell: bash 136 | 137 | - name: Setup filter.lfs.process 138 | run: | 139 | git config --global filter.lfs.process "git-lfs filter-process --skip" 140 | shell: bash 141 | 142 | - name: Fetch sdk 143 | run: | 144 | curl -O -L "https://github.com/V-Sekai/FBXSDK-Linux/archive/refs/tags/2020.2.zip" 145 | shell: bash 146 | 147 | - name: install 7z extract 148 | run: | 149 | 7z x 2020.2.zip 150 | shell: bash 151 | 152 | - name: move 153 | run: | 154 | mkdir -p sdk 155 | mv ./FBXSDK-Linux-2020.2/sdk . 156 | shell: bash 157 | 158 | - name: Decompress sdk 159 | run: | 160 | zstd -d -r --rm ./sdk || true 161 | shell: bash 162 | 163 | - name: Conan install 164 | run: | 165 | conan install . -i build -s build_type=Release --build fmt -s compiler.libcxx=libstdc++11 --build missing 166 | shell: bash 167 | 168 | - name: Conan build 169 | run: | 170 | conan build -bf build . 171 | shell: bash 172 | 173 | - name: Run FBX2glTF help 174 | run: | 175 | ./build/FBX2glTF --help 176 | shell: bash 177 | 178 | - name: Prepare artifacts 179 | run: | 180 | export TARGET=FBX2glTF-linux-x86_64 181 | mkdir $TARGET 182 | cp sdk/Linux/2020.2/License.txt $TARGET/FBX-SDK-License.txt 183 | cp LICENSE $TARGET/FBX2glTF-License.txt 184 | cp build/FBX2glTF $TARGET/FBX2glTF-linux-x86_64 185 | 7z a -r $TARGET.zip $TARGET 186 | shell: bash 187 | 188 | - name: Release 189 | uses: softprops/action-gh-release@v1 190 | if: startsWith(github.ref, 'refs/tags/') 191 | with: 192 | files: FBX2glTF-linux-x86_64.zip 193 | 194 | - name: FBX2glTF-linux-x86_64 195 | uses: actions/upload-artifact@v4 196 | with: 197 | name: FBX2glTF-linux-x86_64 198 | path: FBX2glTF-linux-x86_64/* 199 | 200 | build-macos: 201 | runs-on: macos-13 202 | steps: 203 | - name: Checkout 204 | uses: actions/checkout@v4 205 | 206 | - name: Install conan 207 | run: | 208 | pip install --upgrade conan==1.63.0 209 | shell: bash 210 | 211 | - name: Setup conan profile 212 | run: | 213 | conan profile new default --detect 214 | conan profile show default 215 | shell: bash 216 | 217 | - name: Setup filter.lfs.required 218 | run: | 219 | git config --global filter.lfs.required false 220 | shell: bash 221 | 222 | - name: Setup filter.lfs.smudge 223 | run: | 224 | git config --global filter.lfs.smudge "git-lfs smudge --skip %f" 225 | shell: bash 226 | 227 | - name: Setup filter.lfs.process 228 | run: | 229 | git config --global filter.lfs.process "git-lfs filter-process --skip" 230 | shell: bash 231 | 232 | - name: Fetch sdk 233 | run: | 234 | curl -O -L "https://github.com/V-Sekai/FBXSDK-Darwin/archive/refs/tags/2020.2.zip" 235 | shell: bash 236 | 237 | - name: install 7z extract 238 | run: | 239 | 7z x 2020.2.zip 240 | shell: bash 241 | 242 | - name: move 243 | run: | 244 | mkdir -p sdk 245 | mv ./FBXSDK-Darwin-2020.2/sdk . 246 | shell: bash 247 | 248 | - name: Decompress sdk 249 | run: | 250 | zstd -d -r --rm ./sdk || true 251 | shell: bash 252 | 253 | - name: Conan install 254 | run: | 255 | env CMAKE_OSX_ARCHITECTURES=x86_64 conan install . -i build -s build_type=Release --settings arch=x86_64 --build missing 256 | shell: bash 257 | 258 | - name: Conan build 259 | run: | 260 | env CMAKE_OSX_ARCHITECTURES=x86_64 conan build -bf build . 261 | shell: bash 262 | 263 | - name: Run FBX2glTF help 264 | run: | 265 | ./build/FBX2glTF --help 266 | shell: bash 267 | 268 | - name: Adhoc signature 269 | run: | 270 | codesign -s - --options=runtime build/FBX2glTF 271 | shell: bash 272 | 273 | - name: Prepare artifacts 274 | run: | 275 | export TARGET=FBX2glTF-macos-x86_64 276 | mkdir $TARGET 277 | cp sdk/Darwin/2020.2/License.rtf $TARGET/FBX-SDK-License.rtf 278 | cp LICENSE $TARGET/FBX2glTF-License.txt 279 | cp build/FBX2glTF $TARGET/FBX2glTF-macos-x86_64 280 | 7z a -r $TARGET.zip $TARGET 281 | shell: bash 282 | 283 | - name: Release 284 | uses: softprops/action-gh-release@v1 285 | if: startsWith(github.ref, 'refs/tags/') 286 | with: 287 | files: FBX2glTF-macos-x86_64.zip 288 | 289 | - name: FBX2glTF-macos-x86_64 290 | uses: actions/upload-artifact@v4 291 | with: 292 | name: FBX2glTF-macos-x86_64 293 | path: FBX2glTF-macos-x86_64/* 294 | -------------------------------------------------------------------------------- /.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 | build/ 9 | .cache/ 10 | sdk/ 11 | 12 | demo/.godot/ 13 | -------------------------------------------------------------------------------- /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 "2020.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 1.76 COMPONENTS system filesystem program_options nowide REQUIRED ) 49 | find_package(ZLIB MODULE REQUIRED) 50 | find_package(fmt MODULE REQUIRED) 51 | find_package(Iconv MODULE REQUIRED) 52 | find_package(LibXml2 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 8786740086a9f4d83f44aa83badfbea4dce7a1b5 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 | find_file(FEDORA_FOUND fedora-release 69 | PATHS /etc 70 | ) 71 | if (WIN32) 72 | set(DRACO_LIB "${CMAKE_BINARY_DIR}/draco/lib/${CMAKE_SHARED_MODULE_PREFIX}draco.lib") 73 | elseif(FEDORA_FOUND) 74 | set(DRACO_LIB "${CMAKE_BINARY_DIR}/draco/lib64/${CMAKE_SHARED_MODULE_PREFIX}draco.a") 75 | else() 76 | set(DRACO_LIB "${CMAKE_BINARY_DIR}/draco/lib/${CMAKE_SHARED_MODULE_PREFIX}draco.a") 77 | endif() 78 | 79 | # MATHFU 80 | set(mathfu_build_benchmarks OFF CACHE BOOL "") 81 | set(mathfu_build_tests OFF CACHE BOOL "") 82 | ExternalProject_Add(MathFu 83 | PREFIX mathfu 84 | GIT_REPOSITORY https://github.com/google/mathfu 85 | GIT_TAG v1.1.0 86 | CONFIGURE_COMMAND ${CMAKE_COMMAND} -E echo "Skipping MathFu configure step." 87 | BUILD_COMMAND ${CMAKE_COMMAND} -E echo "Skipping MathFu build step." 88 | INSTALL_COMMAND ${CMAKE_COMMAND} -E echo "Skipping MathFu install step." 89 | ) 90 | set(MATHFU_INCLUDE_DIRS 91 | "${CMAKE_BINARY_DIR}/mathfu/src/MathFu/include/" 92 | "${CMAKE_BINARY_DIR}/mathfu/src/MathFu/dependencies/vectorial/include") 93 | 94 | # OrderedMap 95 | ExternalProject_Add(FiFoMap 96 | PREFIX fifo_map 97 | GIT_REPOSITORY https://github.com/nlohmann/fifo_map 98 | CONFIGURE_COMMAND ${CMAKE_COMMAND} -E echo "Skipping FiFoMap configure step." 99 | BUILD_COMMAND ${CMAKE_COMMAND} -E echo "Skipping FiFoMap build step." 100 | INSTALL_COMMAND ${CMAKE_COMMAND} -E echo "Skipping FiFoMap install step." 101 | ) 102 | set(FIFO_MAP_INCLUDE_DIR "${CMAKE_BINARY_DIR}/fifo_map/src/FiFoMap/src") 103 | 104 | 105 | # cppcodec 106 | ExternalProject_Add(CPPCodec 107 | PREFIX cppcodec 108 | GIT_REPOSITORY https://github.com/tplgy/cppcodec 109 | CONFIGURE_COMMAND ${CMAKE_COMMAND} -E echo "Skipping CPPCodec configure step." 110 | BUILD_COMMAND ${CMAKE_COMMAND} -E echo "Skipping CPPCodec build step." 111 | INSTALL_COMMAND ${CMAKE_COMMAND} -E echo "Skipping CPPCodec install step." 112 | ) 113 | set(CPPCODEC_INCLUDE_DIR "${CMAKE_BINARY_DIR}/cppcodec/src/CPPCodec") 114 | 115 | if (APPLE) 116 | find_library(CF_FRAMEWORK CoreFoundation) 117 | message("CoreFoundation Framework: ${CF_FRAMEWORK}") 118 | set(FRAMEWORKS ${CF_FRAMEWORK}) 119 | endif() 120 | 121 | set(LIB_SOURCE_FILES 122 | src/FBX2glTF.h 123 | src/fbx/materials/3dsMaxPhysicalMaterial.cpp 124 | src/fbx/materials/FbxMaterials.cpp 125 | src/fbx/materials/FbxMaterials.hpp 126 | src/fbx/materials/RoughnessMetallicMaterials.hpp 127 | src/fbx/materials/StingrayPBSMaterial.cpp 128 | src/fbx/materials/TraditionalMaterials.cpp 129 | src/fbx/materials/TraditionalMaterials.hpp 130 | src/fbx/Fbx2Raw.cpp 131 | src/fbx/Fbx2Raw.hpp 132 | src/fbx/FbxBlendShapesAccess.cpp 133 | src/fbx/FbxBlendShapesAccess.hpp 134 | src/fbx/FbxLayerElementAccess.hpp 135 | src/fbx/FbxSkinningAccess.cpp 136 | src/fbx/FbxSkinningAccess.hpp 137 | src/gltf/Raw2Gltf.cpp 138 | src/gltf/Raw2Gltf.hpp 139 | src/gltf/GltfModel.cpp 140 | src/gltf/GltfModel.hpp 141 | src/gltf/TextureBuilder.cpp 142 | src/gltf/TextureBuilder.hpp 143 | src/gltf/properties/AccessorData.cpp 144 | src/gltf/properties/AccessorData.hpp 145 | src/gltf/properties/AnimationData.cpp 146 | src/gltf/properties/AnimationData.hpp 147 | src/gltf/properties/BufferData.cpp 148 | src/gltf/properties/BufferData.hpp 149 | src/gltf/properties/BufferViewData.cpp 150 | src/gltf/properties/BufferViewData.hpp 151 | src/gltf/properties/CameraData.cpp 152 | src/gltf/properties/CameraData.hpp 153 | src/gltf/properties/ImageData.cpp 154 | src/gltf/properties/ImageData.hpp 155 | src/gltf/properties/LightData.cpp 156 | src/gltf/properties/LightData.hpp 157 | src/gltf/properties/MaterialData.cpp 158 | src/gltf/properties/MaterialData.hpp 159 | src/gltf/properties/MeshData.cpp 160 | src/gltf/properties/MeshData.hpp 161 | src/gltf/properties/NodeData.cpp 162 | src/gltf/properties/NodeData.hpp 163 | src/gltf/properties/PrimitiveData.cpp 164 | src/gltf/properties/PrimitiveData.hpp 165 | src/gltf/properties/SamplerData.hpp 166 | src/gltf/properties/SceneData.cpp 167 | src/gltf/properties/SceneData.hpp 168 | src/gltf/properties/SkinData.cpp 169 | src/gltf/properties/SkinData.hpp 170 | src/gltf/properties/TextureData.cpp 171 | src/gltf/properties/TextureData.hpp 172 | src/mathfu.hpp 173 | src/raw/RawModel.cpp 174 | src/raw/RawModel.hpp 175 | src/utils/File_Utils.cpp 176 | src/utils/File_Utils.hpp 177 | src/utils/Image_Utils.cpp 178 | src/utils/Image_Utils.hpp 179 | src/utils/String_Utils.hpp 180 | third_party/CLI11/CLI11.hpp 181 | ) 182 | 183 | add_library(libFBX2glTF STATIC ${LIB_SOURCE_FILES}) 184 | set_target_properties(libFBX2glTF PROPERTIES OUTPUT_NAME "libFBX2glTF") 185 | add_executable(FBX2glTF src/FBX2glTF.cpp) 186 | set_target_properties(FBX2glTF PROPERTIES OUTPUT_NAME "FBX2glTF") 187 | 188 | add_dependencies(libFBX2glTF 189 | Draco 190 | MathFu 191 | FiFoMap 192 | CPPCodec 193 | ) 194 | 195 | if (NOT MSVC) 196 | # Disable annoying & spammy warning from FBX SDK header file 197 | target_compile_options(libFBX2glTF PRIVATE 198 | "-Wno-null-dereference" 199 | "-Wunused" 200 | ) 201 | target_compile_options(FBX2glTF PRIVATE 202 | "-Wno-null-dereference" 203 | "-Wunused" 204 | ) 205 | endif() 206 | 207 | target_link_libraries(libFBX2glTF 208 | ${FRAMEWORKS} 209 | ${DRACO_LIB} 210 | Boost::system 211 | Boost::filesystem 212 | Boost::nowide 213 | optimized ${FBXSDK_LIBRARY} 214 | debug ${FBXSDK_LIBRARY_DEBUG} 215 | fmt::fmt 216 | ZLIB::ZLIB 217 | LibXml2::LibXml2 218 | ${ICONV_MAC_LIB} 219 | ${CMAKE_DL_LIBS} 220 | ${CMAKE_THREAD_LIBS_INIT} 221 | ) 222 | 223 | target_include_directories(libFBX2glTF PUBLIC 224 | ${CMAKE_CURRENT_SOURCE_DIR}/src 225 | ) 226 | 227 | target_include_directories(libFBX2glTF SYSTEM PUBLIC 228 | "third_party/stb" 229 | "third_party/json" 230 | ${FBXSDK_INCLUDE_DIR} 231 | ${DRACO_INCLUDE_DIR} 232 | ${MATHFU_INCLUDE_DIRS} 233 | ${FIFO_MAP_INCLUDE_DIR} 234 | ${CPPCODEC_INCLUDE_DIR} 235 | ) 236 | 237 | target_include_directories(FBX2glTF PUBLIC 238 | "third_party/CLI11" 239 | ) 240 | 241 | if (APPLE) 242 | set(ICONV_MAC_LIB iconv) 243 | endif() 244 | 245 | target_link_libraries(FBX2glTF libFBX2glTF ${ICONV_MAC_LIB}) 246 | 247 | install (TARGETS libFBX2glTF FBX2glTF 248 | RUNTIME DESTINATION bin 249 | ARCHIVE DESTINATION lib 250 | ) 251 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | Godot Engine has adopted a Code of Conduct that we expect project participants to adhere to. 4 | Please read the [full text](https://godotengine.org/code-of-conduct) 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 | 12 | ## Issues 13 | We use GitHub issues to track public bugs. Please ensure your description is 14 | clear and has sufficient instructions to be able to reproduce the issue. 15 | 16 | ## License 17 | By contributing to FBX2glTF, you agree that your contributions will be licensed 18 | under the LICENSE file in the root directory of this source tree. 19 | -------------------------------------------------------------------------------- /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 "2020.2") 28 | endif() 29 | 30 | set(_fbxsdk_vstudio_version "vs2019") 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) 2020-2022 V-Sekai contributors. 6 | Copyright (c) Facebook, Inc. and its affiliates. All rights reserved. 7 | 8 | Redistribution and use in source and binary forms, with or without modification, 9 | are permitted provided that the following conditions are met: 10 | 11 | * Redistributions of source code must retain the above copyright notice, this 12 | list of conditions and the following disclaimer. 13 | 14 | * Redistributions in binary form must reproduce the above copyright notice, 15 | this list of conditions and the following disclaimer in the documentation 16 | and/or other materials provided with the distribution. 17 | 18 | * Neither the name Facebook nor the names of its contributors may be used to 19 | endorse or promote products derived from this software without specific 20 | prior written permission. 21 | 22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 23 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 24 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 25 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 26 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 27 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 28 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 29 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 31 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FBX2glTF 2 | 3 | > [!NOTE] 4 | > 5 | > As of Godot 4.3, FBX2glTF is no longer used. The engine now relies on 6 | > [ufbx](https://github.com/ufbx/ufbx) instead, which is a built-in library 7 | > instead of an external command-line tool. 8 | > 9 | > As such, **in Godot 4.3 or later, you no longer need to set up FBX2glTF** 10 | > to import FBX scenes. 11 | > 12 | > We no longer actively maintain this repository as we won't be needing it 13 | > going forward. If you want to build on top of it for your own use cases, 14 | > feel free to fork it. 15 | 16 | A command-line tool for the conversion of 3D model assets on the FBX file format 17 | to the glTF file format. 18 | 19 | This is a fork of [facebookincubator/FBX2glTF](https://github.com/facebookincubator/FBX2glTF) 20 | to fix issues for the needs of [Godot Engine](https://godotengine.org/). 21 | 22 | Change skinning-weights to 4 with `--skinning-weights 4`, if your engine does not support the 8 bone weights feature. 23 | 24 | Change the default import of the engine to be different from 30 fps if needed, with `--anim-framerate (bake24|bake30|bake60)`. 25 | 26 | ## License 27 | 28 | The FBX2glTF command line tool is distributed under the 3-clause BSD license. 29 | 30 | Precompiled binaries include **proprietary code** from the Autodesk FBX SDK 2020, 31 | which is distributed under the 32 | [Autodesk LICENSE AND SERVICES AGREEMENT](https://github.com/godotengine/FBX2glTF/releases/latest/download/FBX-SDK-License.rtf). 33 | 34 | **By downloading and using this tool, you agree to the terms of that Autodesk 35 | proprietary license.** 36 | 37 | ## Platform binaries 38 | 39 | Check the [latest release](https://github.com/godotengine/FBX2glTF/releases/latest/) 40 | for the last precompiled binaries for Linux, macOS, and Windows. 41 | 42 | - Linux x86_64: [`FBX2glTF-linux-x86_64.zip`](https://github.com/godotengine/FBX2glTF/releases/latest/download/FBX2glTF-linux-x86_64.zip) 43 | * It is built on Ubuntu 20.04 and requires glibc 2.31 or newer. 44 | - macOS x86_64: [`FBX2glTF-macos-x86_64.zip`](https://github.com/godotengine/FBX2glTF/releases/latest/download/FBX2glTF-macos-x86_64.zip) 45 | * It should work fine for macOS ARM64 too using Rosetta 2. 46 | - Windows x86_64: [`FBX2glTF-windows-x86_64.zip`](https://github.com/godotengine/FBX2glTF/releases/latest/download/FBX2glTF-windows-x86_64.zip) 47 | * [**Requires Microsot Visual C++ Redistributable.**](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist) 48 | 49 | There are also artifacts of the latest commit for Linux, macOS, and Windows 50 | in the [GitHub Actions](https://github.com/godotengine/FBX2glTF/actions) tab. 51 | 52 | ## Build instructions 53 | 54 | Reference the [GitHub workflow](https://github.com/godotengine/FBX2glTF/blob/master/.github/workflows/build.yaml). 55 | -------------------------------------------------------------------------------- /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/1.84.0", 13 | "libiconv/1.17", 14 | "zlib/1.3.1", 15 | "libxml2/2.12.5", 16 | "fmt/5.3.0", 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 | -------------------------------------------------------------------------------- /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 | #include 15 | #include 16 | 17 | #include 18 | 19 | #include "FBX2glTF.h" 20 | #include "fbx/Fbx2Raw.hpp" 21 | #include "gltf/Raw2Gltf.hpp" 22 | #include "utils/File_Utils.hpp" 23 | #include "utils/String_Utils.hpp" 24 | 25 | // in Fbx2Raw.cpp 26 | extern std::string NativeToUTF8(const std::string& str); 27 | 28 | bool verboseOutput = false; 29 | 30 | int main(int argc, char* argv[]) { 31 | boost::nowide::nowide_filesystem(); 32 | 33 | GltfOptions gltfOptions; 34 | 35 | CLI::App app{ 36 | fmt::sprintf( 37 | "FBX2glTF %s: Generate a glTF 2.0 representation of an FBX model.", FBX2GLTF_VERSION), 38 | "FBX2glTF"}; 39 | 40 | app.add_flag( 41 | "-v,--verbose", 42 | verboseOutput, 43 | "Print verbose processing output."); 44 | 45 | app.add_flag_function("-V,--version", [&](size_t count) { 46 | fmt::printf("FBX2glTF version %s\nCopyright (c) 2016-2018 Oculus VR, LLC.\n", FBX2GLTF_VERSION); 47 | exit(0); 48 | }); 49 | 50 | std::string inputPath; 51 | app.add_option("FBX Model", inputPath, "The FBX model to convert.")->check(CLI::ExistingFile); 52 | app.add_option("-i,--input", inputPath, "The FBX model to convert.")->check(CLI::ExistingFile); 53 | 54 | std::string outputPath; 55 | app.add_option("-o,--output", outputPath, "Where to generate the output, without suffix."); 56 | 57 | app.add_flag( 58 | "-e,--embed", 59 | gltfOptions.embedResources, 60 | "Inline buffers as data:// URIs within generated non-binary glTF."); 61 | 62 | app.add_flag( 63 | "-t,--separate-textures", gltfOptions.separateTextures, "Write texture files out separately"); 64 | 65 | app.add_flag("-b,--binary", gltfOptions.outputBinary, "Output a single binary format .glb file."); 66 | 67 | app.add_option( 68 | "--long-indices", 69 | [&](std::vector choices) -> bool { 70 | for (const std::string choice : choices) { 71 | if (choice == "never") { 72 | gltfOptions.useLongIndices = UseLongIndicesOptions::NEVER; 73 | } else if (choice == "auto") { 74 | gltfOptions.useLongIndices = UseLongIndicesOptions::AUTO; 75 | } else if (choice == "always") { 76 | gltfOptions.useLongIndices = UseLongIndicesOptions::ALWAYS; 77 | } else { 78 | fmt::printf("Unknown --long-indices: %s\n", choice); 79 | throw CLI::RuntimeError(1); 80 | } 81 | } 82 | return true; 83 | }, 84 | "Whether to use 32-bit indices.") 85 | ->type_name("(never|auto|always)"); 86 | 87 | app.add_option( 88 | "--compute-normals", 89 | [&](std::vector choices) -> bool { 90 | for (const std::string choice : choices) { 91 | if (choice == "never") { 92 | gltfOptions.computeNormals = ComputeNormalsOption::NEVER; 93 | } else if (choice == "broken") { 94 | gltfOptions.computeNormals = ComputeNormalsOption::BROKEN; 95 | } else if (choice == "missing") { 96 | gltfOptions.computeNormals = ComputeNormalsOption::MISSING; 97 | } else if (choice == "always") { 98 | gltfOptions.computeNormals = ComputeNormalsOption::ALWAYS; 99 | } else { 100 | fmt::printf("Unknown --compute-normals option: %s\n", choice); 101 | throw CLI::RuntimeError(1); 102 | } 103 | } 104 | return true; 105 | }, 106 | "When to compute vertex normals from mesh geometry.") 107 | ->type_name("(never|broken|missing|always)"); 108 | 109 | app.add_option( 110 | "--anim-framerate", 111 | [&](std::vector choices) -> bool { 112 | for (const std::string choice : choices) { 113 | if (choice == "bake24") { 114 | gltfOptions.animationFramerate = AnimationFramerateOptions::BAKE24; 115 | } else if (choice == "bake30") { 116 | gltfOptions.animationFramerate = AnimationFramerateOptions::BAKE30; 117 | } else if (choice == "bake60") { 118 | gltfOptions.animationFramerate = AnimationFramerateOptions::BAKE60; 119 | } else { 120 | fmt::printf("Unknown --anim-framerate: %s\n", choice); 121 | throw CLI::RuntimeError(1); 122 | } 123 | } 124 | return true; 125 | }, 126 | "Select baked animation framerate.") 127 | ->type_name("(bake24|bake30|bake60)"); 128 | 129 | const auto opt_flip_u = app.add_flag("--flip-u", "Flip all U texture coordinates."); 130 | const auto opt_no_flip_u = app.add_flag("--no-flip-u", "Don't flip U texture coordinates."); 131 | const auto opt_flip_v = app.add_flag("--flip-v", "Flip all V texture coordinates."); 132 | const auto opt_no_flip_v = app.add_flag("--no-flip-v", "Don't flip V texture coordinates."); 133 | 134 | app.add_flag( 135 | "--pbr-metallic-roughness", 136 | gltfOptions.usePBRMetRough, 137 | "Try to glean glTF 2.0 native PBR attributes from the FBX.") 138 | ->group("Materials"); 139 | 140 | app.add_flag( 141 | "--khr-materials-unlit", 142 | gltfOptions.useKHRMatUnlit, 143 | "Use KHR_materials_unlit extension to request an unlit shader.") 144 | ->group("Materials"); 145 | 146 | app.add_flag_function( 147 | "--no-khr-lights-punctual", 148 | [&](size_t count) { gltfOptions.useKHRLightsPunctual = (count == 0); }, 149 | "Don't use KHR_lights_punctual extension to export FBX lights."); 150 | 151 | app.add_flag( 152 | "--user-properties", 153 | gltfOptions.enableUserProperties, 154 | "Transcribe FBX User Properties into glTF node and material 'extras'."); 155 | 156 | app.add_flag( 157 | "--blend-shape-no-sparse", 158 | gltfOptions.disableSparseBlendShapes, 159 | "Don't use sparse accessors to store blend shapes"); 160 | 161 | app.add_flag( 162 | "--blend-shape-normals", 163 | gltfOptions.useBlendShapeNormals, 164 | "Include blend shape normals, if reported present by the FBX SDK."); 165 | 166 | app.add_flag( 167 | "--blend-shape-tangents", 168 | gltfOptions.useBlendShapeTangents, 169 | "Include blend shape tangents, if reported present by the FBX SDK."); 170 | 171 | app.add_option( 172 | "--normalize-weights", 173 | gltfOptions.normalizeSkinningWeights, 174 | "Normalize skinning weights.", 175 | true); 176 | 177 | app.add_option( 178 | "--skinning-weights", 179 | gltfOptions.maxSkinningWeights, 180 | "The number of joint influences per vertex.", 181 | true) 182 | ->check(CLI::Range(0, 512)); 183 | 184 | app.add_option( 185 | "-k,--keep-attribute", 186 | [&](std::vector attributes) -> bool { 187 | gltfOptions.keepAttribs = 188 | RAW_VERTEX_ATTRIBUTE_JOINT_INDICES | RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS; 189 | for (std::string attribute : attributes) { 190 | if (attribute == "position") { 191 | gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_POSITION; 192 | } else if (attribute == "normal") { 193 | gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_NORMAL; 194 | } else if (attribute == "tangent") { 195 | gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_TANGENT; 196 | } else if (attribute == "binormal") { 197 | gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_BINORMAL; 198 | } else if (attribute == "color") { 199 | gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_COLOR; 200 | } else if (attribute == "uv0") { 201 | gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_UV0; 202 | } else if (attribute == "uv1") { 203 | gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_UV1; 204 | } else if (attribute == "auto") { 205 | gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_AUTO; 206 | } else { 207 | fmt::printf("Unknown --keep-attribute option: %s\n", attribute); 208 | throw CLI::RuntimeError(1); 209 | } 210 | } 211 | return true; 212 | }, 213 | "Used repeatedly to build a limiting set of vertex attributes to keep.") 214 | ->type_size(-1) 215 | ->type_name("(position|normal|tangent|binormial|color|uv0|uv1|auto)"); 216 | 217 | app.add_flag( 218 | "-d,--draco", gltfOptions.draco.enabled, "Apply Draco mesh compression to geometries.") 219 | ->group("Draco"); 220 | 221 | app.add_option( 222 | "--draco-compression-level", 223 | gltfOptions.draco.compressionLevel, 224 | "The compression level to tune Draco to.", 225 | true) 226 | ->check(CLI::Range(0, 10)) 227 | ->group("Draco"); 228 | 229 | app.add_option( 230 | "--draco-bits-for-position", 231 | gltfOptions.draco.quantBitsPosition, 232 | "How many bits to quantize position to.", 233 | true) 234 | ->check(CLI::Range(1, 32)) 235 | ->group("Draco"); 236 | 237 | app.add_option( 238 | "--draco-bits-for-uv", 239 | gltfOptions.draco.quantBitsTexCoord, 240 | "How many bits to quantize UV coordinates to.", 241 | true) 242 | ->check(CLI::Range(1, 32)) 243 | ->group("Draco"); 244 | 245 | app.add_option( 246 | "--draco-bits-for-normals", 247 | gltfOptions.draco.quantBitsNormal, 248 | "How many bits to quantize normals to.", 249 | true) 250 | ->check(CLI::Range(1, 32)) 251 | ->group("Draco"); 252 | 253 | app.add_option( 254 | "--draco-bits-for-colors", 255 | gltfOptions.draco.quantBitsColor, 256 | "How many bits to quantize colors to.", 257 | true) 258 | ->check(CLI::Range(1, 32)) 259 | ->group("Draco"); 260 | 261 | app.add_option( 262 | "--draco-bits-for-other", 263 | gltfOptions.draco.quantBitsGeneric, 264 | "How many bits to quantize all other vertex attributes to.", 265 | true) 266 | ->check(CLI::Range(1, 32)) 267 | ->group("Draco"); 268 | 269 | app.add_option( 270 | "--fbx-temp-dir", gltfOptions.fbxTempDir, "Temporary directory to be used by FBX SDK.") 271 | ->check(CLI::ExistingDirectory); 272 | 273 | CLI11_PARSE(app, argc, argv); 274 | 275 | bool do_flip_u = false; 276 | bool do_flip_v = true; 277 | // somewhat tedious way to resolve --flag vs --no-flag in order provided 278 | for (const auto opt : app.parse_order()) { 279 | do_flip_u = (do_flip_u || (opt == opt_flip_u)) && (opt != opt_no_flip_u); 280 | do_flip_v = (do_flip_v || (opt == opt_flip_v)) && (opt != opt_no_flip_v); 281 | } 282 | std::vector> texturesTransforms; 283 | if (do_flip_u || do_flip_v) { 284 | if (do_flip_u && do_flip_v) { 285 | texturesTransforms.emplace_back([](Vec2f uv) { return Vec2f(1.0 - uv[0], 1.0 - uv[1]); }); 286 | } else if (do_flip_u) { 287 | texturesTransforms.emplace_back([](Vec2f uv) { return Vec2f(1.0 - uv[0], uv[1]); }); 288 | } else { 289 | texturesTransforms.emplace_back([](Vec2f uv) { return Vec2f(uv[0], 1.0 - uv[1]); }); 290 | } 291 | } 292 | if (verboseOutput) { 293 | if (do_flip_u) { 294 | fmt::printf("Flipping texture coordinates in the 'U' dimension.\n"); 295 | } 296 | if (!do_flip_v) { 297 | fmt::printf("NOT flipping texture coordinates in the 'V' dimension.\n"); 298 | } 299 | } 300 | 301 | if (inputPath.empty()) { 302 | fmt::printf("You must supply a FBX file to convert.\n"); 303 | exit(1); 304 | } 305 | 306 | if (!gltfOptions.useKHRMatUnlit && !gltfOptions.usePBRMetRough) { 307 | if (verboseOutput) { 308 | fmt::printf("Defaulting to --pbr-metallic-roughness material support.\n"); 309 | } 310 | gltfOptions.usePBRMetRough = true; 311 | } 312 | 313 | if (gltfOptions.embedResources && gltfOptions.outputBinary) { 314 | fmt::printf("Note: Ignoring --embed; it's meaningless with --binary.\n"); 315 | } 316 | 317 | if (outputPath.empty()) { 318 | // if -o is not given, default to the basename of the .fbx 319 | outputPath = "./" + FileUtils::GetFileBase(inputPath); 320 | } else { 321 | outputPath = NativeToUTF8(outputPath); 322 | } 323 | // the output folder in .gltf mode, not used for .glb 324 | std::string outputFolder; 325 | 326 | // the path of the actual .glb or .gltf file 327 | std::string modelPath; 328 | const auto& suffix = FileUtils::GetFileSuffix(outputPath); 329 | 330 | // Assume binary output if extension is glb 331 | if (suffix.has_value() && suffix.value() == "glb") { 332 | gltfOptions.outputBinary = true; 333 | } 334 | 335 | if (gltfOptions.outputBinary) { 336 | // add .glb to output path, unless it already ends in exactly that 337 | outputFolder = FileUtils::getFolder(outputPath) + "/"; 338 | if (suffix.has_value() && suffix.value() == "glb") { 339 | modelPath = outputPath; 340 | } else { 341 | modelPath = outputPath + ".glb"; 342 | } 343 | // if the extension is gltf set the output folder to the parent directory 344 | } else if (suffix.has_value() && suffix.value() == "gltf") { 345 | outputFolder = FileUtils::getFolder(outputPath) + "/"; 346 | modelPath = outputPath; 347 | } else { 348 | // in gltf mode, we create a folder and write into that 349 | outputFolder = fmt::format("{}_out/", outputPath.c_str()); 350 | modelPath = outputFolder + FileUtils::GetFileName(outputPath) + ".gltf"; 351 | } 352 | if (!FileUtils::CreatePath(modelPath.c_str())) { 353 | fmt::fprintf(stderr, "ERROR: Failed to create folder: %s'\n", outputFolder.c_str()); 354 | return 1; 355 | } 356 | 357 | ModelData* data_render_model = nullptr; 358 | RawModel raw; 359 | 360 | if (verboseOutput) { 361 | fmt::printf("Loading FBX File: %s\n", inputPath); 362 | } 363 | if (!LoadFBXFile(raw, inputPath, {"png", "jpg", "jpeg"}, gltfOptions)) { 364 | fmt::fprintf(stderr, "ERROR:: Failed to parse FBX: %s\n", inputPath); 365 | return 1; 366 | } 367 | 368 | if (!texturesTransforms.empty()) { 369 | raw.TransformTextures(texturesTransforms); 370 | } 371 | raw.Condense(gltfOptions.maxSkinningWeights, gltfOptions.normalizeSkinningWeights); 372 | raw.TransformGeometry(gltfOptions.computeNormals); 373 | 374 | boost::nowide::ofstream outStream; // note: auto-flushes in destructor 375 | const auto streamStart = outStream.tellp(); 376 | 377 | outStream.open(modelPath, std::ios::trunc | std::ios::ate | std::ios::out | std::ios::binary); 378 | if (outStream.fail()) { 379 | fmt::fprintf(stderr, "ERROR:: Couldn't open file for writing: %s\n", modelPath.c_str()); 380 | return 1; 381 | } 382 | data_render_model = Raw2Gltf(outStream, outputFolder, raw, gltfOptions); 383 | 384 | if (gltfOptions.outputBinary) { 385 | fmt::printf( 386 | "Wrote %lu bytes of binary glTF to %s.\n", 387 | (unsigned long)(outStream.tellp() - streamStart), 388 | modelPath); 389 | delete data_render_model; 390 | return 0; 391 | } 392 | 393 | fmt::printf( 394 | "Wrote %lu bytes of glTF to %s.\n", 395 | (unsigned long)(outStream.tellp() - streamStart), 396 | modelPath); 397 | 398 | if (gltfOptions.embedResources) { 399 | // we're done: everything was inlined into the glTF JSON 400 | delete data_render_model; 401 | return 0; 402 | } 403 | 404 | assert(!outputFolder.empty()); 405 | 406 | const std::string binaryPath = outputFolder + extBufferFilename; 407 | FILE* fp = boost::nowide::fopen(binaryPath.c_str(), "wb"); 408 | if (fp == nullptr) { 409 | fmt::fprintf(stderr, "ERROR:: Couldn't open file '%s' for writing.\n", binaryPath); 410 | return 1; 411 | } 412 | 413 | if (data_render_model->binary->empty() == false) { 414 | const unsigned char* binaryData = &(*data_render_model->binary)[0]; 415 | unsigned long binarySize = data_render_model->binary->size(); 416 | if (fwrite(binaryData, binarySize, 1, fp) != 1) { 417 | fmt::fprintf( 418 | stderr, "ERROR: Failed to write %lu bytes to file '%s'.\n", binarySize, binaryPath); 419 | fclose(fp); 420 | return 1; 421 | } 422 | fclose(fp); 423 | fmt::printf("Wrote %lu bytes of binary data to %s.\n", binarySize, binaryPath); 424 | } 425 | 426 | delete data_render_model; 427 | return 0; 428 | } 429 | -------------------------------------------------------------------------------- /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.13.1") 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 | bool separateTextures{true}; 94 | 95 | /** Whether and how to use KHR_draco_mesh_compression to minimize static geometry size. */ 96 | struct { 97 | bool enabled = false; 98 | int compressionLevel = 7; 99 | int quantBitsPosition = 14; 100 | int quantBitsTexCoord = 10; 101 | int quantBitsNormal = 10; 102 | int quantBitsColor = 8; 103 | int quantBitsGeneric = 8; 104 | } draco; 105 | 106 | /** Whether to include FBX User Properties as 'extras' metadata in glTF nodes. */ 107 | bool enableUserProperties{true}; 108 | 109 | /** Whether to use KHR_materials_unlit to extend materials definitions. */ 110 | bool useKHRMatUnlit{false}; 111 | /** Whether to populate the pbrMetallicRoughness substruct in materials. */ 112 | bool usePBRMetRough{true}; 113 | 114 | /** Whether to include lights through the KHR_punctual_lights extension. */ 115 | bool useKHRLightsPunctual{true}; 116 | 117 | /** Whether to not use sparse accessors in blend shapes */ 118 | bool disableSparseBlendShapes{false}; 119 | /** Whether to include blend shape normals, if present according to the SDK. */ 120 | bool useBlendShapeNormals{false}; 121 | /** Whether to include blend shape tangents, if present according to the SDK. */ 122 | bool useBlendShapeTangents{false}; 123 | /** Whether to normalized skinning weights. */ 124 | bool normalizeSkinningWeights{true}; 125 | /** Maximum number of bone influences per vertex. */ 126 | int maxSkinningWeights{8}; 127 | /** When to compute vertex normals from geometry. */ 128 | ComputeNormalsOption computeNormals = ComputeNormalsOption::BROKEN; 129 | /** When to use 32-bit indices. */ 130 | UseLongIndicesOptions useLongIndices = UseLongIndicesOptions::AUTO; 131 | /** Select baked animation framerate. */ 132 | AnimationFramerateOptions animationFramerate = AnimationFramerateOptions::BAKE30; 133 | 134 | /** Temporary directory used by FBX SDK. */ 135 | std::string fbxTempDir; 136 | }; 137 | -------------------------------------------------------------------------------- /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 | vertexSkinning.resize(controlPointCount); 23 | 24 | for (int clusterIndex = 0; clusterIndex < clusterCount; clusterIndex++) { 25 | FbxCluster* cluster = skin->GetCluster(clusterIndex); 26 | const int indexCount = cluster->GetControlPointIndicesCount(); 27 | const int* clusterIndices = cluster->GetControlPointIndices(); 28 | const double* clusterWeights = cluster->GetControlPointWeights(); 29 | 30 | assert( 31 | cluster->GetLinkMode() == FbxCluster::eNormalize || 32 | cluster->GetLinkMode() == FbxCluster::eTotalOne); 33 | 34 | // Transform link matrix. 35 | FbxAMatrix transformLinkMatrix; 36 | cluster->GetTransformLinkMatrix(transformLinkMatrix); 37 | 38 | // The transformation of the mesh at binding time 39 | FbxAMatrix transformMatrix; 40 | cluster->GetTransformMatrix(transformMatrix); 41 | 42 | // Inverse bind matrix. 43 | FbxAMatrix globalBindposeInverseMatrix = transformLinkMatrix.Inverse() * transformMatrix; 44 | inverseBindMatrices.emplace_back(globalBindposeInverseMatrix); 45 | 46 | jointNodes.push_back(cluster->GetLink()); 47 | jointIds.push_back(cluster->GetLink()->GetUniqueID()); 48 | 49 | const FbxAMatrix globalNodeTransform = cluster->GetLink()->EvaluateGlobalTransform(); 50 | jointSkinningTransforms.push_back( 51 | FbxMatrix(globalNodeTransform * globalBindposeInverseMatrix)); 52 | jointInverseGlobalTransforms.push_back(FbxMatrix(globalNodeTransform.Inverse())); 53 | 54 | for (int i = 0; i < indexCount; i++) { 55 | if (clusterIndices[i] < 0 || clusterIndices[i] >= controlPointCount) { 56 | continue; 57 | } 58 | if (clusterWeights[i] <= 0.0) { 59 | continue; 60 | } 61 | 62 | vertexSkinning[clusterIndices[i]].push_back( 63 | FbxVertexSkinningInfo{(int)clusterIndex, (float)clusterWeights[i]}); 64 | } 65 | } 66 | 67 | for (int i = 0; i < vertexSkinning.size(); i++) 68 | maxBoneInfluences = std::max((int)vertexSkinning[i].size(), maxBoneInfluences); 69 | } 70 | } 71 | 72 | rootIndex = -1; 73 | for (size_t i = 0; i < jointNodes.size() && rootIndex == -1; i++) { 74 | rootIndex = (int)i; 75 | FbxNode* parent = jointNodes[i]->GetParent(); 76 | if (parent == nullptr) { 77 | break; 78 | } 79 | for (size_t j = 0; j < jointNodes.size(); j++) { 80 | if (jointNodes[j] == parent) { 81 | rootIndex = -1; 82 | break; 83 | } 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /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 | struct FbxVertexSkinningInfo { 22 | int jointId; 23 | float weight; 24 | }; 25 | 26 | class FbxSkinningAccess { 27 | public: 28 | FbxSkinningAccess(const FbxMesh* pMesh, FbxScene* pScene, FbxNode* pNode); 29 | 30 | bool IsSkinned() const { 31 | return (vertexSkinning.size() > 0); 32 | } 33 | 34 | int GetNodeCount() const { 35 | return (int)jointNodes.size(); 36 | } 37 | 38 | FbxNode* GetJointNode(const int jointIndex) const { 39 | return jointNodes[jointIndex]; 40 | } 41 | 42 | const uint64_t GetJointId(const int jointIndex) const { 43 | return jointIds[jointIndex]; 44 | } 45 | 46 | const FbxMatrix& GetJointSkinningTransform(const int jointIndex) const { 47 | return jointSkinningTransforms[jointIndex]; 48 | } 49 | 50 | const FbxMatrix& GetJointInverseGlobalTransforms(const int jointIndex) const { 51 | return jointInverseGlobalTransforms[jointIndex]; 52 | } 53 | 54 | const uint64_t GetRootNode() const { 55 | assert(rootIndex != -1); 56 | return jointIds[rootIndex]; 57 | } 58 | 59 | const FbxAMatrix& GetInverseBindMatrix(const int jointIndex) const { 60 | return inverseBindMatrices[jointIndex]; 61 | } 62 | 63 | const std::vector GetVertexSkinningInfo( 64 | const int controlPointIndex) const { 65 | return vertexSkinning[controlPointIndex]; 66 | } 67 | 68 | private: 69 | int rootIndex; 70 | int maxBoneInfluences; 71 | std::vector jointIds; 72 | std::vector jointNodes; 73 | std::vector jointSkinningTransforms; 74 | std::vector jointInverseGlobalTransforms; 75 | std::vector inverseBindMatrices; 76 | std::vector> vertexSkinning; 77 | }; 78 | -------------------------------------------------------------------------------- /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 = mesh->GetNode()->GetSrcObject(materialNum); 49 | 50 | if (!surfaceMaterial) { 51 | if (++warnMtrCount == 1) { 52 | fmt::printf("Warning: Reference to missing surface material.\n"); 53 | fmt::printf(" (Further warnings of this type squelched.)\n"); 54 | } 55 | } 56 | 57 | if (materialNum >= summaries.size()) { 58 | summaries.resize(materialNum + 1); 59 | } 60 | auto summary = summaries[materialNum]; 61 | if (summary == nullptr) { 62 | summary = summaries[materialNum] = GetMaterialInfo(surfaceMaterial, textureLocations); 63 | } 64 | 65 | if (materialNum >= userProperties.size()) { 66 | userProperties.resize(materialNum + 1); 67 | } 68 | if (surfaceMaterial && userProperties[materialNum].empty()) { 69 | FbxProperty objectProperty = surfaceMaterial->GetFirstProperty(); 70 | while (objectProperty.IsValid()) { 71 | if (objectProperty.GetFlag(FbxPropertyFlags::eUserDefined)) { 72 | userProperties[materialNum].push_back(TranscribeProperty(objectProperty).dump()); 73 | } 74 | objectProperty = surfaceMaterial->GetNextProperty(objectProperty); 75 | } 76 | } 77 | } 78 | } 79 | 80 | const std::shared_ptr FbxMaterialsAccess::GetMaterial( 81 | const int polygonIndex) const { 82 | if (mappingMode != FbxGeometryElement::eNone) { 83 | const int materialNum = 84 | indices->GetAt((mappingMode == FbxGeometryElement::eByPolygon) ? polygonIndex : 0); 85 | if (materialNum < 0) { 86 | return nullptr; 87 | } 88 | return summaries.at((unsigned long)materialNum); 89 | } 90 | return nullptr; 91 | } 92 | 93 | const std::vector FbxMaterialsAccess::GetUserProperties(const int polygonIndex) const { 94 | if (mappingMode != FbxGeometryElement::eNone) { 95 | const int materialNum = 96 | indices->GetAt((mappingMode == FbxGeometryElement::eByPolygon) ? polygonIndex : 0); 97 | if (materialNum < 0) { 98 | return std::vector(); 99 | } 100 | return userProperties.at((unsigned long)materialNum); 101 | } 102 | return std::vector(); 103 | } 104 | 105 | std::unique_ptr FbxMaterialsAccess::GetMaterialInfo( 106 | FbxSurfaceMaterial* material, 107 | const std::map& textureLocations) { 108 | if (!material) { 109 | return nullptr; 110 | } 111 | std::unique_ptr res = 112 | FbxStingrayPBSMaterialResolver(material, textureLocations).resolve(); 113 | if (res == nullptr) { 114 | res = Fbx3dsMaxPhysicalMaterialResolver(material, textureLocations).resolve(); 115 | if (res == nullptr) { 116 | res = FbxTraditionalMaterialResolver(material, textureLocations).resolve(); 117 | } 118 | } 119 | return res; 120 | } 121 | -------------------------------------------------------------------------------- /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 10 | #include "GltfModel.hpp" 11 | 12 | std::shared_ptr GltfModel::GetAlignedBufferView( 13 | BufferData& buffer, 14 | const BufferViewData::GL_ArrayType target) { 15 | uint32_t bufferSize = to_uint32(this->binary->size()); 16 | if ((bufferSize % 4) > 0) { 17 | bufferSize += (4 - (bufferSize % 4)); 18 | this->binary->resize(bufferSize); 19 | } 20 | return this->bufferViews.hold(new BufferViewData(buffer, bufferSize, target)); 21 | } 22 | 23 | // add a bufferview on the fly and copy data into it 24 | std::shared_ptr 25 | GltfModel::AddRawBufferView(BufferData& buffer, const char* source, uint32_t bytes) { 26 | auto bufferView = GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_NONE); 27 | bufferView->byteLength = bytes; 28 | 29 | // make space for the new bytes (possibly moving the underlying data) 30 | uint32_t bufferSize = to_uint32(this->binary->size()); 31 | this->binary->resize(bufferSize + bytes); 32 | 33 | // and copy them into place 34 | memcpy(&(*this->binary)[bufferSize], source, bytes); 35 | return bufferView; 36 | } 37 | 38 | std::shared_ptr GltfModel::AddBufferViewForFile( 39 | BufferData& buffer, 40 | const std::string& filename) { 41 | // see if we've already created a BufferViewData for this precise file 42 | auto iter = filenameToBufferView.find(filename); 43 | if (iter != filenameToBufferView.end()) { 44 | return iter->second; 45 | } 46 | 47 | std::shared_ptr result; 48 | boost::nowide::ifstream file(filename, std::ios::binary | std::ios::ate); 49 | if (file) { 50 | std::streamsize size = file.tellg(); 51 | file.seekg(0, std::ios::beg); 52 | 53 | std::vector fileBuffer(size); 54 | if (file.read(fileBuffer.data(), size)) { 55 | result = AddRawBufferView(buffer, fileBuffer.data(), to_uint32(size)); 56 | } else { 57 | fmt::printf("Warning: Couldn't read %lu bytes from %s, skipping file.\n", size, filename); 58 | } 59 | } else { 60 | fmt::printf("Warning: Couldn't open file %s, skipping file.\n", filename); 61 | } 62 | // note that we persist here not only success, but also failure, as nullptr 63 | filenameToBufferView[filename] = result; 64 | return result; 65 | } 66 | 67 | void GltfModel::serializeHolders(json& glTFJson) { 68 | serializeHolder(glTFJson, "buffers", buffers); 69 | serializeHolder(glTFJson, "bufferViews", bufferViews); 70 | serializeHolder(glTFJson, "scenes", scenes); 71 | serializeHolder(glTFJson, "accessors", accessors); 72 | serializeHolder(glTFJson, "images", images); 73 | serializeHolder(glTFJson, "samplers", samplers); 74 | serializeHolder(glTFJson, "textures", textures); 75 | serializeHolder(glTFJson, "materials", materials); 76 | serializeHolder(glTFJson, "meshes", meshes); 77 | serializeHolder(glTFJson, "skins", skins); 78 | serializeHolder(glTFJson, "animations", animations); 79 | serializeHolder(glTFJson, "cameras", cameras); 80 | serializeHolder(glTFJson, "nodes", nodes); 81 | if (!lights.ptrs.empty()) { 82 | json lightsJson = json::object(); 83 | serializeHolder(lightsJson, "lights", lights); 84 | glTFJson["extensions"][KHR_LIGHTS_PUNCTUAL] = lightsJson; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /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 | void 74 | CopyToBufferView(BufferViewData& bufferView, const std::vector& source, const GLType& type) { 75 | bufferView.appendAsBinaryArray(source, *binary, type); 76 | } 77 | 78 | template 79 | std::shared_ptr AddAccessorWithView( 80 | BufferViewData& bufferView, 81 | const GLType& type, 82 | const std::vector& source, 83 | std::string name) { 84 | auto accessor = accessors.hold(new AccessorData(bufferView, type, name)); 85 | bufferView.appendAsBinaryArray(source, *binary, type); 86 | accessor->count = bufferView.count; 87 | return accessor; 88 | } 89 | 90 | template 91 | std::shared_ptr AddSparseAccessorWithView( 92 | AccessorData& baseAccessor, 93 | BufferViewData& indexBufferView, 94 | const GLType& indexBufferViewType, 95 | BufferViewData& bufferView, 96 | const GLType& type, 97 | const std::vector& source, 98 | std::string name) { 99 | auto accessor = 100 | accessors.hold(new AccessorData(baseAccessor, indexBufferView, bufferView, type, name)); 101 | bufferView.appendAsBinaryArray(source, *binary, type); 102 | accessor->count = baseAccessor.count; 103 | accessor->sparseIdxBufferViewType = indexBufferViewType.componentType.glType; 104 | return accessor; 105 | } 106 | 107 | // template 108 | std::shared_ptr AddSparseAccessor( 109 | AccessorData& baseAccessor, 110 | BufferViewData& indexBufferView, 111 | const GLType& indexBufferViewType, 112 | BufferViewData& bufferView, 113 | const GLType& type, 114 | // const std::vector& source, 115 | std::string name) { 116 | auto accessor = 117 | accessors.hold(new AccessorData(baseAccessor, indexBufferView, bufferView, type, name)); 118 | // bufferView.appendAsBinaryArray(source, *binary, type); 119 | accessor->count = baseAccessor.count; 120 | accessor->sparseIdxBufferViewType = indexBufferViewType.componentType.glType; 121 | return accessor; 122 | } 123 | 124 | template 125 | std::shared_ptr 126 | AddAccessorAndView(BufferData& buffer, const GLType& type, const std::vector& source) { 127 | auto bufferView = GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_NONE); 128 | return AddAccessorWithView(*bufferView, type, source, std::string("")); 129 | } 130 | 131 | template 132 | std::shared_ptr AddAccessorAndView( 133 | BufferData& buffer, 134 | const GLType& type, 135 | const std::vector& source, 136 | std::string name) { 137 | auto bufferView = GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_NONE); 138 | return AddAccessorWithView(*bufferView, type, source, name); 139 | } 140 | 141 | template 142 | std::shared_ptr AddAttributeToPrimitive( 143 | BufferData& buffer, 144 | const RawModel& surfaceModel, 145 | PrimitiveData& primitive, 146 | const AttributeDefinition& attrDef) { 147 | // copy attribute data into vector 148 | std::vector attribArr; 149 | surfaceModel.GetAttributeArray(attribArr, attrDef.rawAttributeIx); 150 | 151 | std::shared_ptr accessor; 152 | if (attrDef.dracoComponentType != draco::DT_INVALID && primitive.dracoMesh != nullptr) { 153 | primitive.AddDracoAttrib(attrDef, attribArr); 154 | 155 | accessor = accessors.hold(new AccessorData(attrDef.glType)); 156 | accessor->count = to_uint32(attribArr.size()); 157 | } else { 158 | auto bufferView = GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_BUFFER); 159 | accessor = AddAccessorWithView(*bufferView, attrDef.glType, attribArr, std::string("")); 160 | } 161 | primitive.AddAttrib(attrDef.gltfName, *accessor); 162 | return accessor; 163 | }; 164 | 165 | template 166 | std::shared_ptr AddAttributeArrayToPrimitive( 167 | BufferData& buffer, 168 | const RawModel& surfaceModel, 169 | PrimitiveData& primitive, 170 | const AttributeArrayDefinition& attrDef) { 171 | // copy attribute data into vector 172 | std::vector attribArr; 173 | surfaceModel.GetArrayAttributeArray(attribArr, attrDef.rawAttributeIx, attrDef.arrayOffset); 174 | 175 | std::shared_ptr accessor; 176 | if (attrDef.dracoComponentType != draco::DT_INVALID && primitive.dracoMesh != nullptr) { 177 | primitive.AddDracoArrayAttrib(attrDef, attribArr); 178 | 179 | accessor = accessors.hold(new AccessorData(attrDef.glType)); 180 | accessor->count = attribArr.size(); 181 | } else { 182 | auto bufferView = GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_BUFFER); 183 | accessor = AddAccessorWithView(*bufferView, attrDef.glType, attribArr, std::string("")); 184 | } 185 | primitive.AddAttrib(attrDef.gltfName, *accessor); 186 | return accessor; 187 | }; 188 | 189 | template 190 | void serializeHolder(json& glTFJson, std::string key, const Holder holder) { 191 | if (!holder.ptrs.empty()) { 192 | std::vector bits; 193 | for (const auto& ptr : holder.ptrs) { 194 | bits.push_back(ptr->serialize()); 195 | } 196 | glTFJson[key] = bits; 197 | } 198 | } 199 | 200 | void serializeHolders(json& glTFJson); 201 | 202 | const bool isGlb; 203 | 204 | // cache BufferViewData instances that've already been created from a given filename 205 | std::map> filenameToBufferView; 206 | 207 | std::shared_ptr> binary; 208 | 209 | Holder buffers; 210 | Holder bufferViews; 211 | Holder accessors; 212 | Holder images; 213 | Holder samplers; 214 | Holder textures; 215 | Holder materials; 216 | Holder meshes; 217 | Holder skins; 218 | Holder animations; 219 | Holder cameras; 220 | Holder nodes; 221 | Holder scenes; 222 | Holder lights; 223 | 224 | std::shared_ptr defaultSampler; 225 | std::shared_ptr defaultBuffer; 226 | 227 | private: 228 | SamplerData* buildDefaultSampler() { 229 | return new SamplerData(); 230 | } 231 | BufferData* buildDefaultBuffer(const GltfOptions& options) { 232 | return options.outputBinary ? new BufferData(binary) 233 | : new BufferData(extBufferFilename, binary, options.embedResources); 234 | } 235 | }; 236 | -------------------------------------------------------------------------------- /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 | #include 14 | 15 | // This can be a macro under Windows, confusing Draco 16 | #undef ERROR 17 | #include 18 | 19 | #include "FBX2glTF.h" 20 | #include "raw/RawModel.hpp" 21 | 22 | const std::string KHR_DRACO_MESH_COMPRESSION = "KHR_draco_mesh_compression"; 23 | const std::string KHR_MATERIALS_CMN_UNLIT = "KHR_materials_unlit"; 24 | const std::string KHR_LIGHTS_PUNCTUAL = "KHR_lights_punctual"; 25 | 26 | const std::string extBufferFilename = "buffer.bin"; 27 | 28 | struct ComponentType { 29 | // OpenGL Datatype enums 30 | enum GL_DataType { 31 | GL_BYTE = 5120, 32 | GL_UNSIGNED_BYTE, 33 | GL_SHORT, 34 | GL_UNSIGNED_SHORT, 35 | GL_INT, 36 | GL_UNSIGNED_INT, 37 | GL_FLOAT 38 | }; 39 | 40 | const GL_DataType glType; 41 | const unsigned int size; 42 | }; 43 | 44 | const ComponentType CT_USHORT = {ComponentType::GL_UNSIGNED_SHORT, 2}; 45 | const ComponentType CT_UINT = {ComponentType::GL_UNSIGNED_INT, 4}; 46 | const ComponentType CT_FLOAT = {ComponentType::GL_FLOAT, 4}; 47 | 48 | // Map our low-level data types for glTF output 49 | struct GLType { 50 | GLType(const ComponentType& componentType, unsigned int count, const std::string dataType) 51 | : componentType(componentType), count(count), dataType(dataType) {} 52 | 53 | unsigned int byteStride() const { 54 | return componentType.size * count; 55 | } 56 | 57 | void write(uint8_t* buf, const float scalar) const { 58 | *((float*)buf) = scalar; 59 | } 60 | void write(uint8_t* buf, const uint32_t scalar) const { 61 | switch (componentType.size) { 62 | case 1: 63 | *buf = (uint8_t)scalar; 64 | break; 65 | case 2: 66 | *((uint16_t*)buf) = (uint16_t)scalar; 67 | break; 68 | case 4: 69 | *((uint32_t*)buf) = scalar; 70 | break; 71 | } 72 | } 73 | 74 | template 75 | void write(uint8_t* buf, const mathfu::Vector& vector) const { 76 | for (int ii = 0; ii < d; ii++) { 77 | ((T*)buf)[ii] = vector(ii); 78 | } 79 | } 80 | template 81 | void write(uint8_t* buf, const mathfu::Matrix& matrix) const { 82 | // three matrix types require special alignment considerations that we don't handle 83 | // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#data-alignment 84 | assert(!(sizeof(T) == 1 && d == 2)); 85 | assert(!(sizeof(T) == 1 && d == 3)); 86 | assert(!(sizeof(T) == 2 && d == 2)); 87 | for (int col = 0; col < d; col++) { 88 | for (int row = 0; row < d; row++) { 89 | // glTF matrices are column-major 90 | ((T*)buf)[col * d + row] = matrix(row, col); 91 | } 92 | } 93 | } 94 | template 95 | void write(uint8_t* buf, const mathfu::Quaternion& quaternion) const { 96 | for (int ii = 0; ii < 3; ii++) { 97 | ((T*)buf)[ii] = quaternion.vector()(ii); 98 | } 99 | ((T*)buf)[3] = quaternion.scalar(); 100 | } 101 | 102 | const ComponentType componentType; 103 | const uint8_t count; 104 | const std::string dataType; 105 | }; 106 | 107 | const GLType GLT_FLOAT = {CT_FLOAT, 1, "SCALAR"}; 108 | const GLType GLT_USHORT = {CT_USHORT, 1, "SCALAR"}; 109 | const GLType GLT_UINT = {CT_UINT, 1, "SCALAR"}; 110 | const GLType GLT_VEC2F = {CT_FLOAT, 2, "VEC2"}; 111 | const GLType GLT_VEC3F = {CT_FLOAT, 3, "VEC3"}; 112 | const GLType GLT_VEC4F = {CT_FLOAT, 4, "VEC4"}; 113 | const GLType GLT_VEC4I = {CT_USHORT, 4, "VEC4"}; 114 | const GLType GLT_MAT2F = {CT_USHORT, 4, "MAT2"}; 115 | const GLType GLT_MAT3F = {CT_USHORT, 9, "MAT3"}; 116 | const GLType GLT_MAT4F = {CT_FLOAT, 16, "MAT4"}; 117 | const GLType GLT_QUATF = {CT_FLOAT, 4, "VEC4"}; 118 | 119 | /** 120 | * The base of any indexed glTF entity. 121 | */ 122 | struct Holdable { 123 | uint32_t ix = UINT_MAX; 124 | 125 | virtual json serialize() const = 0; 126 | }; 127 | 128 | template 129 | struct AttributeDefinition { 130 | const std::string gltfName; 131 | const T RawVertex::*rawAttributeIx; 132 | const GLType glType; 133 | const draco::GeometryAttribute::Type dracoAttribute; 134 | const draco::DataType dracoComponentType; 135 | 136 | AttributeDefinition( 137 | const std::string gltfName, 138 | const T RawVertex::*rawAttributeIx, 139 | const GLType& _glType, 140 | const draco::GeometryAttribute::Type dracoAttribute, 141 | const draco::DataType dracoComponentType) 142 | : gltfName(gltfName), 143 | rawAttributeIx(rawAttributeIx), 144 | glType(_glType), 145 | dracoAttribute(dracoAttribute), 146 | dracoComponentType(dracoComponentType) {} 147 | 148 | AttributeDefinition( 149 | const std::string gltfName, 150 | const T RawVertex::*rawAttributeIx, 151 | const GLType& _glType) 152 | : gltfName(gltfName), 153 | rawAttributeIx(rawAttributeIx), 154 | glType(_glType), 155 | dracoAttribute(draco::GeometryAttribute::INVALID), 156 | dracoComponentType(draco::DataType::DT_INVALID) {} 157 | }; 158 | 159 | template 160 | struct AttributeArrayDefinition { 161 | const std::string gltfName; 162 | const std::vector RawVertex::*rawAttributeIx; 163 | const GLType glType; 164 | const int arrayOffset; 165 | const draco::GeometryAttribute::Type dracoAttribute; 166 | const draco::DataType dracoComponentType; 167 | 168 | AttributeArrayDefinition( 169 | const std::string gltfName, 170 | const std::vector RawVertex::*rawAttributeIx, 171 | const GLType& _glType, 172 | const draco::GeometryAttribute::Type dracoAttribute, 173 | const draco::DataType dracoComponentType, 174 | const int arrayOffset) 175 | : gltfName(gltfName), 176 | rawAttributeIx(rawAttributeIx), 177 | glType(_glType), 178 | dracoAttribute(dracoAttribute), 179 | dracoComponentType(dracoComponentType), 180 | arrayOffset(arrayOffset) {} 181 | }; 182 | 183 | struct AccessorData; 184 | struct AnimationData; 185 | struct BufferData; 186 | struct BufferViewData; 187 | struct CameraData; 188 | struct GLTFData; 189 | struct ImageData; 190 | struct MaterialData; 191 | struct MeshData; 192 | struct NodeData; 193 | struct PrimitiveData; 194 | struct SamplerData; 195 | struct SceneData; 196 | struct SkinData; 197 | struct TextureData; 198 | 199 | struct ModelData { 200 | explicit ModelData(std::shared_ptr> const& _binary) 201 | : binary(_binary) {} 202 | 203 | std::shared_ptr> const binary; 204 | }; 205 | 206 | ModelData* Raw2Gltf( 207 | boost::nowide::ofstream& gltfOutStream, 208 | const std::string& outputFolder, 209 | const RawModel& raw, 210 | const GltfOptions& options); 211 | -------------------------------------------------------------------------------- /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 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | #include 21 | 22 | #ifdef CopyFile 23 | #undef CopyFile 24 | #endif 25 | 26 | // keep track of some texture data as we load them 27 | struct TexInfo { 28 | explicit TexInfo(int rawTexIx) : rawTexIx(rawTexIx) {} 29 | 30 | const int rawTexIx; 31 | int width{}; 32 | int height{}; 33 | int channels{}; 34 | uint8_t* pixels{}; 35 | }; 36 | 37 | std::shared_ptr TextureBuilder::combine( 38 | const std::vector& ixVec, 39 | const std::string& tag, 40 | const pixel_merger& computePixel, 41 | bool includeAlphaChannel) { 42 | const std::string key = texIndicesKey(ixVec, tag); 43 | auto iter = textureByIndicesKey.find(key); 44 | if (iter != textureByIndicesKey.end()) { 45 | return iter->second; 46 | } 47 | 48 | int width = -1, height = -1; 49 | std::string mergedFilename = tag; 50 | std::vector texes{}; 51 | for (const int rawTexIx : ixVec) { 52 | TexInfo info(rawTexIx); 53 | if (rawTexIx >= 0) { 54 | const RawTexture& rawTex = raw.GetTexture(rawTexIx); 55 | const std::string& fileLoc = rawTex.fileLocation; 56 | const std::string& name = FileUtils::GetFileBase(FileUtils::GetFileName(fileLoc)); 57 | if (!fileLoc.empty()) { 58 | info.pixels = stbi_load(fileLoc.c_str(), &info.width, &info.height, &info.channels, 0); 59 | if (!info.pixels) { 60 | fmt::printf("Warning: merge texture [%d](%s) could not be loaded.\n", rawTexIx, name); 61 | } else { 62 | if (width < 0) { 63 | width = info.width; 64 | height = info.height; 65 | } else if (width != info.width || height != info.height) { 66 | fmt::printf( 67 | "Warning: texture %s (%d, %d) can't be merged with previous texture(s) of dimension (%d, %d)\n", 68 | name, 69 | info.width, 70 | info.height, 71 | width, 72 | height); 73 | // this is bad enough that we abort the whole merge 74 | return nullptr; 75 | } 76 | mergedFilename += "_" + name; 77 | } 78 | } 79 | } 80 | texes.push_back(info); 81 | } 82 | // at the moment, the best choice of filename is also the best choice of name 83 | const std::string mergedName = mergedFilename; 84 | 85 | if (width < 0) { 86 | // no textures to merge; bail 87 | return nullptr; 88 | } 89 | // TODO: which channel combinations make sense in input files? 90 | 91 | // write 3 or 4 channels depending on whether or not we need transparency 92 | int channels = includeAlphaChannel ? 4 : 3; 93 | 94 | std::vector mergedPixels(static_cast(channels * width * height)); 95 | for (int xx = 0; xx < width; xx++) { 96 | for (int yy = 0; yy < height; yy++) { 97 | std::vector pixels(texes.size()); 98 | std::vector pixelPointers(texes.size(), nullptr); 99 | for (int jj = 0; jj < texes.size(); jj++) { 100 | const TexInfo& tex = texes[jj]; 101 | // each texture's structure will depend on its channel count 102 | int ii = tex.channels * (xx + yy * width); 103 | int kk = 0; 104 | if (tex.pixels != nullptr) { 105 | for (; kk < tex.channels; kk++) { 106 | pixels[jj][kk] = tex.pixels[ii++] / 255.0f; 107 | } 108 | } 109 | for (; kk < pixels[jj].size(); kk++) { 110 | pixels[jj][kk] = 1.0f; 111 | } 112 | pixelPointers[jj] = &pixels[jj]; 113 | } 114 | const pixel merged = computePixel(pixelPointers); 115 | int ii = channels * (xx + yy * width); 116 | for (int jj = 0; jj < channels; jj++) { 117 | mergedPixels[ii + jj] = static_cast(fmax(0, fmin(255.0f, merged[jj] * 255.0f))); 118 | } 119 | } 120 | } 121 | 122 | std::vector imgBuffer; 123 | int res = stbi_write_png_to_func( 124 | WriteToVectorContext, 125 | &imgBuffer, 126 | width, 127 | height, 128 | channels, 129 | mergedPixels.data(), 130 | width * channels); 131 | if (!res) { 132 | fmt::printf("Warning: failed to generate merge texture '%s'.\n", mergedFilename); 133 | return nullptr; 134 | } 135 | 136 | ImageData* image; 137 | if (options.outputBinary && !options.separateTextures) { 138 | const auto bufferView = 139 | gltf.AddRawBufferView(*gltf.defaultBuffer, imgBuffer.data(), to_uint32(imgBuffer.size())); 140 | image = new ImageData(mergedName, *bufferView, "image/png"); 141 | } else { 142 | const std::string imageFilename = mergedFilename + (".png"); 143 | const std::string imagePath = outputFolder + imageFilename; 144 | FILE* fp = boost::nowide::fopen(imagePath.c_str(), "wb"); 145 | if (fp == nullptr) { 146 | fmt::printf("Warning:: Couldn't write file '%s' for writing.\n", imagePath); 147 | return nullptr; 148 | } 149 | 150 | if (fwrite(imgBuffer.data(), imgBuffer.size(), 1, fp) != 1) { 151 | fmt::printf( 152 | "Warning: Failed to write %lu bytes to file '%s'.\n", imgBuffer.size(), imagePath); 153 | fclose(fp); 154 | return nullptr; 155 | } 156 | fclose(fp); 157 | if (verboseOutput) { 158 | fmt::printf("Wrote %lu bytes to texture '%s'.\n", imgBuffer.size(), imagePath); 159 | } 160 | image = new ImageData(mergedName, imageFilename); 161 | } 162 | std::shared_ptr texDat = gltf.textures.hold( 163 | new TextureData(mergedName, *gltf.defaultSampler, *gltf.images.hold(image))); 164 | textureByIndicesKey.insert(std::make_pair(key, texDat)); 165 | return texDat; 166 | } 167 | 168 | /** Create a new TextureData for the given RawTexture index, or return a previously created one. */ 169 | std::shared_ptr TextureBuilder::simple(int rawTexIndex, const std::string& tag) { 170 | const std::string key = texIndicesKey({rawTexIndex}, tag); 171 | auto iter = textureByIndicesKey.find(key); 172 | if (iter != textureByIndicesKey.end()) { 173 | return iter->second; 174 | } 175 | 176 | const RawTexture& rawTexture = raw.GetTexture(rawTexIndex); 177 | const std::string textureName = FileUtils::GetFileBase(rawTexture.name); 178 | const std::string relativeFilename = FileUtils::GetFileName(rawTexture.fileLocation); 179 | ImageData* image = nullptr; 180 | if (options.outputBinary) { 181 | auto bufferView = gltf.AddBufferViewForFile(*gltf.defaultBuffer, rawTexture.fileLocation); 182 | if (bufferView) { 183 | const auto& suffix = FileUtils::GetFileSuffix(rawTexture.fileLocation); 184 | std::string mimeType; 185 | if (suffix) { 186 | mimeType = ImageUtils::suffixToMimeType(suffix.value()); 187 | } else { 188 | mimeType = "image/jpeg"; 189 | fmt::printf( 190 | "Warning: Can't deduce mime type of texture '%s'; using %s.\n", 191 | rawTexture.fileLocation, 192 | mimeType); 193 | } 194 | image = new ImageData(relativeFilename, *bufferView, mimeType); 195 | } 196 | 197 | } else if (!relativeFilename.empty()) { 198 | std::string outputPath = outputFolder + "/" + relativeFilename; 199 | auto dstAbs = FileUtils::GetAbsolutePath(outputPath); 200 | image = new ImageData(relativeFilename, relativeFilename); 201 | auto srcAbs = FileUtils::GetAbsolutePath(rawTexture.fileLocation); 202 | if (!FileUtils::FileExists(outputPath) && srcAbs != dstAbs) { 203 | if (FileUtils::CopyFile(rawTexture.fileLocation, outputPath, true)) { 204 | if (verboseOutput) { 205 | fmt::printf("Copied texture '%s' to output folder: %s\n", textureName, outputPath); 206 | } 207 | } else { 208 | // no point commenting further on read/write error; CopyFile() does enough of that, and we 209 | // certainly want to to add an image struct to the glTF JSON, with the correct relative 210 | // path reference, even if the copy failed. 211 | } 212 | } 213 | } 214 | if (!image) { 215 | return nullptr; 216 | } 217 | 218 | std::shared_ptr texDat = gltf.textures.hold( 219 | new TextureData(textureName, *gltf.defaultSampler, *gltf.images.hold(image))); 220 | textureByIndicesKey.insert(std::make_pair(key, texDat)); 221 | return texDat; 222 | } 223 | -------------------------------------------------------------------------------- /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 | if (!outputFolder.empty()) { 31 | if (outputFolder[outputFolder.size() - 1] == '/') { 32 | this->outputFolder = outputFolder.substr(0, outputFolder.size() - 1); 33 | } 34 | } 35 | } 36 | ~TextureBuilder() {} 37 | 38 | std::shared_ptr combine( 39 | const std::vector& ixVec, 40 | const std::string& tag, 41 | const pixel_merger& mergeFunction, 42 | bool transparency); 43 | 44 | std::shared_ptr simple(int rawTexIndex, const std::string& tag); 45 | 46 | static std::string texIndicesKey(const std::vector& ixVec, const std::string& tag) { 47 | std::string result = tag; 48 | for (int ix : ixVec) { 49 | result += "_" + std::to_string(ix); 50 | } 51 | return result; 52 | }; 53 | 54 | static std::string describeChannel(int channels) { 55 | switch (channels) { 56 | case 1: 57 | return "G"; 58 | case 2: 59 | return "GA"; 60 | case 3: 61 | return "RGB"; 62 | case 4: 63 | return "RGBA"; 64 | default: 65 | return fmt::format("?%d?", channels); 66 | } 67 | }; 68 | 69 | static void WriteToVectorContext(void* context, void* data, int size) { 70 | auto* vec = static_cast*>(context); 71 | for (int ii = 0; ii < size; ii++) { 72 | vec->push_back(((uint8_t*)data)[ii]); 73 | } 74 | } 75 | 76 | private: 77 | const RawModel& raw; 78 | const GltfOptions& options; 79 | std::string outputFolder; 80 | GltfModel& gltf; 81 | 82 | std::map> textureByIndicesKey; 83 | }; 84 | -------------------------------------------------------------------------------- /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 | sparse(false) {} 20 | 21 | AccessorData::AccessorData( 22 | const AccessorData& baseAccessor, 23 | const BufferViewData& sparseIdxBufferView, 24 | const BufferViewData& sparseDataBufferView, 25 | GLType type, 26 | std::string name) 27 | : Holdable(), 28 | bufferView(baseAccessor.bufferView), 29 | type(std::move(type)), 30 | byteOffset(baseAccessor.byteOffset), 31 | count(baseAccessor.count), 32 | name(name), 33 | sparse(true), 34 | sparseIdxCount(sparseIdxBufferView.count), 35 | sparseIdxBufferView(sparseIdxBufferView.ix), 36 | sparseIdxBufferViewOffset(0), 37 | sparseIdxBufferViewType(0), 38 | sparseDataBufferView(sparseDataBufferView.ix), 39 | sparseDataBufferViewOffset(0) {} 40 | 41 | AccessorData::AccessorData(GLType type) 42 | : Holdable(), bufferView(-1), type(std::move(type)), byteOffset(0), count(0) {} 43 | 44 | json AccessorData::serialize() const { 45 | json result{ 46 | {"componentType", type.componentType.glType}, {"type", type.dataType}, {"count", count}}; 47 | if (bufferView >= 0 && !sparse) { 48 | result["bufferView"] = bufferView; 49 | result["byteOffset"] = byteOffset; 50 | } 51 | if (!min.empty()) { 52 | result["min"] = min; 53 | } 54 | if (!max.empty()) { 55 | result["max"] = max; 56 | } 57 | if (sparse) { 58 | json sparseData = {{"count", sparseIdxCount}}; 59 | sparseData["indices"] = { 60 | {"bufferView", sparseIdxBufferView}, 61 | {"byteOffset", sparseIdxBufferViewOffset}, 62 | {"componentType", sparseIdxBufferViewType}}; 63 | 64 | sparseData["values"] = { 65 | {"bufferView", sparseDataBufferView}, {"byteOffset", sparseDataBufferViewOffset}}; 66 | 67 | result["sparse"] = sparseData; 68 | } 69 | if (name.length() > 0) { 70 | result["name"] = name; 71 | } 72 | return result; 73 | } 74 | -------------------------------------------------------------------------------- /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 | AccessorData( 17 | const AccessorData& baseAccessor, 18 | const BufferViewData& sparseIdxBufferView, 19 | const BufferViewData& sparseDataBufferView, 20 | GLType type, 21 | std::string name); 22 | 23 | json serialize() const override; 24 | 25 | unsigned int byteLength() const { 26 | return type.byteStride() * count; 27 | } 28 | 29 | const int bufferView; 30 | const GLType type; 31 | 32 | unsigned int byteOffset; 33 | unsigned int count; 34 | std::vector min; 35 | std::vector max; 36 | std::string name; 37 | 38 | bool sparse; 39 | int sparseIdxCount; 40 | int sparseIdxBufferView; 41 | int sparseIdxBufferViewOffset; 42 | int sparseIdxBufferViewType; 43 | int sparseDataBufferView; 44 | int sparseDataBufferViewOffset; 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{ 42 | {"sampler", data.ix}, 43 | { 44 | "target", 45 | {{"node", data.node}, {"path", data.path}}, 46 | }}; 47 | } 48 | 49 | void to_json(json& j, const AnimationData::sampler_t& data) { 50 | j = json{ 51 | {"input", data.time}, 52 | {"interpolation", "LINEAR"}, 53 | {"output", data.output}, 54 | }; 55 | } 56 | -------------------------------------------------------------------------------- /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 | template 25 | void appendAsBinaryArray(const std::vector& in, std::vector& out, GLType type) { 26 | const unsigned int stride = type.byteStride(); 27 | const size_t offset = out.size(); 28 | const size_t count = in.size(); 29 | 30 | this->byteLength = stride * count; 31 | this->count = count; 32 | 33 | out.resize(offset + count * stride); 34 | for (int ii = 0; ii < count; ii++) { 35 | type.write(&out[offset + ii * stride], in[ii]); 36 | } 37 | } 38 | 39 | const unsigned int buffer; 40 | const unsigned int byteOffset; 41 | const GL_ArrayType target; 42 | 43 | unsigned int count = 0; 44 | unsigned int byteLength = 0; 45 | }; 46 | -------------------------------------------------------------------------------- /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 | bool isDoubleSided, 77 | const RawShadingModel shadingModel, 78 | const TextureData* normalTexture, 79 | const TextureData* occlusionTexture, 80 | const TextureData* emissiveTexture, 81 | const Vec3f& emissiveFactor, 82 | std::shared_ptr const khrCmnConstantMaterial, 83 | std::shared_ptr const pbrMetallicRoughness) 84 | : Holdable(), 85 | name(std::move(name)), 86 | shadingModel(shadingModel), 87 | isTransparent(isTransparent), 88 | isDoubleSided(isDoubleSided), 89 | normalTexture(Tex::ref(normalTexture)), 90 | occlusionTexture(Tex::ref(occlusionTexture)), 91 | emissiveTexture(Tex::ref(emissiveTexture)), 92 | emissiveFactor(clamp(emissiveFactor)), 93 | khrCmnConstantMaterial(khrCmnConstantMaterial), 94 | pbrMetallicRoughness(pbrMetallicRoughness) {} 95 | 96 | json MaterialData::serialize() const { 97 | json result = { 98 | {"name", name}, 99 | {"alphaMode", isTransparent ? "BLEND" : "OPAQUE"}, 100 | {"doubleSided", isDoubleSided}, 101 | {"extras", 102 | {{"fromFBX", 103 | {{"shadingModel", Describe(shadingModel)}, 104 | {"isTruePBR", shadingModel == RAW_SHADING_MODEL_PBR_MET_ROUGH}}}}}}; 105 | 106 | if (normalTexture != nullptr) { 107 | result["normalTexture"] = *normalTexture; 108 | } 109 | if (occlusionTexture != nullptr) { 110 | result["occlusionTexture"] = *occlusionTexture; 111 | } 112 | if (emissiveTexture != nullptr) { 113 | result["emissiveTexture"] = *emissiveTexture; 114 | } 115 | if (emissiveFactor.LengthSquared() > 0) { 116 | result["emissiveFactor"] = toStdVec(emissiveFactor); 117 | } 118 | if (pbrMetallicRoughness != nullptr) { 119 | result["pbrMetallicRoughness"] = *pbrMetallicRoughness; 120 | } 121 | if (khrCmnConstantMaterial != nullptr) { 122 | json extensions = {}; 123 | extensions[KHR_MATERIALS_CMN_UNLIT] = *khrCmnConstantMaterial; 124 | result["extensions"] = extensions; 125 | } 126 | 127 | for (const auto& i : userProperties) { 128 | auto& prop_map = result["extras"]["fromFBX"]["userProperties"]; 129 | 130 | json j = json::parse(i); 131 | for (const auto& k : json::iterator_wrapper(j)) { 132 | prop_map[k.key()] = k.value(); 133 | } 134 | } 135 | 136 | return result; 137 | } 138 | -------------------------------------------------------------------------------- /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 | bool isDoubleSided, 47 | RawShadingModel shadingModel, 48 | const TextureData* normalTexture, 49 | const TextureData* occlusionTexture, 50 | const TextureData* emissiveTexture, 51 | const Vec3f& emissiveFactor, 52 | std::shared_ptr const khrCmnConstantMaterial, 53 | std::shared_ptr const pbrMetallicRoughness); 54 | 55 | json serialize() const override; 56 | 57 | const std::string name; 58 | const RawShadingModel shadingModel; 59 | const bool isTransparent; 60 | const bool isDoubleSided; 61 | const std::unique_ptr normalTexture; 62 | const std::unique_ptr occlusionTexture; 63 | const std::unique_ptr emissiveTexture; 64 | const Vec3f emissiveFactor; 65 | 66 | const std::shared_ptr khrCmnConstantMaterial; 67 | const std::shared_ptr pbrMetallicRoughness; 68 | 69 | std::vector userProperties; 70 | }; 71 | 72 | void to_json(json& j, const Tex& data); 73 | void to_json(json& j, const KHRCmnUnlitMaterial& d); 74 | void to_json(json& j, const PBRMetallicRoughness& d); 75 | -------------------------------------------------------------------------------- /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() && jsonTargetNamesArray.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"] = { 77 | {KHR_DRACO_MESH_COMPRESSION, 78 | {{"bufferView", d.dracoBufferView}, {"attributes", d.dracoAttributes}}}}; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /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 | componentCount, 45 | attribute.dracoComponentType, 46 | false, 47 | componentCount * draco::DataTypeLength(attribute.dracoComponentType)); 48 | 49 | const int dracoAttId = dracoMesh->AddAttribute(att, true, to_uint32(attribArr.size())); 50 | draco::PointAttribute* attPtr = dracoMesh->attribute(dracoAttId); 51 | 52 | std::vector buf(sizeof(T)); 53 | for (uint32_t ii = 0; ii < attribArr.size(); ii++) { 54 | uint8_t* ptr = &buf[0]; 55 | attribute.glType.write(ptr, attribArr[ii]); 56 | attPtr->SetAttributeValue(attPtr->mapped_index(draco::PointIndex(ii)), ptr); 57 | } 58 | 59 | dracoAttributes[attribute.gltfName] = dracoAttId; 60 | } 61 | 62 | template 63 | void AddDracoArrayAttrib( 64 | const AttributeArrayDefinition attribute, 65 | const std::vector& attribArr) { 66 | draco::PointAttribute att; 67 | int8_t componentCount = attribute.glType.count; 68 | att.Init( 69 | attribute.dracoAttribute, 70 | componentCount, 71 | attribute.dracoComponentType, 72 | false, 73 | componentCount * draco::DataTypeLength(attribute.dracoComponentType)); 74 | 75 | const int dracoAttId = dracoMesh->AddAttribute(att, true, attribArr.size()); 76 | draco::PointAttribute* attPtr = dracoMesh->attribute(dracoAttId); 77 | 78 | std::vector buf(sizeof(T)); 79 | for (uint32_t ii = 0; ii < attribArr.size(); ii++) { 80 | uint8_t* ptr = &buf[0]; 81 | attribute.glType.write(ptr, attribArr[ii]); 82 | attPtr->SetAttributeValue(attPtr->mapped_index(draco::PointIndex(ii)), ptr); 83 | } 84 | 85 | dracoAttributes[attribute.gltfName] = dracoAttId; 86 | } 87 | 88 | void NoteDracoBuffer(const BufferViewData& data); 89 | 90 | const int indices; 91 | const unsigned int material; 92 | const MeshMode mode; 93 | 94 | std::vector> targetAccessors{}; 95 | std::vector targetNames{}; 96 | 97 | std::map attributes; 98 | std::map dracoAttributes; 99 | 100 | std::shared_ptr dracoMesh; 101 | int dracoBufferView; 102 | }; 103 | 104 | void to_json(json& j, const PrimitiveData& d); 105 | -------------------------------------------------------------------------------- /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 | isExtraSkin(false) {} 23 | 24 | SkinData::SkinData( 25 | const std::vector joints, 26 | bool isExtraSkin) 27 | : Holdable(), 28 | joints(joints), 29 | inverseBindMatrices(0), 30 | skeletonRootNode(0), 31 | isExtraSkin(true) {} 32 | 33 | json SkinData::serialize() const { 34 | if (isExtraSkin) { 35 | return {{"joints", joints}}; 36 | } 37 | return { 38 | {"joints", joints}, 39 | {"inverseBindMatrices", inverseBindMatrices}, 40 | {"skeleton", skeletonRootNode}}; 41 | } 42 | -------------------------------------------------------------------------------- /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 | SkinData( 20 | const std::vector joints, 21 | bool isExtraSkin); 22 | 23 | json serialize() const override; 24 | 25 | const std::vector joints; 26 | const uint32_t skeletonRootNode; 27 | const uint32_t inverseBindMatrices; 28 | const bool isExtraSkin; 29 | }; 30 | -------------------------------------------------------------------------------- /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 RawVertexSkinningInfo { 42 | int jointIndex; 43 | float jointWeight; 44 | 45 | bool operator>(const RawVertexSkinningInfo& rjw) const { 46 | return jointWeight > rjw.jointWeight; 47 | } 48 | }; 49 | 50 | struct RawVertex { 51 | Vec3f position{0.0f}; 52 | Vec3f normal{0.0f}; 53 | Vec3f binormal{0.0f}; 54 | Vec4f tangent{0.0f}; 55 | Vec4f color{0.0f}; 56 | Vec2f uv0{0.0f}; 57 | Vec2f uv1{0.0f}; 58 | std::vector jointIndices; 59 | std::vector jointWeights; 60 | 61 | std::vector skinningInfo; 62 | // end of members that directly correspond to vertex attributes 63 | 64 | // if this vertex participates in a blend shape setup, the surfaceIx of its dedicated mesh; 65 | // otherwise, -1 66 | int blendSurfaceIx = -1; 67 | // the size of this vector is always identical to the size of the corresponding 68 | // RawSurface.blendChannels 69 | std::vector blends; 70 | 71 | bool polarityUv0 = false; 72 | bool pad1 = false; 73 | bool pad2 = false; 74 | bool pad3 = false; 75 | 76 | bool operator==(const RawVertex& other) const; 77 | size_t Difference(const RawVertex& other) const; 78 | }; 79 | 80 | class VertexHasher { 81 | public: 82 | size_t operator()(const RawVertex& v) const; 83 | }; 84 | 85 | struct RawTriangle { 86 | int verts[3]; 87 | int materialIndex; 88 | int surfaceIndex; 89 | }; 90 | 91 | enum RawShadingModel { 92 | RAW_SHADING_MODEL_UNKNOWN = -1, 93 | RAW_SHADING_MODEL_CONSTANT, 94 | RAW_SHADING_MODEL_LAMBERT, 95 | RAW_SHADING_MODEL_BLINN, 96 | RAW_SHADING_MODEL_PHONG, 97 | RAW_SHADING_MODEL_PBR_MET_ROUGH, 98 | RAW_SHADING_MODEL_MAX 99 | }; 100 | 101 | inline std::string Describe(RawShadingModel model) { 102 | switch (model) { 103 | case RAW_SHADING_MODEL_UNKNOWN: 104 | return ""; 105 | case RAW_SHADING_MODEL_CONSTANT: 106 | return "Constant"; 107 | case RAW_SHADING_MODEL_LAMBERT: 108 | return "Lambert"; 109 | case RAW_SHADING_MODEL_BLINN: 110 | return "Blinn"; 111 | case RAW_SHADING_MODEL_PHONG: 112 | return "Phong"; 113 | case RAW_SHADING_MODEL_PBR_MET_ROUGH: 114 | return "Metallic/Roughness"; 115 | case RAW_SHADING_MODEL_MAX: 116 | default: 117 | return ""; 118 | } 119 | } 120 | 121 | enum RawTextureUsage { 122 | RAW_TEXTURE_USAGE_NONE = -1, 123 | RAW_TEXTURE_USAGE_AMBIENT, 124 | RAW_TEXTURE_USAGE_DIFFUSE, 125 | RAW_TEXTURE_USAGE_NORMAL, 126 | RAW_TEXTURE_USAGE_SPECULAR, 127 | RAW_TEXTURE_USAGE_SHININESS, 128 | RAW_TEXTURE_USAGE_EMISSIVE, 129 | RAW_TEXTURE_USAGE_REFLECTION, 130 | RAW_TEXTURE_USAGE_ALBEDO, 131 | RAW_TEXTURE_USAGE_OCCLUSION, 132 | RAW_TEXTURE_USAGE_ROUGHNESS, 133 | RAW_TEXTURE_USAGE_METALLIC, 134 | RAW_TEXTURE_USAGE_MAX 135 | }; 136 | 137 | inline std::string Describe(RawTextureUsage usage) { 138 | switch (usage) { 139 | case RAW_TEXTURE_USAGE_NONE: 140 | return ""; 141 | case RAW_TEXTURE_USAGE_AMBIENT: 142 | return "ambient"; 143 | case RAW_TEXTURE_USAGE_DIFFUSE: 144 | return "diffuse"; 145 | case RAW_TEXTURE_USAGE_NORMAL: 146 | return "normal"; 147 | case RAW_TEXTURE_USAGE_SPECULAR: 148 | return "specular"; 149 | case RAW_TEXTURE_USAGE_SHININESS: 150 | return "shininess"; 151 | case RAW_TEXTURE_USAGE_EMISSIVE: 152 | return "emissive"; 153 | case RAW_TEXTURE_USAGE_REFLECTION: 154 | return "reflection"; 155 | case RAW_TEXTURE_USAGE_OCCLUSION: 156 | return "occlusion"; 157 | case RAW_TEXTURE_USAGE_ROUGHNESS: 158 | return "roughness"; 159 | case RAW_TEXTURE_USAGE_METALLIC: 160 | return "metallic"; 161 | case RAW_TEXTURE_USAGE_MAX: 162 | default: 163 | return "unknown"; 164 | } 165 | }; 166 | 167 | enum RawTextureOcclusion { RAW_TEXTURE_OCCLUSION_OPAQUE, RAW_TEXTURE_OCCLUSION_TRANSPARENT }; 168 | 169 | struct RawTexture { 170 | std::string name; // logical name in FBX file 171 | int width; 172 | int height; 173 | int mipLevels; 174 | RawTextureUsage usage; 175 | RawTextureOcclusion occlusion; 176 | std::string fileName; // original filename in FBX file 177 | std::string fileLocation; // inferred path in local filesystem, or "" 178 | }; 179 | 180 | enum RawMaterialType { 181 | RAW_MATERIAL_TYPE_OPAQUE, 182 | RAW_MATERIAL_TYPE_TRANSPARENT, 183 | RAW_MATERIAL_TYPE_SKINNED_OPAQUE, 184 | RAW_MATERIAL_TYPE_SKINNED_TRANSPARENT, 185 | }; 186 | 187 | struct RawMatProps { 188 | explicit RawMatProps(RawShadingModel shadingModel) : shadingModel(shadingModel) {} 189 | const RawShadingModel shadingModel; 190 | 191 | virtual bool operator!=(const RawMatProps& other) const { 192 | return !(*this == other); 193 | } 194 | virtual bool operator==(const RawMatProps& other) const { 195 | return shadingModel == other.shadingModel; 196 | }; 197 | }; 198 | 199 | struct RawTraditionalMatProps : RawMatProps { 200 | RawTraditionalMatProps( 201 | RawShadingModel shadingModel, 202 | const Vec3f&& ambientFactor, 203 | const Vec4f&& diffuseFactor, 204 | const Vec3f&& emissiveFactor, 205 | const Vec3f&& specularFactor, 206 | const float shininess) 207 | : RawMatProps(shadingModel), 208 | ambientFactor(ambientFactor), 209 | diffuseFactor(diffuseFactor), 210 | emissiveFactor(emissiveFactor), 211 | specularFactor(specularFactor), 212 | shininess(shininess) {} 213 | 214 | const Vec3f ambientFactor; 215 | const Vec4f diffuseFactor; 216 | const Vec3f emissiveFactor; 217 | const Vec3f specularFactor; 218 | const float shininess; 219 | 220 | bool operator==(const RawMatProps& other) const override { 221 | if (RawMatProps::operator==(other)) { 222 | const auto& typed = (RawTraditionalMatProps&)other; 223 | return ambientFactor == typed.ambientFactor && diffuseFactor == typed.diffuseFactor && 224 | specularFactor == typed.specularFactor && emissiveFactor == typed.emissiveFactor && 225 | shininess == typed.shininess; 226 | } 227 | return false; 228 | } 229 | }; 230 | 231 | struct RawMetRoughMatProps : RawMatProps { 232 | RawMetRoughMatProps( 233 | RawShadingModel shadingModel, 234 | const Vec4f&& diffuseFactor, 235 | const Vec3f&& emissiveFactor, 236 | float emissiveIntensity, 237 | float metallic, 238 | float roughness, 239 | bool invertRoughnessMap) 240 | : RawMatProps(shadingModel), 241 | diffuseFactor(diffuseFactor), 242 | emissiveFactor(emissiveFactor), 243 | emissiveIntensity(emissiveIntensity), 244 | metallic(metallic), 245 | roughness(roughness), 246 | invertRoughnessMap(invertRoughnessMap) {} 247 | const Vec4f diffuseFactor; 248 | const Vec3f emissiveFactor; 249 | const float emissiveIntensity; 250 | const float metallic; 251 | const float roughness; 252 | const bool invertRoughnessMap; 253 | 254 | bool operator==(const RawMatProps& other) const override { 255 | if (RawMatProps::operator==(other)) { 256 | const auto& typed = (RawMetRoughMatProps&)other; 257 | return diffuseFactor == typed.diffuseFactor && emissiveFactor == typed.emissiveFactor && 258 | emissiveIntensity == typed.emissiveIntensity && metallic == typed.metallic && 259 | roughness == typed.roughness; 260 | } 261 | return false; 262 | } 263 | }; 264 | 265 | struct RawMaterial { 266 | long id; 267 | std::string name; 268 | RawMaterialType type; 269 | std::shared_ptr info; 270 | int textures[RAW_TEXTURE_USAGE_MAX]; 271 | std::vector userProperties; 272 | bool isDoubleSided; 273 | }; 274 | 275 | enum RawLightType { 276 | RAW_LIGHT_TYPE_DIRECTIONAL, 277 | RAW_LIGHT_TYPE_POINT, 278 | RAW_LIGHT_TYPE_SPOT, 279 | }; 280 | 281 | struct RawLight { 282 | std::string name; 283 | RawLightType type; 284 | Vec3f color; 285 | float intensity; 286 | float innerConeAngle; // only meaningful for spot 287 | float outerConeAngle; // only meaningful for spot 288 | }; 289 | 290 | struct RawBlendChannel { 291 | float defaultDeform; 292 | bool hasNormals; 293 | bool hasTangents; 294 | std::string name; 295 | }; 296 | 297 | struct RawSurface { 298 | long id; 299 | std::string name; // The name of this surface 300 | long skeletonRootId; // The id of the root node of the skeleton. 301 | Bounds bounds; 302 | std::vector jointIds; 303 | std::vector jointGeometryMins; 304 | std::vector jointGeometryMaxs; 305 | std::vector inverseBindMatrices; 306 | std::vector blendChannels; 307 | bool discrete; 308 | }; 309 | 310 | struct RawChannel { 311 | int nodeIndex; 312 | std::vector translations; 313 | std::vector rotations; 314 | std::vector scales; 315 | std::vector weights; 316 | }; 317 | 318 | struct RawAnimation { 319 | std::string name; 320 | std::vector times; 321 | std::vector channels; 322 | }; 323 | 324 | struct RawCamera { 325 | std::string name; 326 | long nodeId; 327 | 328 | enum { CAMERA_MODE_PERSPECTIVE, CAMERA_MODE_ORTHOGRAPHIC } mode; 329 | 330 | struct { 331 | float aspectRatio; 332 | float fovDegreesX; 333 | float fovDegreesY; 334 | float nearZ; 335 | float farZ; 336 | } perspective; 337 | 338 | struct { 339 | float magX; 340 | float magY; 341 | float nearZ; 342 | float farZ; 343 | } orthographic; 344 | }; 345 | 346 | struct RawNode { 347 | bool isJoint; 348 | long id; 349 | std::string name; 350 | long parentId; 351 | std::vector childIds; 352 | Vec3f translation; 353 | Quatf rotation; 354 | Vec3f scale; 355 | long surfaceId; 356 | long lightIx; 357 | std::vector userProperties; 358 | int extraSkinIx; 359 | }; 360 | 361 | class RawModel { 362 | public: 363 | RawModel(); 364 | 365 | // Add geometry. 366 | void AddVertexAttribute(const RawVertexAttribute attrib); 367 | int AddVertex(const RawVertex& vertex); 368 | int AddTriangle( 369 | const int v0, 370 | const int v1, 371 | const int v2, 372 | const int materialIndex, 373 | const int surfaceIndex); 374 | int AddTexture( 375 | const std::string& name, 376 | const std::string& fileName, 377 | const std::string& fileLocation, 378 | RawTextureUsage usage); 379 | int AddMaterial(const RawMaterial& material); 380 | int AddMaterial( 381 | const long id, 382 | const char* name, 383 | const RawMaterialType materialType, 384 | const int textures[RAW_TEXTURE_USAGE_MAX], 385 | std::shared_ptr materialInfo, 386 | const std::vector& userProperties, 387 | const bool isDoubleSided); 388 | int AddLight( 389 | const char* name, 390 | RawLightType lightType, 391 | Vec3f color, 392 | float intensity, 393 | float innerConeAngle, 394 | float outerConeAngle); 395 | int AddSurface(const RawSurface& suface); 396 | int AddSurface(const char* name, long surfaceId); 397 | int AddAnimation(const RawAnimation& animation); 398 | int AddCameraPerspective( 399 | const char* name, 400 | const long nodeId, 401 | const float aspectRatio, 402 | const float fovDegreesX, 403 | const float fovDegreesY, 404 | const float nearZ, 405 | const float farZ); 406 | int AddCameraOrthographic( 407 | const char* name, 408 | const long nodeId, 409 | const float magX, 410 | const float magY, 411 | const float nearZ, 412 | const float farZ); 413 | int AddNode(const RawNode& node); 414 | int AddNode(const long id, const char* name, const long parentId, const int extraSkinIx); 415 | void SetRootNode(const long nodeId) { 416 | rootNodeId = nodeId; 417 | } 418 | const long GetRootNode() const { 419 | return rootNodeId; 420 | } 421 | 422 | // Remove unused vertices, textures or materials after removing vertex attributes, textures, 423 | // materials or surfaces. 424 | void Condense(const int maxSkinningWeights, const bool normalizeWeights); 425 | 426 | void TransformGeometry(ComputeNormalsOption); 427 | 428 | void TransformTextures(const std::vector>& transforms); 429 | 430 | size_t CalculateNormals(bool); 431 | 432 | // Get the attributes stored per vertex. 433 | int GetVertexAttributes() const { 434 | return vertexAttributes; 435 | } 436 | 437 | // Iterate over the vertices. 438 | int GetVertexCount() const { 439 | return (int)vertices.size(); 440 | } 441 | 442 | int GetGlobalWeightCount() const { 443 | return globalMaxWeights; 444 | } 445 | 446 | const RawVertex& GetVertex(const int index) const { 447 | return vertices[index]; 448 | } 449 | 450 | // Iterate over the triangles. 451 | int GetTriangleCount() const { 452 | return (int)triangles.size(); 453 | } 454 | const RawTriangle& GetTriangle(const int index) const { 455 | return triangles[index]; 456 | } 457 | 458 | // Iterate over the textures. 459 | int GetTextureCount() const { 460 | return (int)textures.size(); 461 | } 462 | const RawTexture& GetTexture(const int index) const { 463 | return textures[index]; 464 | } 465 | 466 | // Iterate over the materials. 467 | int GetMaterialCount() const { 468 | return (int)materials.size(); 469 | } 470 | const RawMaterial& GetMaterial(const int index) const { 471 | return materials[index]; 472 | } 473 | 474 | // Iterate over the surfaces. 475 | int GetSurfaceCount() const { 476 | return (int)surfaces.size(); 477 | } 478 | const RawSurface& GetSurface(const int index) const { 479 | return surfaces[index]; 480 | } 481 | RawSurface& GetSurface(const int index) { 482 | return surfaces[index]; 483 | } 484 | int GetSurfaceById(const long id) const; 485 | 486 | // Iterate over the animations. 487 | int GetAnimationCount() const { 488 | return (int)animations.size(); 489 | } 490 | const RawAnimation& GetAnimation(const int index) const { 491 | return animations[index]; 492 | } 493 | 494 | // Iterate over the cameras. 495 | int GetCameraCount() const { 496 | return (int)cameras.size(); 497 | } 498 | const RawCamera& GetCamera(const int index) const { 499 | return cameras[index]; 500 | } 501 | 502 | // Iterate over the lights. 503 | int GetLightCount() const { 504 | return (int)lights.size(); 505 | } 506 | const RawLight& GetLight(const int index) const { 507 | return lights[index]; 508 | } 509 | 510 | // Iterate over the nodes. 511 | int GetNodeCount() const { 512 | return (int)nodes.size(); 513 | } 514 | const RawNode& GetNode(const int index) const { 515 | return nodes[index]; 516 | } 517 | RawNode& GetNode(const int index) { 518 | return nodes[index]; 519 | } 520 | int GetNodeById(const long nodeId) const; 521 | 522 | // Create individual attribute arrays. 523 | // Returns true if the vertices store the particular attribute. 524 | template 525 | void GetAttributeArray(std::vector<_attrib_type_>& out, const _attrib_type_ RawVertex::*ptr) 526 | const; 527 | 528 | // Create individual attribute arrays, with the source as an array. 529 | // Returns true if the vertices store the particular attribute. 530 | template 531 | void GetArrayAttributeArray( 532 | std::vector<_attrib_type_>& out, 533 | const std::vector<_attrib_type_> RawVertex::*ptr, 534 | const int arrayOffset) const; 535 | 536 | // Create an array with a raw model for each material. 537 | // Multiple surfaces with the same material will turn into a single model. 538 | // However, surfaces that are marked as 'discrete' will turn into separate models. 539 | void CreateMaterialModels( 540 | std::vector& materialModels, 541 | bool shortIndices, 542 | const int keepAttribs, 543 | const bool forceDiscrete) const; 544 | 545 | int CreateExtraSkinIndex() { 546 | int ret = nextExtraSkinIx; 547 | nextExtraSkinIx++; 548 | return ret; 549 | } 550 | int GetExtraSkinCount() const { 551 | return nextExtraSkinIx; 552 | } 553 | 554 | private: 555 | Vec3f getFaceNormal(int verts[3]) const; 556 | 557 | int nextExtraSkinIx; 558 | long rootNodeId; 559 | int vertexAttributes; 560 | int globalMaxWeights; 561 | std::unordered_map vertexHash; 562 | std::vector vertices; 563 | std::vector triangles; 564 | std::vector textures; 565 | std::vector materials; 566 | std::vector lights; 567 | std::vector surfaces; 568 | std::vector animations; 569 | std::vector cameras; 570 | std::vector nodes; 571 | }; 572 | 573 | template 574 | void RawModel::GetAttributeArray( 575 | std::vector<_attrib_type_>& out, 576 | const _attrib_type_ RawVertex::*ptr) const { 577 | out.resize(vertices.size()); 578 | for (size_t i = 0; i < vertices.size(); i++) { 579 | out[i] = vertices[i].*ptr; 580 | } 581 | } 582 | 583 | template 584 | void RawModel::GetArrayAttributeArray( 585 | std::vector<_attrib_type_>& out, 586 | const std::vector<_attrib_type_> RawVertex::*ptr, 587 | const int arrayOffset) const { 588 | out.resize(vertices.size()); 589 | for (size_t i = 0; i < vertices.size(); i++) { 590 | out[i] = (vertices[i].*ptr)[arrayOffset]; 591 | } 592 | } 593 | -------------------------------------------------------------------------------- /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 | #ifdef CopyFile 23 | #undef CopyFile 24 | #endif 25 | 26 | namespace FileUtils { 27 | 28 | std::vector ListFolderFiles( 29 | std::string folder, 30 | const std::set& matchExtensions) { 31 | std::vector fileList; 32 | if (folder.empty()) { 33 | folder = "."; 34 | } 35 | for (const auto& entry : boost::filesystem::directory_iterator(folder)) { 36 | const auto& suffix = FileUtils::GetFileSuffix(entry.path().string()); 37 | if (suffix.has_value()) { 38 | const auto& suffix_str = StringUtils::ToLower(suffix.value()); 39 | if (matchExtensions.find(suffix_str) != matchExtensions.end()) { 40 | fileList.push_back(entry.path().filename().string()); 41 | } 42 | } 43 | } 44 | return fileList; 45 | } 46 | 47 | bool CreatePath(const std::string path) { 48 | const auto& parent = boost::filesystem::path(path).parent_path(); 49 | if (parent.empty()) { 50 | // this is either CWD or boost::filesystem root; either way it exists 51 | return true; 52 | } 53 | if (boost::filesystem::exists(parent)) { 54 | return boost::filesystem::is_directory(parent); 55 | } 56 | return boost::filesystem::create_directory(parent); 57 | } 58 | 59 | bool CopyFile(const std::string& srcFilename, const std::string& dstFilename, bool createPath) { 60 | boost::nowide::ifstream srcFile(srcFilename, std::ios::binary); 61 | if (!srcFile) { 62 | fmt::printf("Warning: Couldn't open file %s for reading.\n", srcFilename); 63 | return false; 64 | } 65 | // find source file length 66 | srcFile.seekg(0, std::ios::end); 67 | std::streamsize srcSize = srcFile.tellg(); 68 | srcFile.seekg(0, std::ios::beg); 69 | 70 | if (createPath && !CreatePath(dstFilename.c_str())) { 71 | fmt::printf("Warning: Couldn't create directory %s.\n", dstFilename); 72 | return false; 73 | } 74 | 75 | boost::nowide::ofstream dstFile(dstFilename, std::ios::binary | std::ios::trunc); 76 | if (!dstFile) { 77 | fmt::printf("Warning: Couldn't open file %s for writing.\n", dstFilename); 78 | return false; 79 | } 80 | dstFile << srcFile.rdbuf(); 81 | std::streamsize dstSize = dstFile.tellp(); 82 | if (srcSize == dstSize) { 83 | return true; 84 | } 85 | fmt::printf( 86 | "Warning: Only copied %lu bytes to %s, when %s is %lu bytes long.\n", 87 | dstSize, 88 | dstFilename, 89 | srcFilename, 90 | srcSize); 91 | return false; 92 | } 93 | } // namespace FileUtils 94 | -------------------------------------------------------------------------------- /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 | #ifdef CopyFile 19 | #undef CopyFile 20 | #endif 21 | 22 | namespace FileUtils { 23 | 24 | std::string GetCurrentFolder(); 25 | 26 | bool FileExists(const std::string& folderPath); 27 | bool FolderExists(const std::string& folderPath); 28 | 29 | std::vector ListFolderFiles( 30 | const std::string folder, 31 | const std::set& matchExtensions); 32 | 33 | bool CreatePath(std::string path); 34 | 35 | bool CopyFile( 36 | const std::string& srcFilename, 37 | const std::string& dstFilename, 38 | bool createPath = false); 39 | 40 | inline std::string GetAbsolutePath(const std::string& filePath) { 41 | return boost::filesystem::absolute(filePath).string(); 42 | } 43 | 44 | inline std::string GetCurrentFolder() { 45 | return boost::filesystem::current_path().string(); 46 | } 47 | 48 | inline bool FileExists(const std::string& filePath) { 49 | return boost::filesystem::exists(filePath) && boost::filesystem::is_regular_file(filePath); 50 | } 51 | 52 | inline bool FolderExists(const std::string& folderPath) { 53 | return boost::filesystem::exists(folderPath) && boost::filesystem::is_directory(folderPath); 54 | } 55 | 56 | inline std::string getFolder(const std::string& path) { 57 | auto parent = boost::filesystem::path(path).parent_path().string(); 58 | return parent == "" ? "." : parent; 59 | } 60 | 61 | inline std::string GetFileName(const std::string& path) { 62 | return boost::filesystem::path(path).filename().string(); 63 | } 64 | 65 | inline std::string GetFileBase(const std::string& path) { 66 | return boost::filesystem::path(path).stem().string(); 67 | } 68 | 69 | inline boost::optional GetFileSuffix(const std::string& path) { 70 | const auto& extension = boost::filesystem::path(path).extension(); 71 | if (extension.empty()) { 72 | return boost::none; 73 | } 74 | return extension.string().substr(1); 75 | } 76 | 77 | inline bool MakeDir(const std::string& path) { 78 | return boost::filesystem::create_directories(boost::filesystem::path(path)); 79 | } 80 | 81 | } // namespace FileUtils 82 | -------------------------------------------------------------------------------- /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 STBI_WINDOWS_UTF8 15 | #define STB_IMAGE_IMPLEMENTATION 16 | #include 17 | 18 | #define STB_IMAGE_WRITE_IMPLEMENTATION 19 | #include 20 | 21 | namespace ImageUtils { 22 | 23 | static bool imageHasTransparentPixels(FILE* f) { 24 | int width, height, channels; 25 | // RGBA: we have to load the pixels to figure out if the image is fully opaque 26 | uint8_t* pixels = stbi_load_from_file(f, &width, &height, &channels, 0); 27 | if (pixels != nullptr) { 28 | int pixelCount = width * height; 29 | for (int ix = 0; ix < pixelCount; ix++) { 30 | // test fourth byte (alpha); 255 is 1.0 31 | if (pixels[4 * ix + 3] != 255) { 32 | return true; 33 | } 34 | } 35 | } 36 | return false; 37 | } 38 | 39 | ImageProperties GetImageProperties(char const* filePath) { 40 | ImageProperties result = { 41 | 1, 42 | 1, 43 | IMAGE_OPAQUE, 44 | }; 45 | 46 | FILE* f = fopen(filePath, "rb"); 47 | if (f == nullptr) { 48 | return result; 49 | } 50 | 51 | int channels; 52 | int success = stbi_info_from_file(f, &result.width, &result.height, &channels); 53 | 54 | if (success && channels == 4 && imageHasTransparentPixels(f)) { 55 | result.occlusion = IMAGE_TRANSPARENT; 56 | } 57 | 58 | fclose(f); 59 | return result; 60 | } 61 | 62 | std::string suffixToMimeType(std::string suffix) { 63 | std::transform(suffix.begin(), suffix.end(), suffix.begin(), ::tolower); 64 | 65 | if (suffix == "jpg" || suffix == "jpeg") { 66 | return "image/jpeg"; 67 | } 68 | if (suffix == "png") { 69 | return "image/png"; 70 | } 71 | return "image/unknown"; 72 | } 73 | 74 | } // namespace ImageUtils 75 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------