Compare commits
226 Commits
Author | SHA1 | Date |
---|---|---|
|
739ee5db94 | |
|
37f992321e | |
|
be627fa228 | |
|
1d735698ba | |
|
5c3229d6cf | |
|
3c08169510 | |
|
b9c7d0a400 | |
|
44d2d5bf97 | |
|
24092a80bb | |
|
31e3665862 | |
|
7b39358f46 | |
|
648fdfb944 | |
|
6437d02e5f | |
|
b09bdef836 | |
|
3daf2b712a | |
|
70136c6f53 | |
|
ec98db9762 | |
|
43e3d05440 | |
|
9853625ba1 | |
|
3afd9f3266 | |
|
5b475476ba | |
|
25ef9167ed | |
|
a89b7d526f | |
|
4e645ddfca | |
|
e411f4165a | |
|
83e1cdc512 | |
|
449c5ed59d | |
|
7683d208cb | |
|
75f811491c | |
|
8f51ee4016 | |
|
b8b470395b | |
|
07c69ce0e7 | |
|
a66049f2e8 | |
|
959d799482 | |
|
feb2768b61 | |
|
3c57e62ae5 | |
|
0ba34ee7f5 | |
|
80e4a96d9b | |
|
6b8490a6ae | |
|
1100e09111 | |
|
1cc57c90b2 | |
|
f0eaa1efb2 | |
|
ed43cacb33 | |
|
33ef6fef2e | |
|
b1960feb64 | |
|
8e5ded122e | |
|
df00e0538d | |
|
a07cabd1ec | |
|
4b501431fe | |
|
7fc7120487 | |
|
366e904b70 | |
|
7970914dd0 | |
|
5ba62a726c | |
|
7dd8438c78 | |
|
4bb4bdbac1 | |
|
4ed510ca8e | |
|
13f463d336 | |
|
9c76e5ade1 | |
|
769454e964 | |
|
11398f6acb | |
|
910a8bfdd0 | |
|
9e6ab31e37 | |
|
c6ed20a45a | |
|
1f21a50cc9 | |
|
46f6234af5 | |
|
d2f7d0e270 | |
|
ca78a31b22 | |
|
9c114ffa49 | |
|
678a9bf844 | |
|
b8d587d2c7 | |
|
a596073f04 | |
|
4780efdc6f | |
|
a3841fe7cd | |
|
e83d495fdc | |
|
713ee2f879 | |
|
9d4a599d0c | |
|
f170772f8a | |
|
70c3823eb7 | |
|
5ec3184a8a | |
|
ffd6af3142 | |
|
ce2a9f8d85 | |
|
d0883136d3 | |
|
a3bee2e42a | |
|
12025de179 | |
|
d451aa73f3 | |
|
1328a4b96f | |
|
5c07d274c3 | |
|
1145defda3 | |
|
5730d1c301 | |
|
be1b75431d | |
|
5389d848e2 | |
|
09089a7d79 | |
|
49892c5e83 | |
|
f2cb6a1010 | |
|
8b081a5fc3 | |
|
7374bcf34f | |
|
f71be404f4 | |
|
62bb8710c0 | |
|
c1faf6b822 | |
|
90e5bef116 | |
|
3e0aa3565c | |
|
073f06aa13 | |
|
e990432ecf | |
|
fe2aa11516 | |
|
2029fa7277 | |
|
a65628f56f | |
|
c34f861f69 | |
|
d38ccd935f | |
|
efd404764d | |
|
6e527563cd | |
|
0c94cb5706 | |
|
319c0fe460 | |
|
bbbba646de | |
|
2a4da70de0 | |
|
2e03a3cdea | |
|
f646be2e47 | |
|
5282f693f9 | |
|
52de0d20d8 | |
|
f988cb7aa7 | |
|
7d36e7f4d7 | |
|
b475fbead4 | |
|
891cf668b9 | |
|
2fc254ed79 | |
|
9c84e14ddc | |
|
bdcf16f042 | |
|
7995f7b341 | |
|
d3f9a269ba | |
|
5788eea8ad | |
|
e178a75be3 | |
|
3c8cad4730 | |
|
f530af8bf4 | |
|
447333a16a | |
|
68983ad0d0 | |
|
cb76a49b82 | |
|
3bfe56b71f | |
|
542550192c | |
|
9174fe2f50 | |
|
b3492194cf | |
|
3f1590a26b | |
|
e992aac1d9 | |
|
ca12a38afe | |
|
4ec8c8e34d | |
|
f6ce7e345d | |
|
93d1385b1e | |
|
e92261f491 | |
|
c72cf85acf | |
|
dc8f199d54 | |
|
362b8bd761 | |
|
a8f194f793 | |
|
a984f7bf37 | |
|
3f796dd90f | |
|
f1982e6ca3 | |
|
da5d606c93 | |
|
608c6f1797 | |
|
0deaf186a8 | |
|
7c0715c4ad | |
|
67bb9372d7 | |
|
b95c50a72f | |
|
20b1bd7051 | |
|
54c3b04fce | |
|
a3989249f3 | |
|
8ab9459d6a | |
|
cbd2e3f00d | |
|
74479426a7 | |
|
41f8a5ef03 | |
|
a2d5c7d87b | |
|
13367dfc66 | |
|
9d1096f3ff | |
|
cc0a6e5b89 | |
|
1a46ccf7ff | |
|
9c2e485cfc | |
|
a9c4fb1bd0 | |
|
21305baa93 | |
|
fdf7bb3336 | |
|
e2e0d741a2 | |
|
fada9e45ee | |
|
f61814f7c9 | |
|
423458a841 | |
|
c70ded31d5 | |
|
bcbfdae6be | |
|
5eb36702b5 | |
|
e73c2f46e2 | |
|
7f8746f56e | |
|
412b98b9ab | |
|
145f0d84a1 | |
|
3eeebf4599 | |
|
5786fc9f0b | |
|
4f8ddffdf7 | |
|
3b7872f4f1 | |
|
d2a20ee5cb | |
|
5eb3779ad8 | |
|
60f18ae635 | |
|
7ecc6e5743 | |
|
9ae36088b4 | |
|
5e0f05261c | |
|
14150269a0 | |
|
0fcd00cb5b | |
|
21138b6eff | |
|
7f9aa3318a | |
|
58518f5e00 | |
|
34800d056a | |
|
075dc4a8c3 | |
|
22354fd7ce | |
|
1829f6d6a6 | |
|
6137694595 | |
|
a03e252866 | |
|
b1076fa373 | |
|
91d11c88b7 | |
|
db3f232fc9 | |
|
4e0f5e4fcf | |
|
fb7dae380b | |
|
e417fbe90e | |
|
f535effb8b | |
|
72eb620d87 | |
|
5580dcfa92 | |
|
5a4959d86c | |
|
dfb026033d | |
|
d46aeb3d46 | |
|
998b7719cb | |
|
e0e81404f9 | |
|
8cf7f446b7 | |
|
454cb7c864 | |
|
946f12361c | |
|
9085b8d284 | |
|
c194843239 | |
|
606d31997c |
|
@ -0,0 +1,87 @@
|
|||
---
|
||||
AccessModifierOffset: -1
|
||||
AlignAfterOpenBracket: AlwaysBreak
|
||||
AlignConsecutiveAssignments: false
|
||||
AlignConsecutiveDeclarations: false
|
||||
AlignEscapedNewlinesLeft: true
|
||||
AlignOperands: false
|
||||
AlignTrailingComments: false
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
AllowShortBlocksOnASingleLine: false
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: Empty
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: true
|
||||
AlwaysBreakTemplateDeclarations: true
|
||||
BinPackArguments: false
|
||||
BinPackParameters: false
|
||||
BraceWrapping:
|
||||
AfterClass: false
|
||||
AfterControlStatement: false
|
||||
AfterEnum: false
|
||||
AfterFunction: false
|
||||
AfterNamespace: false
|
||||
AfterObjCDeclaration: false
|
||||
AfterStruct: false
|
||||
AfterUnion: false
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
IndentBraces: false
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeBraces: Attach
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializersBeforeComma: false
|
||||
BreakAfterJavaFieldAnnotations: false
|
||||
BreakStringLiterals: false
|
||||
ColumnLimit: 100
|
||||
CommentPragmas: '^ IWYU pragma:'
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: true
|
||||
DerivePointerAlignment: false
|
||||
DisableFormat: false
|
||||
ForEachMacros: [ FOR_EACH, FOR_EACH_ENUMERATE, FOR_EACH_KV, FOR_EACH_R, FOR_EACH_RANGE, FOR_EACH_RANGE_R, ]
|
||||
IncludeCategories:
|
||||
- Regex: '^<.*\.h(pp)?>'
|
||||
Priority: 1
|
||||
- Regex: '^<.*'
|
||||
Priority: 2
|
||||
- Regex: '.*'
|
||||
Priority: 3
|
||||
IndentCaseLabels: true
|
||||
IndentWidth: 2
|
||||
IndentWrappedFunctionNames: false
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
MacroBlockBegin: ''
|
||||
MacroBlockEnd: ''
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
ObjCBlockIndentWidth: 2
|
||||
ObjCSpaceAfterProperty: false
|
||||
ObjCSpaceBeforeProtocolList: false
|
||||
PenaltyBreakBeforeFirstCallParameter: 1
|
||||
PenaltyBreakComment: 300
|
||||
PenaltyBreakFirstLessLess: 120
|
||||
PenaltyBreakString: 1000
|
||||
PenaltyExcessCharacter: 1000000
|
||||
PenaltyReturnTypeOnItsOwnLine: 200
|
||||
PointerAlignment: Left
|
||||
ReflowComments: true
|
||||
SortIncludes: true
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
SpacesInAngles: false
|
||||
SpacesInContainerLiterals: true
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: Cpp11
|
||||
TabWidth: 8
|
||||
UseTab: Never
|
||||
...
|
|
@ -0,0 +1,3 @@
|
|||
.dockerignore
|
||||
Dockerfile
|
||||
sdk
|
|
@ -0,0 +1,14 @@
|
|||
# FBX SDK
|
||||
*.a filter=lfs diff=lfs merge=lfs -text
|
||||
*.dylib filter=lfs diff=lfs merge=lfs -text
|
||||
*.so filter=lfs diff=lfs merge=lfs -text
|
||||
*.dll filter=lfs diff=lfs merge=lfs -text
|
||||
*.lib filter=lfs diff=lfs merge=lfs -text
|
||||
|
||||
# TEST FILES
|
||||
*.glb filter=lfs diff=lfs merge=lfs -text
|
||||
*.fbx filter=lfs diff=lfs merge=lfs -text
|
||||
*.tga filter=lfs diff=lfs merge=lfs -text
|
||||
*.png filter=lfs diff=lfs merge=lfs -text
|
||||
*.jpg filter=lfs diff=lfs merge=lfs -text
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
npm/fbx2gltf/bin/Darwin/FBX2glTF
|
||||
npm/fbx2gltf/bin/Linux/FBX2glTF
|
||||
npm/fbx2gltf/bin/Windows_NT/FBX2glTF.exe
|
||||
npm/fbx2gltf/node_modules/
|
||||
npm/tests/node_modules/
|
||||
npm/tests/test/*.js
|
||||
npm/tests/test/*.js.map
|
|
@ -0,0 +1,63 @@
|
|||
git:
|
||||
lfs_skip_smudge: true
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- os: linux
|
||||
dist: xenial
|
||||
env:
|
||||
- APP_NAME="FBX2glTF-linux-x64"
|
||||
- CONAN_CONFIG="-s compiler.libcxx=libstdc++11"
|
||||
- FBXSDK_TARBALL="https://github.com/zellski/FBXSDK-Linux/archive/2019.2.tar.gz"
|
||||
- TAR_WILDCARDS="--wildcards"
|
||||
- os: osx
|
||||
osx_image: xcode10.2
|
||||
env:
|
||||
- APP_NAME="FBX2glTF-darwin-x64"
|
||||
- CONAN_CONFIG="-s compiler=apple-clang -s compiler.version=10.0 -s compiler.libcxx=libc++"
|
||||
- FBXSDK_TARBALL="https://github.com/zellski/FBXSDK-Darwin/archive/2019.2.tar.gz"
|
||||
- TAR_WILDCARDS=""
|
||||
compiler: gcc
|
||||
language: generic
|
||||
|
||||
#disabled for now
|
||||
#cache:
|
||||
# directories:
|
||||
# - ${HOME}/.conan
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages: zstd
|
||||
homebrew:
|
||||
packages: zstd
|
||||
|
||||
install:
|
||||
- curl -sL "${FBXSDK_TARBALL}" | tar xz --strip-components=1 ${TAR_WILDCARDS} */sdk
|
||||
- zstd -d -r --rm sdk
|
||||
- git clone --depth 1 git://github.com/astropy/ci-helpers.git
|
||||
- source ci-helpers/travis/setup_conda.sh
|
||||
- conda config --set always_yes yes
|
||||
- conda info -a
|
||||
- conda create -n travis_env python=3.7 pip
|
||||
- conda activate travis_env
|
||||
- pip install conan
|
||||
- conan user
|
||||
- conan remote add --force bincrafters https://api.bintray.com/conan/bincrafters/public-conan
|
||||
|
||||
script:
|
||||
- conan install . -i build -s build_type=Release ${CONAN_CONFIG}
|
||||
- conan build . -bf build
|
||||
- mv build/FBX2glTF build/${APP_NAME}
|
||||
|
||||
notifications:
|
||||
webhooks:
|
||||
- "https://code.facebook.com/travis/webhook/"
|
||||
deploy:
|
||||
provider: releases
|
||||
api_key:
|
||||
secure: V9CTmZKM7yvsT/WCesJ/tLTuapSf0oIp73zyZrwID7zQtXaq1QJSna4tWM2T0qeZIYhniH1/mqEr2jZVW1txmYn9ZxUMH1Nmp9zzOGl/q+JlRrJUi6HRUWWhCMz003L90whngyOcGI+T7rHtcVcby4owVsze15SrQqqV74NXI8DYNIbNgQR1Nwmqsrg0QirFPEBaIKDAiKonnRDWKPy2P8vqnN9fLhj00uHLwuvahlKAnWFEbNnFbiRScKifB+Mlo6Pf6r64iikrxS2jBxAgSsvPLkuemWLmaHTeGbJMM82aqh5vGSvgYcExvZi+0RdXeIcBdv/jaivM/xge4aZ+4P+IJoX32ZNCcYFMsqES+a6TztkywMs2k1r5gV6LrTjeXJsINSW+BDFdmrwmkudETc4gelQgkMmEkdCwFHENtZGl65z8HJDQKcu9F8NQlhNU7Z5rwQNLmYccvktSDhwbFSG5eq2kFFfcbVx3ovvn1voRTNnyhhVD2ZnLepSQInAVkZbaLkE90bQ+t9icf8uDdHDn17zOQaAZuecPlSW1y4XUCJnZCi0JPLhdSmQYiF60LHYI6xDneC8pmIz8kCUbk921zu8bJBy7zKHmfHy2vqNlPKuRULRIs5QzY31jf2PVZHzB5zX3KSqx9Dd+3DtgbLX2HLaZnANbkQc0rr1X2kk=
|
||||
file: build/${APP_NAME}
|
||||
skip_cleanup: true
|
||||
on:
|
||||
repo: facebookincubator/FBX2glTF
|
||||
tags: true
|
234
CMakeLists.txt
234
CMakeLists.txt
|
@ -1,37 +1,68 @@
|
|||
cmake_minimum_required(VERSION 3.5)
|
||||
project(FBX2glTF)
|
||||
|
||||
set(typical_usage_str
|
||||
"Example usage:\n\
|
||||
> mkdir -p build_debug\n\
|
||||
> conan install . -i build_debug -s build_type=Debug -e FBXSDK_SDKS=/home/zell/FBXSDK\n\
|
||||
> conan build . -bf build_debug")
|
||||
|
||||
if ("${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}")
|
||||
message(FATAL_ERROR
|
||||
"Building from within the source tree is not supported.\n"
|
||||
"Hint: mkdir -p build; cmake -H. -Bbuild; make -Cbuild\n")
|
||||
"Building from within the source tree is not supported! ${typical_usage_str}")
|
||||
endif ()
|
||||
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
include(ExternalProject)
|
||||
|
||||
# FBX
|
||||
find_package(FBX REQUIRED)
|
||||
foreach (FBXSDK_VERSION "2019.2")
|
||||
find_package(FBX)
|
||||
if (FBXSDK_FOUND)
|
||||
break()
|
||||
endif()
|
||||
endforeach(FBXSDK_VERSION)
|
||||
if (NOT FBXSDK_FOUND)
|
||||
message(FATAL_ERROR
|
||||
"Can't find FBX SDK in either:\n"
|
||||
" - Mac OS X: ${FBXSDK_APPLE_ROOT}\n"
|
||||
" - Windows: ${FBXSDK_WINDOWS_ROOT}\n"
|
||||
" - Linux: ${FBXSDK_LINUX_ROOT}"
|
||||
)
|
||||
)
|
||||
endif()
|
||||
|
||||
if(NOT EXISTS "${CMAKE_BINARY_DIR}/conan_paths.cmake")
|
||||
message(FATAL_ERROR
|
||||
"The Conan package manager must run ('install') first. ${typical_usage_str}")
|
||||
endif()
|
||||
include("${CMAKE_BINARY_DIR}/conan_paths.cmake")
|
||||
|
||||
set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
list(INSERT CMAKE_MODULE_PATH 0 "${CMAKE_BINARY_DIR}")
|
||||
|
||||
# stuff we get from Conan
|
||||
find_package(boost_filesystem MODULE REQUIRED)
|
||||
find_package(boost_optional MODULE REQUIRED)
|
||||
find_package(libxml2 MODULE REQUIRED)
|
||||
find_package(zlib MODULE REQUIRED)
|
||||
find_package(fmt MODULE REQUIRED)
|
||||
|
||||
# create a compilation database for e.g. clang-tidy
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
# DRACO
|
||||
ExternalProject_Add(Draco
|
||||
UPDATE_DISCONNECTED TRUE
|
||||
GIT_REPOSITORY https://github.com/google/draco
|
||||
GIT_TAG 1.3.4
|
||||
PREFIX draco
|
||||
INSTALL_DIR
|
||||
CMAKE_ARGS
|
||||
-DCMAKE_INSTALL_PREFIX=<INSTALL_DIR>
|
||||
-DCMAKE_INSTALL_PREFIX=<INSTALL_DIR>
|
||||
-DBUILD_FOR_GLTF=1
|
||||
)
|
||||
set(DRACO_INCLUDE_DIR "${CMAKE_BINARY_DIR}/draco/include")
|
||||
if (WIN32)
|
||||
|
@ -44,9 +75,9 @@ endif()
|
|||
set(mathfu_build_benchmarks OFF CACHE BOOL "")
|
||||
set(mathfu_build_tests OFF CACHE BOOL "")
|
||||
ExternalProject_Add(MathFu
|
||||
UPDATE_DISCONNECTED TRUE
|
||||
GIT_REPOSITORY https://github.com/google/mathfu
|
||||
PREFIX mathfu
|
||||
GIT_REPOSITORY https://github.com/google/mathfu
|
||||
GIT_TAG v1.1.0
|
||||
CONFIGURE_COMMAND ${CMAKE_COMMAND} -E echo "Skipping MathFu configure step."
|
||||
BUILD_COMMAND ${CMAKE_COMMAND} -E echo "Skipping MathFu build step."
|
||||
INSTALL_COMMAND ${CMAKE_COMMAND} -E echo "Skipping MathFu install step."
|
||||
|
@ -55,124 +86,161 @@ set(MATHFU_INCLUDE_DIRS
|
|||
"${CMAKE_BINARY_DIR}/mathfu/src/MathFu/include/"
|
||||
"${CMAKE_BINARY_DIR}/mathfu/src/MathFu/dependencies/vectorial/include")
|
||||
|
||||
# JSON
|
||||
ExternalProject_Add(Json
|
||||
UPDATE_DISCONNECTED TRUE
|
||||
GIT_REPOSITORY https://github.com/nlohmann/json
|
||||
PREFIX json
|
||||
CONFIGURE_COMMAND ${CMAKE_COMMAND} -E echo "Skipping JSON configure step."
|
||||
BUILD_COMMAND ${CMAKE_COMMAND} -E echo "Skipping JSON build step."
|
||||
INSTALL_COMMAND ${CMAKE_COMMAND} -E echo "Skipping JSON install step."
|
||||
# OrderedMap
|
||||
ExternalProject_Add(FiFoMap
|
||||
PREFIX fifo_map
|
||||
GIT_REPOSITORY https://github.com/nlohmann/fifo_map
|
||||
CONFIGURE_COMMAND ${CMAKE_COMMAND} -E echo "Skipping FiFoMap configure step."
|
||||
BUILD_COMMAND ${CMAKE_COMMAND} -E echo "Skipping FiFoMap build step."
|
||||
INSTALL_COMMAND ${CMAKE_COMMAND} -E echo "Skipping FiFoMap install step."
|
||||
)
|
||||
set(JSON_INCLUDE_DIR "${CMAKE_BINARY_DIR}/json/src/Json/src")
|
||||
set(FIFO_MAP_INCLUDE_DIR "${CMAKE_BINARY_DIR}/fifo_map/src/FiFoMap/src")
|
||||
|
||||
|
||||
# cppcodec
|
||||
ExternalProject_Add(CPPCodec
|
||||
UPDATE_DISCONNECTED TRUE
|
||||
GIT_REPOSITORY https://github.com/tplgy/cppcodec
|
||||
PREFIX cppcodec
|
||||
GIT_REPOSITORY https://github.com/tplgy/cppcodec
|
||||
CONFIGURE_COMMAND ${CMAKE_COMMAND} -E echo "Skipping CPPCodec configure step."
|
||||
BUILD_COMMAND ${CMAKE_COMMAND} -E echo "Skipping CPPCodec build step."
|
||||
INSTALL_COMMAND ${CMAKE_COMMAND} -E echo "Skipping CPPCodec install step."
|
||||
)
|
||||
set(CPPCODEC_INCLUDE_DIR "${CMAKE_BINARY_DIR}/cppcodec/src/CPPCodec")
|
||||
|
||||
# CXXOPTS
|
||||
ExternalProject_Add(CxxOpts
|
||||
UPDATE_DISCONNECTED TRUE
|
||||
GIT_REPOSITORY https://github.com/jarro2783/cxxopts
|
||||
PREFIX cxxopts
|
||||
CONFIGURE_COMMAND ${CMAKE_COMMAND} -E echo "Skipping cxxopts configure step."
|
||||
BUILD_COMMAND ${CMAKE_COMMAND} -E echo "Skipping cxxopts build step."
|
||||
INSTALL_COMMAND ${CMAKE_COMMAND} -E echo "Skipping cxxopts install step."
|
||||
)
|
||||
set(CXXOPTS_INCLUDE_DIR "${CMAKE_BINARY_DIR}/cxxopts/src/CxxOpts/include")
|
||||
|
||||
# FMT
|
||||
ExternalProject_Add(Fmt
|
||||
UPDATE_DISCONNECTED TRUE
|
||||
GIT_REPOSITORY https://github.com/fmtlib/fmt
|
||||
CMAKE_CACHE_ARGS "-DFMT_DOC:BOOL=OFF" "-DFMT_INSTALL:BOOL=ON" "-DFMT_TEST:BOOL=OFF"
|
||||
CMAKE_ARGS
|
||||
-DCMAKE_INSTALL_PREFIX=<INSTALL_DIR>
|
||||
PREFIX fmt
|
||||
)
|
||||
set(FMT_INCLUDE_DIR "${CMAKE_BINARY_DIR}/fmt/include")
|
||||
if (WIN32)
|
||||
set(FMT_LIB "${CMAKE_BINARY_DIR}/fmt/lib/fmt.lib")
|
||||
else()
|
||||
set(FMT_LIB "${CMAKE_BINARY_DIR}/fmt/lib/libfmt.a")
|
||||
endif()
|
||||
|
||||
if (APPLE)
|
||||
find_library(CF_FRAMEWORK CoreFoundation)
|
||||
message("CoreFoundation Framework: ${CF_FRAMEWORK}")
|
||||
set(FRAMEWORKS ${CF_FRAMEWORK})
|
||||
endif()
|
||||
|
||||
set(SOURCE_FILES
|
||||
set(LIB_SOURCE_FILES
|
||||
src/FBX2glTF.h
|
||||
src/fbx/materials/3dsMaxPhysicalMaterial.cpp
|
||||
src/fbx/materials/FbxMaterials.cpp
|
||||
src/fbx/materials/FbxMaterials.hpp
|
||||
src/fbx/materials/RoughnessMetallicMaterials.hpp
|
||||
src/fbx/materials/StingrayPBSMaterial.cpp
|
||||
src/fbx/materials/TraditionalMaterials.cpp
|
||||
src/fbx/materials/TraditionalMaterials.hpp
|
||||
src/fbx/Fbx2Raw.cpp
|
||||
src/fbx/Fbx2Raw.hpp
|
||||
src/fbx/FbxBlendShapesAccess.cpp
|
||||
src/fbx/FbxBlendShapesAccess.hpp
|
||||
src/fbx/FbxLayerElementAccess.hpp
|
||||
src/fbx/FbxSkinningAccess.cpp
|
||||
src/fbx/FbxSkinningAccess.hpp
|
||||
src/gltf/Raw2Gltf.cpp
|
||||
src/gltf/Raw2Gltf.hpp
|
||||
src/gltf/GltfModel.cpp
|
||||
src/gltf/GltfModel.hpp
|
||||
src/gltf/TextureBuilder.cpp
|
||||
src/gltf/TextureBuilder.hpp
|
||||
src/gltf/properties/AccessorData.cpp
|
||||
src/gltf/properties/AccessorData.hpp
|
||||
src/gltf/properties/AnimationData.cpp
|
||||
src/gltf/properties/AnimationData.hpp
|
||||
src/gltf/properties/BufferData.cpp
|
||||
src/gltf/properties/BufferData.hpp
|
||||
src/gltf/properties/BufferViewData.cpp
|
||||
src/gltf/properties/BufferViewData.hpp
|
||||
src/gltf/properties/CameraData.cpp
|
||||
src/gltf/properties/CameraData.hpp
|
||||
src/gltf/properties/ImageData.cpp
|
||||
src/gltf/properties/ImageData.hpp
|
||||
src/gltf/properties/LightData.cpp
|
||||
src/gltf/properties/LightData.hpp
|
||||
src/gltf/properties/MaterialData.cpp
|
||||
src/gltf/properties/MaterialData.hpp
|
||||
src/gltf/properties/MeshData.cpp
|
||||
src/gltf/properties/MeshData.hpp
|
||||
src/gltf/properties/NodeData.cpp
|
||||
src/gltf/properties/NodeData.hpp
|
||||
src/gltf/properties/PrimitiveData.cpp
|
||||
src/gltf/properties/PrimitiveData.hpp
|
||||
src/gltf/properties/SamplerData.hpp
|
||||
src/gltf/properties/SceneData.cpp
|
||||
src/gltf/properties/SceneData.hpp
|
||||
src/gltf/properties/SkinData.cpp
|
||||
src/gltf/properties/SkinData.hpp
|
||||
src/gltf/properties/TextureData.cpp
|
||||
src/gltf/properties/TextureData.hpp
|
||||
src/mathfu.hpp
|
||||
src/raw/RawModel.cpp
|
||||
src/raw/RawModel.hpp
|
||||
src/utils/File_Utils.cpp
|
||||
src/utils/File_Utils.hpp
|
||||
src/utils/Image_Utils.cpp
|
||||
src/utils/String_Utils.cpp
|
||||
src/main.cpp
|
||||
src/Fbx2Raw.cpp
|
||||
src/Raw2Gltf.cpp
|
||||
src/RawModel.cpp
|
||||
src/glTF/BufferData.cpp
|
||||
src/glTF/MaterialData.cpp
|
||||
src/glTF/MeshData.cpp
|
||||
src/glTF/NodeData.cpp
|
||||
src/glTF/PrimitiveData.cpp
|
||||
src/glTF/BufferViewData.cpp
|
||||
src/glTF/BufferViewData.h
|
||||
src/glTF/AccessorData.cpp
|
||||
src/glTF/AccessorData.h
|
||||
src/glTF/ImageData.cpp
|
||||
src/glTF/TextureData.cpp
|
||||
src/glTF/SkinData.cpp
|
||||
src/glTF/AnimationData.cpp
|
||||
src/glTF/CameraData.cpp
|
||||
src/glTF/SceneData.cpp
|
||||
src/utils/Image_Utils.hpp
|
||||
src/utils/String_Utils.hpp
|
||||
third_party/CLI11/CLI11.hpp
|
||||
)
|
||||
|
||||
add_executable(FBX2glTF ${SOURCE_FILES})
|
||||
add_library(libFBX2glTF STATIC ${LIB_SOURCE_FILES})
|
||||
set_target_properties(libFBX2glTF PROPERTIES OUTPUT_NAME "FBX2glTF")
|
||||
add_executable(appFBX2glTF src/FBX2glTF.cpp)
|
||||
set_target_properties(appFBX2glTF PROPERTIES OUTPUT_NAME "FBX2glTF")
|
||||
|
||||
add_dependencies(FBX2glTF
|
||||
add_dependencies(libFBX2glTF
|
||||
Draco
|
||||
MathFu
|
||||
Json
|
||||
CxxOpts
|
||||
FiFoMap
|
||||
CPPCodec
|
||||
Fmt
|
||||
)
|
||||
|
||||
if (NOT MSVC)
|
||||
# Disable annoying & spammy warning from FBX SDK header file
|
||||
target_compile_options(FBX2glTF PRIVATE
|
||||
target_compile_options(libFBX2glTF PRIVATE
|
||||
"-Wno-null-dereference"
|
||||
"-Wunused"
|
||||
)
|
||||
target_compile_options(appFBX2glTF PRIVATE
|
||||
"-Wno-null-dereference"
|
||||
"-Wunused"
|
||||
)
|
||||
endif()
|
||||
|
||||
target_link_libraries(FBX2glTF
|
||||
target_link_libraries(libFBX2glTF
|
||||
${FRAMEWORKS}
|
||||
${CMAKE_DL_LIBS}
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
boost_filesystem::boost_filesystem
|
||||
boost_optional::boost_optional
|
||||
${DRACO_LIB}
|
||||
${FMT_LIB}
|
||||
optimized ${FBXSDK_LIBRARY}
|
||||
debug ${FBXSDK_LIBRARY_DEBUG}
|
||||
fmt::fmt
|
||||
libxml2::libxml2
|
||||
zlib::zlib
|
||||
${CMAKE_DL_LIBS}
|
||||
${CMAKE_THREAD_LIBS_INIT}
|
||||
)
|
||||
|
||||
target_include_directories(FBX2glTF PUBLIC
|
||||
if (APPLE)
|
||||
find_package(Iconv MODULE REQUIRED)
|
||||
target_link_libraries(libFBX2glTF Iconv)
|
||||
else()
|
||||
find_package(libiconv MODULE REQUIRED)
|
||||
target_link_libraries(libFBX2glTF libiconv::libiconv)
|
||||
endif()
|
||||
|
||||
target_include_directories(libFBX2glTF PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||
)
|
||||
|
||||
target_include_directories(libFBX2glTF SYSTEM PUBLIC
|
||||
"third_party/stb"
|
||||
"third_party/json"
|
||||
${FBXSDK_INCLUDE_DIR}
|
||||
${DRACO_INCLUDE_DIR}
|
||||
${MATHFU_INCLUDE_DIRS}
|
||||
${JSON_INCLUDE_DIR}
|
||||
${CXXOPTS_INCLUDE_DIR}
|
||||
${FIFO_MAP_INCLUDE_DIR}
|
||||
${CPPCODEC_INCLUDE_DIR}
|
||||
${FMT_INCLUDE_DIR}
|
||||
)
|
||||
|
||||
install (TARGETS FBX2glTF DESTINATION bin)
|
||||
target_include_directories(appFBX2glTF PUBLIC
|
||||
"third_party/CLI11"
|
||||
)
|
||||
target_link_libraries(appFBX2glTF libFBX2glTF)
|
||||
|
||||
install (TARGETS libFBX2glTF appFBX2glTF
|
||||
RUNTIME DESTINATION bin
|
||||
ARCHIVE DESTINATION lib
|
||||
)
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
# Code of Conduct
|
||||
|
||||
Facebook has adopted a Code of Conduct that we expect project participants to adhere to.
|
||||
Please read the [full text](https://code.fb.com/codeofconduct/)
|
||||
so that you can understand what actions will and will not be tolerated.
|
|
@ -0,0 +1,29 @@
|
|||
FROM ubuntu:16.04
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y software-properties-common && \
|
||||
add-apt-repository ppa:jonathonf/python-3.6 && \
|
||||
add-apt-repository ppa:git-core/ppa && \
|
||||
apt-get update && \
|
||||
apt-get install -y python3.6 curl build-essential cmake libxml2-dev zlib1g-dev git && \
|
||||
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py && python3 get-pip.py && \
|
||||
pip install conan && \
|
||||
conan remote add bincrafters https://api.bintray.com/conan/bincrafters/public-conan
|
||||
|
||||
# Install FBX SDK
|
||||
RUN mkdir -p /fbx2gltf/sdk/Linux/2019.2 && \
|
||||
curl -L https://www.autodesk.com/content/dam/autodesk/www/adn/fbx/20192/fbx20192_fbxsdk_linux.tar.gz -o fbx20192_fbxsdk_linux.tar.gz && \
|
||||
tar -xvf fbx20192_fbxsdk_linux.tar.gz && \
|
||||
echo "yes\nn" | ./fbx20192_fbxsdk_linux /fbx2gltf/sdk/Linux/2019.2 && \
|
||||
rm -rf /fbxsdktemp
|
||||
|
||||
COPY . /fbx2gltf
|
||||
|
||||
WORKDIR /fbx2gltf
|
||||
|
||||
# Build and install
|
||||
RUN conan install . -i docker-build -s build_type=Release -s compiler=gcc -s compiler.version=5 -s compiler.libcxx=libstdc++11 && \
|
||||
conan build -bf docker-build . && \
|
||||
cp docker-build/FBX2glTF /usr/bin && \
|
||||
cd / && \
|
||||
rm -rf /fbx2gltf /root/.conan
|
|
@ -1,3 +1,5 @@
|
|||
# Copyright (c) 2014-present, Facebook, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Helper function for finding the FBX SDK.
|
||||
# Cribbed & tweaked from https://github.com/floooh/fbxc/
|
||||
|
@ -21,27 +23,24 @@ else()
|
|||
set(ARCH_32 OFF)
|
||||
endif()
|
||||
|
||||
set(FBXSDK_VERSION "2018.1.1" CACHE STRING "Precise version string of FBX SDK to use.")
|
||||
if (NOT DEFINED FBXSDK_VERSION)
|
||||
set(FBXSDK_VERSION "2019.2")
|
||||
endif()
|
||||
|
||||
set(_fbxsdk_vstudio_version "vs2015")
|
||||
set(_fbxsdk_vstudio_version "vs2017")
|
||||
|
||||
message("Looking for FBX SDK version: ${FBXSDK_VERSION}")
|
||||
|
||||
if (DEFINED FBXSDK_SDKS)
|
||||
get_filename_component(FBXSDK_SDKS_ABS ${FBXSDK_SDKS} ABSOLUTE)
|
||||
|
||||
set(FBXSDK_APPLE_ROOT "${FBXSDK_SDKS_ABS}/Darwin/${FBXSDK_VERSION}")
|
||||
set(FBXSDK_LINUX_ROOT "${FBXSDK_SDKS_ABS}/Linux/${FBXSDK_VERSION}")
|
||||
set(FBXSDK_WINDOWS_ROOT "${FBXSDK_SDKS_ABS}/Windows/${FBXSDK_VERSION}")
|
||||
else()
|
||||
set(FBXSDK_APPLE_ROOT
|
||||
"/Applications/Autodesk/FBX SDK/${FBXSDK_VERSION}")
|
||||
set(FBXSDK_LINUX_ROOT
|
||||
"/usr")
|
||||
set(FBXSDK_WINDOWS_ROOT
|
||||
"C:/Program Files/Autodesk/FBX/FBX SDK/${FBXSDK_VERSION}")
|
||||
if (NOT DEFINED FBXSDK_SDKS)
|
||||
set(FBXSDK_SDKS "${CMAKE_CURRENT_SOURCE_DIR}/sdk")
|
||||
endif()
|
||||
|
||||
get_filename_component(FBXSDK_SDKS_ABS ${FBXSDK_SDKS} ABSOLUTE)
|
||||
|
||||
set(FBXSDK_APPLE_ROOT "${FBXSDK_SDKS_ABS}/Darwin/${FBXSDK_VERSION}")
|
||||
set(FBXSDK_LINUX_ROOT "${FBXSDK_SDKS_ABS}/Linux/${FBXSDK_VERSION}")
|
||||
set(FBXSDK_WINDOWS_ROOT "${FBXSDK_SDKS_ABS}/Windows/${FBXSDK_VERSION}")
|
||||
|
||||
if (APPLE)
|
||||
set(_fbxsdk_root "${FBXSDK_APPLE_ROOT}")
|
||||
set(_fbxsdk_libdir_debug "lib/clang/debug")
|
||||
|
@ -62,11 +61,11 @@ elseif (WIN32)
|
|||
elseif (UNIX)
|
||||
set(_fbxsdk_root "${FBXSDK_LINUX_ROOT}")
|
||||
if (ARCH_32)
|
||||
set(_fbxsdk_libdir_debug "lib/gcc4/x86/debug")
|
||||
set(_fbxsdk_libdir_release "lib/gcc4/x86/release")
|
||||
set(_fbxsdk_libdir_debug "lib/gcc/x86/debug")
|
||||
set(_fbxsdk_libdir_release "lib/gcc/x86/release")
|
||||
else()
|
||||
set(_fbxsdk_libdir_debug "lib/gcc4/x64/debug")
|
||||
set(_fbxsdk_libdir_release "lib/gcc4/x64/release")
|
||||
set(_fbxsdk_libdir_debug "lib/gcc/x64/debug")
|
||||
set(_fbxsdk_libdir_release "lib/gcc/x64/release")
|
||||
endif()
|
||||
set(_fbxsdk_libname_debug "libfbxsdk.a")
|
||||
set(_fbxsdk_libname_release "libfbxsdk.a")
|
||||
|
|
2
LICENSE
2
LICENSE
|
@ -2,7 +2,7 @@ BSD License
|
|||
|
||||
For FBX2glTF software
|
||||
|
||||
Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
|
33
PATENTS
33
PATENTS
|
@ -1,33 +0,0 @@
|
|||
Additional Grant of Patent Rights Version 2
|
||||
|
||||
"Software" means the FBX2glTF software contributed by Facebook, Inc.
|
||||
|
||||
Facebook, Inc. ("Facebook") hereby grants to each recipient of the Software
|
||||
("you") a perpetual, worldwide, royalty-free, non-exclusive, irrevocable
|
||||
(subject to the termination provision below) license under any Necessary
|
||||
Claims, to make, have made, use, sell, offer to sell, import, and otherwise
|
||||
transfer the Software. For avoidance of doubt, no license is granted under
|
||||
Facebook’s rights in any patent claims that are infringed by (i) modifications
|
||||
to the Software made by you or any third party or (ii) the Software in
|
||||
combination with any software or other technology.
|
||||
|
||||
The license granted hereunder will terminate, automatically and without notice,
|
||||
if you (or any of your subsidiaries, corporate affiliates or agents) initiate
|
||||
directly or indirectly, or take a direct financial interest in, any Patent
|
||||
Assertion: (i) against Facebook or any of its subsidiaries or corporate
|
||||
affiliates, (ii) against any party if such Patent Assertion arises in whole or
|
||||
in part from any software, technology, product or service of Facebook or any of
|
||||
its subsidiaries or corporate affiliates, or (iii) against any party relating
|
||||
to the Software. Notwithstanding the foregoing, if Facebook or any of its
|
||||
subsidiaries or corporate affiliates files a lawsuit alleging patent
|
||||
infringement against you in the first instance, and you respond by filing a
|
||||
patent infringement counterclaim in that lawsuit against that party that is
|
||||
unrelated to the Software, the license granted hereunder will not terminate
|
||||
under section (i) of this paragraph due to such counterclaim.
|
||||
|
||||
A "Necessary Claim" is a claim of a patent owned by Facebook that is
|
||||
necessarily infringed by the Software standing alone.
|
||||
|
||||
A "Patent Assertion" is any lawsuit or other action alleging direct, indirect,
|
||||
or contributory infringement or inducement to infringe any patent, including a
|
||||
cross-claim or counterclaim.
|
348
README.md
348
README.md
|
@ -1,148 +1,315 @@
|
|||
# FBX2glTF
|
||||
|
||||
[](https://opensource.org/licenses/BSD-3-Clause)
|
||||
|
||||
This is a command line tool for converting 3D model assets on Autodesk's
|
||||
venerable [FBX](https://www.autodesk.com/products/fbx/overview) format to
|
||||
[glTF 2.0](https://github.com/KhronosGroup/glTF/tree/master/specification/2.0),
|
||||
a modern runtime asset delivery format.
|
||||
|
||||
## Building & Running
|
||||
Precompiled binaries releases for Windows, Mac OS X and Linux may be
|
||||
found [here](https://github.com/facebookincubator/FBX2glTF/releases).
|
||||
|
||||
This tool has been tested on Linux, Mac OS X and Windows. It requires CMake 3.5+
|
||||
and a reasonably C++11 compliant toolchain.
|
||||
Bleeding-edge binaries for Windows may be found [here](https://ci.appveyor.com/project/Facebook/fbx2gltf/build/artifacts). Linux and Mac OS X to come; meanwhile, you can [build your own](#building-it-on-your-own).
|
||||
|
||||
[](https://travis-ci.com/facebookincubator/FBX2glTF)
|
||||
[](https://ci.appveyor.com/project/Facebook/fbx2gltf)
|
||||
|
||||
## Running
|
||||
|
||||
The tool can be invoked like so:
|
||||
|
||||
```
|
||||
> FBX2glTF ~/models/butterfly.fbx
|
||||
```
|
||||
|
||||
Or perhaps, as part of a more complex pipeline:
|
||||
|
||||
```
|
||||
> FBX2glTF --binary --draco --verbose \
|
||||
--input ~/models/source/butterfly.fbx \
|
||||
--output ~/models/target/butterfly.glb
|
||||
```
|
||||
|
||||
There are also some friendly & hands-on instructions available [over at Facebook](https://developers.facebook.com/docs/sharing/3d-posts/glb-tutorials/#convert-from-fbx).
|
||||
|
||||
### CLI Switches
|
||||
|
||||
You can always run the binary with --help to see what options it takes:
|
||||
|
||||
```
|
||||
FBX2glTF 0.9.7: Generate a glTF 2.0 representation of an FBX model.
|
||||
Usage: FBX2glTF [OPTIONS] [FBX Model]
|
||||
|
||||
Positionals:
|
||||
FBX Model FILE The FBX model to convert.
|
||||
|
||||
Options:
|
||||
-h,--help Print this help message and exit
|
||||
-v,--verbose Include blend shape tangents, if reported present by the FBX SDK.
|
||||
-V,--version
|
||||
-i,--input FILE The FBX model to convert.
|
||||
-o,--output TEXT Where to generate the output, without suffix.
|
||||
-e,--embed Inline buffers as data:// URIs within generated non-binary glTF.
|
||||
-b,--binary Output a single binary format .glb file.
|
||||
--long-indices (never|auto|always)
|
||||
Whether to use 32-bit indices.
|
||||
--compute-normals (never|broken|missing|always)
|
||||
When to compute vertex normals from mesh geometry.
|
||||
--anim-framerate (bake24|bake30|bake60)
|
||||
Select baked animation framerate.
|
||||
--flip-u Flip all U texture coordinates.
|
||||
--no-flip-u Don't flip U texture coordinates.
|
||||
--flip-v Flip all V texture coordinates.
|
||||
--no-flip-v Don't flip V texture coordinates.
|
||||
--no-khr-lights-punctual Don't use KHR_lights_punctual extension to export FBX lights.
|
||||
--user-properties Transcribe FBX User Properties into glTF node and material 'extras'.
|
||||
--blend-shape-normals Include blend shape normals, if reported present by the FBX SDK.
|
||||
--blend-shape-tangents Include blend shape tangents, if reported present by the FBX SDK.
|
||||
-k,--keep-attribute (position|normal|tangent|binormial|color|uv0|uv1|auto) ...
|
||||
Used repeatedly to build a limiting set of vertex attributes to keep.
|
||||
--fbx-temp-dir DIR Temporary directory to be used by FBX SDK.
|
||||
|
||||
|
||||
Materials:
|
||||
--pbr-metallic-roughness Try to glean glTF 2.0 native PBR attributes from the FBX.
|
||||
--khr-materials-unlit Use KHR_materials_unlit extension to request an unlit shader.
|
||||
|
||||
|
||||
Draco:
|
||||
-d,--draco Apply Draco mesh compression to geometries.
|
||||
--draco-compression-level INT in [0 - 10]=7
|
||||
The compression level to tune Draco to.
|
||||
--draco-bits-for-position INT in [1 - 32]=14
|
||||
How many bits to quantize position to.
|
||||
--draco-bits-for-uv INT in [1 - 32]=10
|
||||
How many bits to quantize UV coordinates to.
|
||||
--draco-bits-for-normals INT in [1 - 32]=10
|
||||
How many bits to quantize nornals to.
|
||||
--draco-bits-for-colors INT in [1 - 32]=8
|
||||
How many bits to quantize colors to.
|
||||
--draco-bits-for-other INT in [1 - 32]=8
|
||||
How many bits to quantize all other vertex attributes to.
|
||||
```
|
||||
|
||||
Some of these switches are not obvious:
|
||||
|
||||
- `--embed` is the way to get a single distributable file without using the
|
||||
binary format. It encodes the binary buffer(s) as a single base64-encoded
|
||||
`data://` URI. This is a very slow and space-consuming way to accomplish what
|
||||
the binary format was invented to do simply and efficiently, but it can be
|
||||
useful e.g. for loaders that don't understand the .glb format.
|
||||
- `--flip-u` and `--flip-v`, when enabled, will apply a `x -> (1.0 - x)`
|
||||
function to all `u` or `v` texture coordinates respectively. The `u` version
|
||||
is perhaps not commonly used, but flipping `v` is **the default behaviour**.
|
||||
Your FBX is likely constructed with the assumption that `(0, 0)` is bottom
|
||||
left, whereas glTF has `(0, 0)` as top left. To produce spec-compliant glTF,
|
||||
we must flip the texcoords. To request unflipped coordinates:
|
||||
- `--long-indices` lets you force the use of either 16-bit or 32-bit indices.
|
||||
The default option is auto, which make the choice on a per-mesh-size basis.
|
||||
- `--compute-normals` controls when automatic vertex normals should be computed
|
||||
from the mesh. By default, empty normals (which are forbidden by glTF) are
|
||||
replaced. A choice of 'missing' implies 'broken', but additionally creates
|
||||
normals for models that lack them completely.
|
||||
- `--no-flip-v` will actively disable v coordinat flipping. This can be useful
|
||||
if your textures are pre-flipped, or if for some other reason you were already
|
||||
in a glTF-centric texture coordinate system.
|
||||
- All three material options are, in their own way, works in progress, but the
|
||||
`--pbr-metallic-roughness` switch is at least compliant with the core spec;
|
||||
unlike the others, it does not depend on an unratified extension. That option
|
||||
will be chosen by default if you supply none of the others. Material switches
|
||||
are documented further below.
|
||||
- If you supply any `-keep-attribute` option, you enable a mode wherein you must
|
||||
supply it repeatedly to list _all_ the vertex attributes you wish to keep in
|
||||
the conversion process. This is a way to trim the size of the resulting glTF
|
||||
if you know the FBX contains superfluous attributes. The supported arguments
|
||||
are `position`, `normal`, `tangent`, `color`, `uv0`, and `uv1`.
|
||||
- When **blend shapes** are present, you may use `--blend-shape-normals` and
|
||||
`--blend-shape-tangents` to include normal and tangent attributes in the glTF
|
||||
morph targets. They are not included by default because they rarely or never
|
||||
seem to be correctly present in the actual FBX source, which means the SDK
|
||||
must be computing them from geometry, unasked? In any case, they are beyond
|
||||
the control of the artist, and can yield strange crinkly behaviour. Since
|
||||
they also take up significant space in the output file, we made them opt-in.
|
||||
|
||||
## Building it on your own
|
||||
|
||||
We currently depend on the open source projects
|
||||
[Draco](https://github.com/google/draco),
|
||||
[MathFu](https://github.com/google/mathfu),
|
||||
[Json](https://github.com/nlohmann/json),
|
||||
[cppcodec](https://github.com/tplgy/cppcodec),
|
||||
[cxxopts](https://github.com/jarro2783/cxxopts),
|
||||
[CLI11](https://github.com/CLIUtils/CLI11),
|
||||
[stb](https://github.com/nothings/stb),
|
||||
and [fmt](https://github.com/fmtlib/fmt);
|
||||
all of which are automatically downloaded, configured and built.
|
||||
all of which are automatically downloaded and/or built.
|
||||
|
||||
You must manually download and install the
|
||||
[Autodesk FBX SDK](https://www.autodesk.com/products/fbx/overview) 2018.1.1 and
|
||||
accept its license agreement. Once installed, the build system will attempt to
|
||||
find the SDK in its default location for each system.
|
||||
|
||||
Once that's all done...
|
||||
**At present, only version 2019.2 of the FBX SDK is supported**. The
|
||||
build system will not successfully locate any other version.
|
||||
|
||||
### Linux and MacOS X
|
||||
Compilation on Unix machines should be as simple as:
|
||||
|
||||
Your development environment will need to have:
|
||||
|
||||
- build essentials (gcc for Linux, clang for Mac)
|
||||
- cmake
|
||||
- python 3.\* and associated pip3/pip command
|
||||
- zstd
|
||||
|
||||
Then, compilation on Unix machines will look something like:
|
||||
|
||||
```
|
||||
> cd <FBX2glTF directory>
|
||||
> cmake -H. -Bbuild
|
||||
> make -Cbuild
|
||||
# Determine SDK location & build settings for Linux vs (Recent) Mac OS X
|
||||
> if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
export CONAN_CONFIG="-s compiler=apple-clang -s compiler.version=10.0 -s compiler.libcxx=libc++"
|
||||
export FBXSDK_TARBALL="https://github.com/zellski/FBXSDK-Darwin/archive/2019.2.tar.gz"
|
||||
elif [[ "$OSTYPE" == "linux"* ]]; then
|
||||
export CONAN_CONFIG="-s compiler.libcxx=libstdc++11"
|
||||
export FBXSDK_TARBALL="https://github.com/zellski/FBXSDK-Linux/archive/2019.2.tar.gz"
|
||||
else
|
||||
echo "This snippet only handles Mac OS X and Linux."
|
||||
fi
|
||||
|
||||
# Fetch Project
|
||||
> git clone https://github.com/facebookincubator/FBX2glTF.git
|
||||
> cd FBX2glTF
|
||||
|
||||
# Fetch and unpack FBX SDK
|
||||
> curl -sL "${FBXSDK_TARBALL}" | tar xz --strip-components=1 --include */sdk/
|
||||
# Then decompress the contents
|
||||
> zstd -d -r --rm sdk
|
||||
|
||||
# Install and configure Conan, if needed
|
||||
> pip3 install conan # or sometimes just "pip"; you may need to install Python/PIP
|
||||
> conan remote add --force bincrafters https://api.bintray.com/conan/bincrafters/public-conan
|
||||
|
||||
# Initialize & run build
|
||||
> conan install . -i build -s build_type=Release ${CONAN_CONFIG}
|
||||
> conan build . -bf build
|
||||
```
|
||||
|
||||
If all goes well, you will end up with a statically linked executable that can
|
||||
be invoked like so:
|
||||
```
|
||||
> ./build/FBX2glTF ~/models/butterfly.fbx
|
||||
```
|
||||
|
||||
Or perhaps, as part of a more complex pipeline:
|
||||
```
|
||||
> ./build/FBX2glTF --binary --draco --flip-v \
|
||||
--khr-materials-common \
|
||||
--input ~/models/source/butterfly.fbx \
|
||||
--output ~/models/target/butterfly.glb
|
||||
```
|
||||
If all goes well, you will end up with a statically linked executable in `./build/FBX2glTF`.
|
||||
|
||||
### Windows
|
||||
|
||||
<TODO> the below is out of date
|
||||
|
||||
Windows users may [download](https://cmake.org/download) CMake for Windows,
|
||||
install it and [run it](https://cmake.org/runningcmake/) on the FBX2glTF
|
||||
checkout (choose a build directory distinct from the source). As part of this
|
||||
process, you will be asked to choose which generator to use; it should be fine
|
||||
to pick any recent Visual Studio option relevant to your system.
|
||||
checkout (choose a build directory distinct from the source).
|
||||
|
||||
Note that the CMAKE_BUILD_TYPE variable from the Unix Makefile system is
|
||||
entirely ignored here; the Visual Studio solution that's generated handles all
|
||||
the canonical build types -- Debug, Release, MinSizeRel, and so on. You will
|
||||
choose which one to build in the Visual Studio IDE.
|
||||
As part of this process, you will be asked to choose which generator
|
||||
to use. **At present, only Visual Studio 2017 or 2019 is supported.** Older
|
||||
versions of the IDE are unlikely to successfully build the tool.
|
||||
|
||||
Note that the `CMAKE_BUILD_TYPE` variable from the Unix Makefile system is
|
||||
entirely ignored here; it is when you open the generated solution that
|
||||
you will be choose one of the canonical build types — _Debug_,
|
||||
_Release_, _MinSizeRel_, and so on.
|
||||
|
||||
## Conversion Process
|
||||
|
||||
The actual translation begins with the FBX SDK parsing the input file, and ends
|
||||
with the generation of the core `JSON` description that forms the core of glTF,
|
||||
along with binary buffers that hold geometry and animations (and optionally also
|
||||
with the generation of the descriptive `JSON` that forms the core of glTF, along
|
||||
with binary buffers that hold geometry and animations (and optionally also
|
||||
emedded resources such as textures.)
|
||||
|
||||
In the process, each node and mesh in the FBX is ripped apart into a long list
|
||||
of surfaces and associated triangles, with a material assigned to each one. A
|
||||
similar process happens in reverse when we construct meshes and materials that
|
||||
conform to the expectations of the glTF format.
|
||||
In the process, each mesh is ripped apart into a long list of triangles and
|
||||
their associated vertices, with a material assigned to each one. A similar
|
||||
process happens in reverse when we construct meshes and materials that conform
|
||||
to the expectations of the glTF format.
|
||||
|
||||
### Animations
|
||||
|
||||
Every animation in the FBX file becomes an animation in the glTF file. The
|
||||
method used is one of "baking": we step through the interval of time spanned by
|
||||
the animation, keyframe by keyframe, calculate the local transform of each node,
|
||||
and whenever we find any node that's rotated, translated or scaled, we record
|
||||
that fact in the output.
|
||||
the animation, keyframe by keyframe, calculate the local transform of each
|
||||
node, and whenever we find any node that's rotated, translated or scaled, we
|
||||
record that fact in the output.
|
||||
|
||||
This method has the benefit of being simple and precise. It has the drawback of
|
||||
creating potentially very large files. The more complex the animation rig, the
|
||||
less avoidable this situation is.
|
||||
Beyond skeleton-based animation, _Blend Shapes_ are also supported; they are
|
||||
read from the FBX file on a per-mesh basis, and clips can use them by varying
|
||||
the weights associated with each one.
|
||||
|
||||
The baking method has the benefit of being simple and precise. It has the
|
||||
drawback of creating potentially very large files. The more complex the
|
||||
animation rig, the less avoidable this data explosion is.
|
||||
|
||||
There are three future enhancements we hope to see for animations:
|
||||
|
||||
There are two future enhancements we hope to see for animations:
|
||||
- Version 2.0 of glTF brought us support for expressing quadratic animation
|
||||
curves, where previously we had only had linear. Not coincidentally, quadratic
|
||||
splines are one of the key ways animations are expressed inside the FBX. When
|
||||
we find such a curve, it would be more efficient to output it without baking
|
||||
it into a long sequence of linear approximations.
|
||||
- Perhaps more useful in practice is the idea of compressing animation curves
|
||||
- We do not yet ever generate
|
||||
[sparse accessors](https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#sparse-accessors),
|
||||
but many animations (especially morph targets) would benefit from this
|
||||
storage optimisation.
|
||||
- Perhaps most useful in practice is the idea of compressing animation curves
|
||||
the same way we use Draco to compress meshes (see below). Like geometry,
|
||||
animations are highly redundant -- each new value is highly predictable from
|
||||
animations are highly redundant — each new value is highly predictable from
|
||||
preceding values. If Draco extends its support for animations (it's on their
|
||||
roadmap), or if someone else develops a glTF extension for animation
|
||||
compression, we will likely add support in this tool.
|
||||
|
||||
### Materials
|
||||
|
||||
With glTF 2.0, we leaped headlong into physically-based rendering (BPR), where
|
||||
canonical way of expressing what a mesh looks like is by describing its visible
|
||||
material in fundamental attributes like "how rough is this surface".
|
||||
With glTF 2.0, we leaped headlong into physically-based rendering (PBR), where
|
||||
the canonical way of expressing what a mesh looks like is by describing its
|
||||
visible material in fundamental attributes like "how rough is this surface".
|
||||
|
||||
By contrast, FBX's material support remains in the older world of Lambert and
|
||||
Phong, with much simpler illumination and shading models. These are modes are
|
||||
largely incompatible (for example, textures in the old workflow often contain
|
||||
baked lighting that would arise naturally in a PBR environment).
|
||||
By contrast, FBX's material support remains largely in the older world of
|
||||
Lambert and Phong, with simpler and more direct illumination and shading
|
||||
models. These modes are inherently incompatible — for example, textures in the
|
||||
old workflow often contain baked lighting of the type that would arise naturally
|
||||
in a PBR environment.
|
||||
|
||||
Some material settings remain well supported and transfer automatically:
|
||||
- Emissive constants and textures
|
||||
- Occlusion maps
|
||||
- Normal maps
|
||||
|
||||
This leaves the other traditional settings of Lambert:
|
||||
- Ambient -- this is anathema in the PBR world, where such effects should
|
||||
emerge naturally from the fundamental colour of the material and any ambient
|
||||
lighting present.
|
||||
- Diffuse -- the material's direction-agnostic, non-specular reflection,
|
||||
and additionally, with Blinn/Phong:
|
||||
- Specular -- a more polished material's direction-sensitive reflection,
|
||||
- Shininess -- just how polished the material is,
|
||||
- Emissive constants and textures
|
||||
- Occlusion maps
|
||||
- Normal maps
|
||||
|
||||
This leaves the other traditional settings, first of Lambert:
|
||||
|
||||
- Ambient — this is anathema in the PBR world, where such effects should
|
||||
emerge naturally from the fundamental colour of the material and any ambient
|
||||
lighting present.
|
||||
- Diffuse — the material's direction-agnostic, non-specular reflection,
|
||||
and additionally, with Blinn/Phong:
|
||||
- Specular — a more polished material's direction-sensitive reflection,
|
||||
- Shininess — just how polished the material is; a higher value here yields a
|
||||
more mirror-like surface.
|
||||
|
||||
(All these can be either constants or textures.)
|
||||
|
||||
Increasingly with PBR materials, those properties are just left at sensible zero
|
||||
or default values in the FBX. But when they're there, and they're how you want
|
||||
to define your materials, one option is to use the --khr-materials-common
|
||||
command line switch, which incurs a required dependency on the glTF extension
|
||||
`KHR_materials_common`. **Note that at the time of writing, this glTF extension
|
||||
is still undergoing the ratification process, and is furthermore likely to
|
||||
change names.**
|
||||
#### Exporting as Unlit
|
||||
|
||||
Given the command line flag --pbr-metallic-roughness, we accept glTF 2.0's PBR
|
||||
mode, but we do so very partially, filling in a couple of reasonable constants
|
||||
for metalness and roughness and using the diffuse texture, if it exists, as the
|
||||
`base colour` texture.
|
||||
If you have a model was constructed using an unlit workflow, e.g. a photogrammetry
|
||||
capture or a landscape with careful baked-in lighting, you may choose to export
|
||||
it using the --khr-materials-common switch. This incurs a dependency on the glTF
|
||||
extension 'KHR_materials_unlit; a client that accepts that extension is making
|
||||
a promise it'll do its best to render pixel values without lighting calculations.
|
||||
|
||||
More work is needed to harness the power of glTF's 2.0's materials. The biggest
|
||||
issue here is the lack of any obviously emerging standards to complement FBX
|
||||
itself. It's not clear what format an artist can export their PBR materials on,
|
||||
and when they can, how to communicate this information well to `FBX2glTF`.
|
||||
**Note that at the time of writing, this glTF extension is still undergoing the
|
||||
ratification process**
|
||||
|
||||
#### Exporting as Metallic-Roughness PBR
|
||||
|
||||
Given the command line flag --pbr-metallic-roughness, we throw ourselves into
|
||||
the warm embrace of glTF 2.0's PBR preference.
|
||||
|
||||
As mentioned above, there is little consensus in the world on how PBR should be
|
||||
represented in FBX. At present, we support only one format: Stingray PBS. This
|
||||
is a feature that comes bundled with Maya, and any PBR model exported through
|
||||
that route should be digested propertly by FBX2glTF.
|
||||
|
||||
(A happy note: Allegorithmic's Substance Painter also exports Stingray PBS,
|
||||
when hooked up to Maya.)
|
||||
|
||||
## Draco Compression
|
||||
|
||||
The tool will optionally apply [Draco](https://github.com/google/draco)
|
||||
compression to the geometric data of each mesh (vertex indices, positions,
|
||||
normals, per-vertex color, and so on). This can be dramatically effective
|
||||
|
@ -156,13 +323,18 @@ viewer that is willing and able to decompress the data.
|
|||
ratification process.**
|
||||
|
||||
## Future Improvements
|
||||
|
||||
This tool is under continuous development. We do not have a development roadmap
|
||||
per se, but some aspirations have been noted above.
|
||||
per se, but some aspirations have been noted above. The canonical list of active
|
||||
TODO items can be found
|
||||
[on GitHub](https://github.com/facebookincubator/FBX2glTF/labels/enhancement).
|
||||
|
||||
## Authors
|
||||
- Pär Winzell
|
||||
- J.M.P. van Waveren
|
||||
- Amanda Watson
|
||||
|
||||
- Pär Winzell
|
||||
- J.M.P. van Waveren
|
||||
- Amanda Watson
|
||||
|
||||
## License
|
||||
`FBX2glTF` is BSD-licensed. We also provide an additional patent grant.
|
||||
|
||||
FBX2glTF is licensed under the [3-clause BSD license](LICENSE).
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
# C/C++ with GCC
|
||||
# Build your C/C++ project with GCC using make.
|
||||
# Add steps that publish test results, save build artifacts, deploy, and more:
|
||||
# https://docs.microsoft.com/azure/devops/pipelines/apps/c-cpp/gcc
|
||||
|
||||
jobs:
|
||||
- job: Linux
|
||||
pool:
|
||||
vmImage: 'Ubuntu 16.04'
|
||||
|
||||
steps:
|
||||
- task: UsePythonVersion@0
|
||||
inputs:
|
||||
versionSpec: '3.6'
|
||||
architecture: 'x64'
|
||||
|
||||
- script: python -m pip install --upgrade pip setuptools wheel
|
||||
displayName: 'Install Python tools'
|
||||
|
||||
- script: |
|
||||
pip install conan
|
||||
conan remote add bincrafters https://api.bintray.com/conan/bincrafters/public-conan
|
||||
displayName: 'Install & configure Conan'
|
||||
|
||||
- script: |
|
||||
conan install . -i build -s build_type=Release -e FBXSDK_SDKS=sdk
|
||||
displayName: 'Resolve binary dependencies and build CMake files.'
|
||||
|
||||
- script: |
|
||||
conan build -bf build .
|
||||
mv build/FBX2glTF build/FBX2glTF-linux-x64
|
||||
displayName: 'Build FBX2glTF'
|
||||
|
||||
- task: PublishBuildArtifacts@1
|
||||
inputs:
|
||||
pathtoPublish: 'build/FBX2glTF-linux-x64'
|
||||
artifactName: 'binaries'
|
||||
|
||||
- job: Mac
|
||||
pool:
|
||||
vmImage: 'macOS-10.14'
|
||||
|
||||
steps:
|
||||
- task: UsePythonVersion@0
|
||||
inputs:
|
||||
versionSpec: '3.6'
|
||||
architecture: 'x64'
|
||||
|
||||
- script: python -m pip install --upgrade pip setuptools wheel
|
||||
displayName: 'Install Python tools'
|
||||
|
||||
- script: |
|
||||
pip install conan
|
||||
conan remote add bincrafters https://api.bintray.com/conan/bincrafters/public-conan
|
||||
displayName: 'Install Conan'
|
||||
|
||||
- script: |
|
||||
conan install . -i build -s compiler=apple-clang -s compiler=apple-clang -s compiler.version=10.0 -s compiler.libcxx=libc++ -s build_type=Release -e FBXSDK_SDKS=sdk
|
||||
displayName: 'Resolve binary dependencies and build CMake files.'
|
||||
|
||||
- script: |
|
||||
conan build -bf build .
|
||||
mv build/FBX2glTF build/FBX2glTF-darwin-x64
|
||||
displayName: 'Build FBX2glTF'
|
||||
|
||||
- task: PublishBuildArtifacts@1
|
||||
inputs:
|
||||
pathtoPublish: 'build/FBX2glTF-darwin-x64'
|
||||
artifactName: 'binaries'
|
||||
|
||||
- job: Windows
|
||||
pool:
|
||||
vmImage: 'vs2017-win2016'
|
||||
|
||||
steps:
|
||||
- task: UsePythonVersion@0
|
||||
inputs:
|
||||
versionSpec: '3.6'
|
||||
architecture: 'x64'
|
||||
|
||||
- script: python -m pip install --upgrade pip setuptools wheel
|
||||
displayName: 'Install Python tools'
|
||||
|
||||
- script: |
|
||||
pip install conan
|
||||
conan remote add bincrafters https://api.bintray.com/conan/bincrafters/public-conan
|
||||
displayName: 'Install Conan'
|
||||
|
||||
- script: |
|
||||
conan install . -i build -s build_type=Release -e FBXSDK_SDKS=sdk
|
||||
displayName: 'Resolve binary dependencies and build CMake files.'
|
||||
|
||||
- script: |
|
||||
conan build -bf build .
|
||||
move build\Release\FBX2glTF.exe build\Release\FBX2glTF-windows-x64.exe
|
||||
displayName: 'Build FBX2glTF'
|
||||
|
||||
- task: PublishBuildArtifacts@1
|
||||
inputs:
|
||||
pathtoPublish: 'build/Release/FBX2glTF-windows-x64.exe'
|
||||
artifactName: 'binaries'
|
|
@ -0,0 +1,33 @@
|
|||
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
|
||||
#
|
||||
|
||||
import os
|
||||
|
||||
from conans import ConanFile, CMake
|
||||
|
||||
|
||||
class FBX2glTFConan(ConanFile):
|
||||
settings = "os", "compiler", "build_type", "arch"
|
||||
requires = (
|
||||
("boost_filesystem/1.69.0@bincrafters/stable"),
|
||||
("libiconv/1.15@bincrafters/stable"),
|
||||
("zlib/1.2.11@conan/stable"),
|
||||
("libxml2/2.9.9@bincrafters/stable"),
|
||||
("fmt/5.3.0@bincrafters/stable"),
|
||||
)
|
||||
generators = "cmake_find_package", "cmake_paths"
|
||||
|
||||
def configure(self):
|
||||
if (
|
||||
self.settings.compiler == "gcc"
|
||||
and self.settings.compiler.libcxx == "libstdc++"
|
||||
):
|
||||
raise Exception(
|
||||
"Rerun 'conan install' with argument: '-s compiler.libcxx=libstdc++11'"
|
||||
)
|
||||
|
||||
def build(self):
|
||||
cmake = CMake(self)
|
||||
cmake.definitions["FBXSDK_SDKS"] = os.getenv("FBXSDK_SDKS", "sdk")
|
||||
cmake.configure()
|
||||
cmake.build()
|
|
@ -0,0 +1,5 @@
|
|||
version: '3.7'
|
||||
services:
|
||||
fbx2gltf:
|
||||
build:
|
||||
context: .
|
33
npm/PATENTS
33
npm/PATENTS
|
@ -1,33 +0,0 @@
|
|||
Additional Grant of Patent Rights Version 2
|
||||
|
||||
"Software" means the FBX2glTF software contributed by Facebook, Inc.
|
||||
|
||||
Facebook, Inc. ("Facebook") hereby grants to each recipient of the Software
|
||||
("you") a perpetual, worldwide, royalty-free, non-exclusive, irrevocable
|
||||
(subject to the termination provision below) license under any Necessary
|
||||
Claims, to make, have made, use, sell, offer to sell, import, and otherwise
|
||||
transfer the Software. For avoidance of doubt, no license is granted under
|
||||
Facebook’s rights in any patent claims that are infringed by (i) modifications
|
||||
to the Software made by you or any third party or (ii) the Software in
|
||||
combination with any software or other technology.
|
||||
|
||||
The license granted hereunder will terminate, automatically and without notice,
|
||||
if you (or any of your subsidiaries, corporate affiliates or agents) initiate
|
||||
directly or indirectly, or take a direct financial interest in, any Patent
|
||||
Assertion: (i) against Facebook or any of its subsidiaries or corporate
|
||||
affiliates, (ii) against any party if such Patent Assertion arises in whole or
|
||||
in part from any software, technology, product or service of Facebook or any of
|
||||
its subsidiaries or corporate affiliates, or (iii) against any party relating
|
||||
to the Software. Notwithstanding the foregoing, if Facebook or any of its
|
||||
subsidiaries or corporate affiliates files a lawsuit alleging patent
|
||||
infringement against you in the first instance, and you respond by filing a
|
||||
patent infringement counterclaim in that lawsuit against that party that is
|
||||
unrelated to the Software, the license granted hereunder will not terminate
|
||||
under section (i) of this paragraph due to such counterclaim.
|
||||
|
||||
A "Necessary Claim" is a claim of a patent owned by Facebook that is
|
||||
necessarily infringed by the Software standing alone.
|
||||
|
||||
A "Patent Assertion" is any lawsuit or other action alleging direct, indirect,
|
||||
or contributory infringement or inducement to infringe any patent, including a
|
||||
cross-claim or counterclaim.
|
|
@ -1 +0,0 @@
|
|||
%~dp0\Windows\FBX2glTF --binary %1 %2
|
|
@ -1,43 +0,0 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# fbx2glb.sh <input FBX> <output GLB>
|
||||
#
|
||||
# TODO: Pass command line switches through to binary.
|
||||
|
||||
set -e
|
||||
|
||||
BINDIR=`dirname $0`
|
||||
BINDIR=`cd ${BINDIR} ; pwd`
|
||||
|
||||
SYSTEM=`uname -s`
|
||||
FBX2GLTF="${BINDIR}/${SYSTEM}/FBX2glTF"
|
||||
|
||||
if [ ! -f "${FBX2GLTF}" ]; then
|
||||
echo "Unable to find 'FBX2glTF' binary: ${FBX2GLTF}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$#" != 2 ]; then
|
||||
echo "Usage: <fbx input> <glb output>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
fullpath() {
|
||||
OLDPWD=$PWD
|
||||
cd "$(dirname "$1")"
|
||||
FULLPATH="$PWD/$(basename "$1")"
|
||||
cd "$OLDPWD"
|
||||
echo "$FULLPATH"
|
||||
}
|
||||
|
||||
INFILE=$(fullpath $1)
|
||||
OUTFILE=$(fullpath $(basename $2 ".glb"))
|
||||
|
||||
# construct a safe work dir
|
||||
SCRIPT_BASE=`basename $0`
|
||||
TEMP_DIR=`mktemp -d "/tmp/${SCRIPT_BASE}.XXXX"`
|
||||
trap "rm -rf ${TEMP_DIR}" EXIT
|
||||
cd ${TEMP_DIR}
|
||||
|
||||
# some hard-coded defaults for now
|
||||
"${FBX2GLTF}" --binary --flip-v --input "${INFILE}" --output "${OUTFILE}"
|
|
@ -2,7 +2,7 @@ BSD License
|
|||
|
||||
For FBX2glTF software
|
||||
|
||||
Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
||||
Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
|
@ -1,5 +1,8 @@
|
|||
# FBX2glTF
|
||||
|
||||
[](https://opensource.org/licenses/BSD-3-Clause)
|
||||
|
||||
|
||||
This is a command line tool for converting 3D model assets on the
|
||||
well-established [FBX](https://www.autodesk.com/products/fbx/overview) format to
|
||||
[glTF 2.0](https://github.com/KhronosGroup/glTF/tree/master/specification/2.0),
|
||||
|
@ -11,7 +14,36 @@ This package contains three versions of `FBX2glTF`, compiled for three platforms
|
|||
and located in three eponymous directories:
|
||||
- bin/Darwin/FBX2glTF
|
||||
- bin/Linux/FBX2glTF
|
||||
- bin/Windows/FBX2glTF.exe
|
||||
- bin/Windows_NT/FBX2glTF.exe
|
||||
|
||||
# Usage
|
||||
|
||||
```js
|
||||
/**
|
||||
* Converts an FBX to a GTLF or GLB file.
|
||||
* @param string srcFile path to the source file.
|
||||
* @param string destFile path to the destination file.
|
||||
* This must end in `.glb` or `.gltf` (case matters).
|
||||
* @param string[] [opts] options to pass to the converter tool.
|
||||
* @return Promise<string> a promise that yields the full path to the converted
|
||||
* file, an error on conversion failure.
|
||||
*/
|
||||
convert(srcPath :string, destPath :string, args :?string[]) :Promise<string>
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
```js
|
||||
const convert = require('fbx2gltf');
|
||||
convert('path/to/some.fbx', 'path/to/target.glb', ['--khr-materials-unlit']).then(
|
||||
destPath => {
|
||||
// yay, do what we will with our shiny new GLB file!
|
||||
},
|
||||
error => {
|
||||
// ack, conversion failed: inspect 'error' for details
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
# Further Reading
|
||||
|
||||
|
@ -24,6 +56,9 @@ The home of this tool is [here](https://github.com/facebookincubator/FBX2glTF).
|
|||
|
||||
# Legal
|
||||
|
||||
FBX2glTF is licensed under the [3-clause BSD license](LICENSE).
|
||||
|
||||
```
|
||||
This software contains Autodesk® FBX® code developed by Autodesk, Inc. Copyright
|
||||
2017 Autodesk, Inc. All rights, reserved. Such code is provided “as is” and
|
||||
Autodesk, Inc. disclaims any and all warranties, whether express or implied,
|
||||
|
@ -35,4 +70,4 @@ of substitute goods or services; loss of use, data, or profits; or business
|
|||
interruption) however caused and on any theory of liability, whether in
|
||||
contract, strict liability, or tort (including negligence or otherwise) arising
|
||||
in any way out of such code.
|
||||
|
||||
```
|
|
@ -0,0 +1,97 @@
|
|||
/**
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*/
|
||||
|
||||
const childProcess = require('child_process');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const rimraf = require('rimraf');
|
||||
|
||||
const binaries = {
|
||||
'darwin': `bin/darwin/Fbx2Gtlf`,
|
||||
'linux': `bin/linux/Fbx2Gtlf`,
|
||||
'win32': `bin\windows\Fbx2Gtlf.exe`,
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts an FBX to a GTLF or GLB file.
|
||||
* @param string srcFile path to the source file.
|
||||
* @param string destFile path to the destination file or destination path.
|
||||
* This must end in `.glb` or `.gltf` (case matters).
|
||||
* @param string[] [opts] options to pass to the converter tool.
|
||||
* @return Promise<string> a promise that yields the full path to the converted
|
||||
* file, an error on conversion failure.
|
||||
*/
|
||||
function convert(srcFile, destFile, opts = []) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
let binExt = os.type() === 'Windows_NT' ? '.exe' : '';
|
||||
let tool = path.join(__dirname, 'bin', os.type(), 'FBX2glTF' + binExt);
|
||||
if (!fs.existsSync(tool)) {
|
||||
throw new Error(`Unsupported OS: ${os.type()}`);
|
||||
}
|
||||
|
||||
let destExt = path.extname(destFile).toLowerCase();
|
||||
|
||||
if (!destExt) {
|
||||
destExt = '.gltf'
|
||||
|
||||
const srcFilename = path.basename(srcFile, path.extname(srcFile))
|
||||
destFile = path.join(destFile, srcFilename + destExt)
|
||||
}
|
||||
|
||||
if (destExt !== '.glb' && destExt !== '.gltf') {
|
||||
throw new Error(`Unsupported file extension: ${destFile}`);
|
||||
}
|
||||
|
||||
const binary = opts.includes('--binary') || opts.includes('-b');
|
||||
|
||||
if (binary && destExt !== '.glb') {
|
||||
destExt = '.glb';
|
||||
} else if (!binary && destExt === 'glb') {
|
||||
opts.push('--binary');
|
||||
}
|
||||
|
||||
let srcPath = fs.realpathSync(srcFile);
|
||||
let destDir = fs.realpathSync(path.dirname(destFile));
|
||||
let destFilename = path.basename(destFile, path.extname(destFile)) + destExt;
|
||||
let destPath = path.join(destDir, destFilename);
|
||||
|
||||
let args = opts.slice(0);
|
||||
args.push('--input', srcPath, '--output', destPath);
|
||||
let child = childProcess.spawn(tool, args);
|
||||
|
||||
let output = '';
|
||||
child.stdout.on('data', (data) => output += data);
|
||||
child.stderr.on('data', (data) => output += data);
|
||||
child.on('error', reject);
|
||||
child.on('close', code => {
|
||||
// the FBX SDK may create an .fbm dir during conversion; delete!
|
||||
let fbmCruft = srcPath.replace(/.fbx$/i, '.fbm');
|
||||
// don't stick a fork in things if this fails, just log a warning
|
||||
const onError = error =>
|
||||
error && console.warn(`Failed to delete ${fbmCruft}: ${error}`);
|
||||
try {
|
||||
fs.existsSync(fbmCruft) && rimraf(fbmCruft, {}, onError);
|
||||
} catch (error) {
|
||||
onError(error);
|
||||
}
|
||||
|
||||
// non-zero exit code is failure
|
||||
if (code != 0) {
|
||||
reject(new Error(`Converter output:\n` +
|
||||
(output.length ? output : "<none>")));
|
||||
} else {
|
||||
resolve(destPath);
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = convert;
|
|
@ -0,0 +1,93 @@
|
|||
{
|
||||
"name": "fbx2gltf",
|
||||
"version": "0.9.7-p1",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
||||
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.1.4",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
|
||||
"integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==",
|
||||
"requires": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.0.4",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
|
||||
"requires": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
|
||||
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
|
||||
"requires": {
|
||||
"glob": "^7.1.3"
|
||||
}
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "fbx2gltf",
|
||||
"version": "0.9.0",
|
||||
"version": "0.9.7-p1",
|
||||
"description": "Node wrapper around FBX2glTF tools.",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
|
@ -16,12 +16,14 @@
|
|||
"bugs": {
|
||||
"url": "https://github.com/facebookincubator/FBX2glTF/issues"
|
||||
},
|
||||
"homepage": "https://github.com/facebookincubator/FBX2glTF"
|
||||
"homepage": "https://github.com/facebookincubator/FBX2glTF",
|
||||
"files": [
|
||||
"LICENSE",
|
||||
"PATENTS",
|
||||
"README.md",
|
||||
"bin",
|
||||
"index.js"
|
||||
]
|
||||
],
|
||||
"dependencies": {
|
||||
"rimraf": "^2.6.2"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
balanced-match@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
|
||||
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
|
||||
|
||||
brace-expansion@^1.1.7:
|
||||
version "1.1.11"
|
||||
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
|
||||
integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
|
||||
dependencies:
|
||||
balanced-match "^1.0.0"
|
||||
concat-map "0.0.1"
|
||||
|
||||
concat-map@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
|
||||
|
||||
fs.realpath@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
||||
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
|
||||
|
||||
glob@^7.1.3:
|
||||
version "7.1.4"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255"
|
||||
integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==
|
||||
dependencies:
|
||||
fs.realpath "^1.0.0"
|
||||
inflight "^1.0.4"
|
||||
inherits "2"
|
||||
minimatch "^3.0.4"
|
||||
once "^1.3.0"
|
||||
path-is-absolute "^1.0.0"
|
||||
|
||||
inflight@^1.0.4:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
|
||||
integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
|
||||
dependencies:
|
||||
once "^1.3.0"
|
||||
wrappy "1"
|
||||
|
||||
inherits@2:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
|
||||
minimatch@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
|
||||
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
|
||||
dependencies:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
once@^1.3.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
||||
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
|
||||
dependencies:
|
||||
wrappy "1"
|
||||
|
||||
path-is-absolute@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
|
||||
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
|
||||
|
||||
rimraf@^2.6.2:
|
||||
version "2.6.3"
|
||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
|
||||
integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==
|
||||
dependencies:
|
||||
glob "^7.1.3"
|
||||
|
||||
wrappy@1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
|
43
npm/index.js
43
npm/index.js
|
@ -1,43 +0,0 @@
|
|||
const childProcess = require('child_process');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
|
||||
const binaries = {
|
||||
'darwin': `bin/darwin/Fbx2Gtlf`,
|
||||
'linux': `bin/linux/Fbx2Gtlf`,
|
||||
'win32': `bin\windows\Fbx2Gtlf.exe`,
|
||||
};
|
||||
|
||||
function fbx2glb(srcFile, destFile, cwd) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let script = os.type() === 'Windows_NT' ? 'fbx2glb.bat' : 'fbx2glb.sh';
|
||||
let child;
|
||||
try {
|
||||
let opts = {};
|
||||
cwd && (opts.cwd = cwd);
|
||||
child = childProcess.spawn(
|
||||
path.join(__dirname, 'bin', script),
|
||||
[ srcFile, destFile ],
|
||||
opts
|
||||
);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
let output = '';
|
||||
child.stdout.on('data', (data) => output += data);
|
||||
child.stderr.on('data', (data) => output += data);
|
||||
child.on('error', reject);
|
||||
child.on('close', code => {
|
||||
// non-zero exit code is failure
|
||||
if (code != 0) {
|
||||
reject(new Error(`Script ${script} output:\n` +
|
||||
(output.length ? output : "<none>")));
|
||||
} else {
|
||||
resolve(destFile);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = fbx2glb;
|
|
@ -0,0 +1,394 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <CLI11.hpp>
|
||||
|
||||
#include "FBX2glTF.h"
|
||||
#include "fbx/Fbx2Raw.hpp"
|
||||
#include "gltf/Raw2Gltf.hpp"
|
||||
#include "utils/File_Utils.hpp"
|
||||
#include "utils/String_Utils.hpp"
|
||||
|
||||
bool verboseOutput = false;
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
GltfOptions gltfOptions;
|
||||
|
||||
CLI::App app{
|
||||
fmt::sprintf(
|
||||
"FBX2glTF %s: Generate a glTF 2.0 representation of an FBX model.", FBX2GLTF_VERSION),
|
||||
"FBX2glTF"};
|
||||
|
||||
app.add_flag(
|
||||
"-v,--verbose",
|
||||
verboseOutput,
|
||||
"Include blend shape tangents, if reported present by the FBX SDK.");
|
||||
|
||||
app.add_flag_function("-V,--version", [&](size_t count) {
|
||||
fmt::printf("FBX2glTF version %s\nCopyright (c) 2016-2018 Oculus VR, LLC.\n", FBX2GLTF_VERSION);
|
||||
exit(0);
|
||||
});
|
||||
|
||||
std::string inputPath;
|
||||
app.add_option("FBX Model", inputPath, "The FBX model to convert.")->check(CLI::ExistingFile);
|
||||
app.add_option("-i,--input", inputPath, "The FBX model to convert.")->check(CLI::ExistingFile);
|
||||
|
||||
std::string outputPath;
|
||||
app.add_option("-o,--output", outputPath, "Where to generate the output, without suffix.");
|
||||
|
||||
app.add_flag(
|
||||
"-e,--embed",
|
||||
gltfOptions.embedResources,
|
||||
"Inline buffers as data:// URIs within generated non-binary glTF.");
|
||||
app.add_flag("-b,--binary", gltfOptions.outputBinary, "Output a single binary format .glb file.");
|
||||
|
||||
app.add_option(
|
||||
"--long-indices",
|
||||
[&](std::vector<std::string> choices) -> bool {
|
||||
for (const std::string choice : choices) {
|
||||
if (choice == "never") {
|
||||
gltfOptions.useLongIndices = UseLongIndicesOptions::NEVER;
|
||||
} else if (choice == "auto") {
|
||||
gltfOptions.useLongIndices = UseLongIndicesOptions::AUTO;
|
||||
} else if (choice == "always") {
|
||||
gltfOptions.useLongIndices = UseLongIndicesOptions::ALWAYS;
|
||||
} else {
|
||||
fmt::printf("Unknown --long-indices: %s\n", choice);
|
||||
throw CLI::RuntimeError(1);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
"Whether to use 32-bit indices.")
|
||||
->type_name("(never|auto|always)");
|
||||
|
||||
app.add_option(
|
||||
"--compute-normals",
|
||||
[&](std::vector<std::string> choices) -> bool {
|
||||
for (const std::string choice : choices) {
|
||||
if (choice == "never") {
|
||||
gltfOptions.computeNormals = ComputeNormalsOption::NEVER;
|
||||
} else if (choice == "broken") {
|
||||
gltfOptions.computeNormals = ComputeNormalsOption::BROKEN;
|
||||
} else if (choice == "missing") {
|
||||
gltfOptions.computeNormals = ComputeNormalsOption::MISSING;
|
||||
} else if (choice == "always") {
|
||||
gltfOptions.computeNormals = ComputeNormalsOption::ALWAYS;
|
||||
} else {
|
||||
fmt::printf("Unknown --compute-normals option: %s\n", choice);
|
||||
throw CLI::RuntimeError(1);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
"When to compute vertex normals from mesh geometry.")
|
||||
->type_name("(never|broken|missing|always)");
|
||||
|
||||
app.add_option(
|
||||
"--anim-framerate",
|
||||
[&](std::vector<std::string> choices) -> bool {
|
||||
for (const std::string choice : choices) {
|
||||
if (choice == "bake24") {
|
||||
gltfOptions.animationFramerate = AnimationFramerateOptions::BAKE24;
|
||||
} else if (choice == "bake30") {
|
||||
gltfOptions.animationFramerate = AnimationFramerateOptions::BAKE30;
|
||||
} else if (choice == "bake60") {
|
||||
gltfOptions.animationFramerate = AnimationFramerateOptions::BAKE60;
|
||||
} else {
|
||||
fmt::printf("Unknown --anim-framerate: %s\n", choice);
|
||||
throw CLI::RuntimeError(1);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
"Select baked animation framerate.")
|
||||
->type_name("(bake24|bake30|bake60)");
|
||||
|
||||
const auto opt_flip_u = app.add_flag("--flip-u", "Flip all U texture coordinates.");
|
||||
const auto opt_no_flip_u = app.add_flag("--no-flip-u", "Don't flip U texture coordinates.");
|
||||
const auto opt_flip_v = app.add_flag("--flip-v", "Flip all V texture coordinates.");
|
||||
const auto opt_no_flip_v = app.add_flag("--no-flip-v", "Don't flip V texture coordinates.");
|
||||
|
||||
app.add_flag(
|
||||
"--pbr-metallic-roughness",
|
||||
gltfOptions.usePBRMetRough,
|
||||
"Try to glean glTF 2.0 native PBR attributes from the FBX.")
|
||||
->group("Materials");
|
||||
|
||||
app.add_flag(
|
||||
"--khr-materials-unlit",
|
||||
gltfOptions.useKHRMatUnlit,
|
||||
"Use KHR_materials_unlit extension to request an unlit shader.")
|
||||
->group("Materials");
|
||||
|
||||
app.add_flag_function(
|
||||
"--no-khr-lights-punctual",
|
||||
[&](size_t count) { gltfOptions.useKHRLightsPunctual = (count == 0); },
|
||||
"Don't use KHR_lights_punctual extension to export FBX lights.");
|
||||
|
||||
app.add_flag(
|
||||
"--user-properties",
|
||||
gltfOptions.enableUserProperties,
|
||||
"Transcribe FBX User Properties into glTF node and material 'extras'.");
|
||||
|
||||
app.add_flag(
|
||||
"--blend-shape-normals",
|
||||
gltfOptions.useBlendShapeNormals,
|
||||
"Include blend shape normals, if reported present by the FBX SDK.");
|
||||
|
||||
app.add_flag(
|
||||
"--blend-shape-tangents",
|
||||
gltfOptions.useBlendShapeTangents,
|
||||
"Include blend shape tangents, if reported present by the FBX SDK.");
|
||||
|
||||
app.add_option(
|
||||
"-k,--keep-attribute",
|
||||
[&](std::vector<std::string> attributes) -> bool {
|
||||
gltfOptions.keepAttribs =
|
||||
RAW_VERTEX_ATTRIBUTE_JOINT_INDICES | RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS;
|
||||
for (std::string attribute : attributes) {
|
||||
if (attribute == "position") {
|
||||
gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_POSITION;
|
||||
} else if (attribute == "normal") {
|
||||
gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_NORMAL;
|
||||
} else if (attribute == "tangent") {
|
||||
gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_TANGENT;
|
||||
} else if (attribute == "binormal") {
|
||||
gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_BINORMAL;
|
||||
} else if (attribute == "color") {
|
||||
gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_COLOR;
|
||||
} else if (attribute == "uv0") {
|
||||
gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_UV0;
|
||||
} else if (attribute == "uv1") {
|
||||
gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_UV1;
|
||||
} else if (attribute == "auto") {
|
||||
gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_AUTO;
|
||||
} else {
|
||||
fmt::printf("Unknown --keep-attribute option: %s\n", attribute);
|
||||
throw CLI::RuntimeError(1);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
"Used repeatedly to build a limiting set of vertex attributes to keep.")
|
||||
->type_size(-1)
|
||||
->type_name("(position|normal|tangent|binormial|color|uv0|uv1|auto)");
|
||||
|
||||
app.add_flag(
|
||||
"-d,--draco", gltfOptions.draco.enabled, "Apply Draco mesh compression to geometries.")
|
||||
->group("Draco");
|
||||
|
||||
app.add_option(
|
||||
"--draco-compression-level",
|
||||
gltfOptions.draco.compressionLevel,
|
||||
"The compression level to tune Draco to.",
|
||||
true)
|
||||
->check(CLI::Range(0, 10))
|
||||
->group("Draco");
|
||||
|
||||
app.add_option(
|
||||
"--draco-bits-for-position",
|
||||
gltfOptions.draco.quantBitsPosition,
|
||||
"How many bits to quantize position to.",
|
||||
true)
|
||||
->check(CLI::Range(1, 32))
|
||||
->group("Draco");
|
||||
|
||||
app.add_option(
|
||||
"--draco-bits-for-uv",
|
||||
gltfOptions.draco.quantBitsTexCoord,
|
||||
"How many bits to quantize UV coordinates to.",
|
||||
true)
|
||||
->check(CLI::Range(1, 32))
|
||||
->group("Draco");
|
||||
|
||||
app.add_option(
|
||||
"--draco-bits-for-normals",
|
||||
gltfOptions.draco.quantBitsNormal,
|
||||
"How many bits to quantize nornals to.",
|
||||
true)
|
||||
->check(CLI::Range(1, 32))
|
||||
->group("Draco");
|
||||
|
||||
app.add_option(
|
||||
"--draco-bits-for-colors",
|
||||
gltfOptions.draco.quantBitsColor,
|
||||
"How many bits to quantize colors to.",
|
||||
true)
|
||||
->check(CLI::Range(1, 32))
|
||||
->group("Draco");
|
||||
|
||||
app.add_option(
|
||||
"--draco-bits-for-other",
|
||||
gltfOptions.draco.quantBitsGeneric,
|
||||
"How many bits to quantize all other vertex attributes to.",
|
||||
true)
|
||||
->check(CLI::Range(1, 32))
|
||||
->group("Draco");
|
||||
|
||||
app.add_option("--fbx-temp-dir", gltfOptions.fbxTempDir, "Temporary directory to be used by FBX SDK.")->check(CLI::ExistingDirectory);
|
||||
|
||||
CLI11_PARSE(app, argc, argv);
|
||||
|
||||
bool do_flip_u = false;
|
||||
bool do_flip_v = true;
|
||||
// somewhat tedious way to resolve --flag vs --no-flag in order provided
|
||||
for (const auto opt : app.parse_order()) {
|
||||
do_flip_u = (do_flip_u || (opt == opt_flip_u)) && (opt != opt_no_flip_u);
|
||||
do_flip_v = (do_flip_v || (opt == opt_flip_v)) && (opt != opt_no_flip_v);
|
||||
}
|
||||
std::vector<std::function<Vec2f(Vec2f)>> texturesTransforms;
|
||||
if (do_flip_u || do_flip_v) {
|
||||
if (do_flip_u && do_flip_v) {
|
||||
texturesTransforms.emplace_back([](Vec2f uv) { return Vec2f(1.0 - uv[0], 1.0 - uv[1]); });
|
||||
} else if (do_flip_u) {
|
||||
texturesTransforms.emplace_back([](Vec2f uv) { return Vec2f(1.0 - uv[0], uv[1]); });
|
||||
} else {
|
||||
texturesTransforms.emplace_back([](Vec2f uv) { return Vec2f(uv[0], 1.0 - uv[1]); });
|
||||
}
|
||||
}
|
||||
if (verboseOutput) {
|
||||
if (do_flip_u) {
|
||||
fmt::printf("Flipping texture coordinates in the 'U' dimension.\n");
|
||||
}
|
||||
if (!do_flip_v) {
|
||||
fmt::printf("NOT flipping texture coordinates in the 'V' dimension.\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (inputPath.empty()) {
|
||||
fmt::printf("You must supply a FBX file to convert.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (!gltfOptions.useKHRMatUnlit && !gltfOptions.usePBRMetRough) {
|
||||
if (verboseOutput) {
|
||||
fmt::printf("Defaulting to --pbr-metallic-roughness material support.\n");
|
||||
}
|
||||
gltfOptions.usePBRMetRough = true;
|
||||
}
|
||||
|
||||
if (gltfOptions.embedResources && gltfOptions.outputBinary) {
|
||||
fmt::printf("Note: Ignoring --embed; it's meaningless with --binary.\n");
|
||||
}
|
||||
|
||||
if (outputPath.empty()) {
|
||||
// if -o is not given, default to the basename of the .fbx
|
||||
outputPath = "./" + FileUtils::GetFileBase(inputPath);
|
||||
}
|
||||
// the output folder in .gltf mode, not used for .glb
|
||||
std::string outputFolder;
|
||||
|
||||
// the path of the actual .glb or .gltf file
|
||||
std::string modelPath;
|
||||
const auto& suffix = FileUtils::GetFileSuffix(outputPath);
|
||||
|
||||
// Assume binary output if extension is glb
|
||||
if (suffix.has_value() && suffix.value() == "glb") {
|
||||
gltfOptions.outputBinary = true;
|
||||
}
|
||||
|
||||
if (gltfOptions.outputBinary) {
|
||||
// add .glb to output path, unless it already ends in exactly that
|
||||
if (suffix.has_value() && suffix.value() == "glb") {
|
||||
modelPath = outputPath;
|
||||
} else {
|
||||
modelPath = outputPath + ".glb";
|
||||
}
|
||||
// if the extension is gltf set the output folder to the parent directory
|
||||
} else if (suffix.has_value() && suffix.value() == "gltf") {
|
||||
outputFolder = FileUtils::getFolder(outputPath) + "/";
|
||||
modelPath = outputPath;
|
||||
} else {
|
||||
// in gltf mode, we create a folder and write into that
|
||||
outputFolder = fmt::format("{}_out/", outputPath.c_str());
|
||||
modelPath = outputFolder + FileUtils::GetFileName(outputPath) + ".gltf";
|
||||
}
|
||||
if (!FileUtils::CreatePath(modelPath.c_str())) {
|
||||
fmt::fprintf(stderr, "ERROR: Failed to create folder: %s'\n", outputFolder.c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
ModelData* data_render_model = nullptr;
|
||||
RawModel raw;
|
||||
|
||||
if (verboseOutput) {
|
||||
fmt::printf("Loading FBX File: %s\n", inputPath);
|
||||
}
|
||||
if (!LoadFBXFile(raw, inputPath, {"png", "jpg", "jpeg"}, gltfOptions)) {
|
||||
fmt::fprintf(stderr, "ERROR:: Failed to parse FBX: %s\n", inputPath);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!texturesTransforms.empty()) {
|
||||
raw.TransformTextures(texturesTransforms);
|
||||
}
|
||||
raw.Condense();
|
||||
raw.TransformGeometry(gltfOptions.computeNormals);
|
||||
|
||||
std::ofstream outStream; // note: auto-flushes in destructor
|
||||
const auto streamStart = outStream.tellp();
|
||||
|
||||
outStream.open(modelPath, std::ios::trunc | std::ios::ate | std::ios::out | std::ios::binary);
|
||||
if (outStream.fail()) {
|
||||
fmt::fprintf(stderr, "ERROR:: Couldn't open file for writing: %s\n", modelPath.c_str());
|
||||
return 1;
|
||||
}
|
||||
data_render_model = Raw2Gltf(outStream, outputFolder, raw, gltfOptions);
|
||||
|
||||
if (gltfOptions.outputBinary) {
|
||||
fmt::printf(
|
||||
"Wrote %lu bytes of binary glTF to %s.\n",
|
||||
(unsigned long)(outStream.tellp() - streamStart),
|
||||
modelPath);
|
||||
delete data_render_model;
|
||||
return 0;
|
||||
}
|
||||
|
||||
fmt::printf(
|
||||
"Wrote %lu bytes of glTF to %s.\n",
|
||||
(unsigned long)(outStream.tellp() - streamStart),
|
||||
modelPath);
|
||||
|
||||
if (gltfOptions.embedResources) {
|
||||
// we're done: everything was inlined into the glTF JSON
|
||||
delete data_render_model;
|
||||
return 0;
|
||||
}
|
||||
|
||||
assert(!outputFolder.empty());
|
||||
|
||||
const std::string binaryPath = outputFolder + extBufferFilename;
|
||||
FILE* fp = fopen(binaryPath.c_str(), "wb");
|
||||
if (fp == nullptr) {
|
||||
fmt::fprintf(stderr, "ERROR:: Couldn't open file '%s' for writing.\n", binaryPath);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (data_render_model->binary->empty() == false) {
|
||||
const unsigned char* binaryData = &(*data_render_model->binary)[0];
|
||||
unsigned long binarySize = data_render_model->binary->size();
|
||||
if (fwrite(binaryData, binarySize, 1, fp) != 1) {
|
||||
fmt::fprintf(
|
||||
stderr, "ERROR: Failed to write %lu bytes to file '%s'.\n", binarySize, binaryPath);
|
||||
fclose(fp);
|
||||
return 1;
|
||||
}
|
||||
fclose(fp);
|
||||
fmt::printf("Wrote %lu bytes of binary data to %s.\n", binarySize, binaryPath);
|
||||
}
|
||||
|
||||
delete data_render_model;
|
||||
return 0;
|
||||
}
|
122
src/FBX2glTF.h
122
src/FBX2glTF.h
|
@ -1,26 +1,128 @@
|
|||
/**
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#ifndef __FBX2GLTF_H__
|
||||
#define __FBX2GLTF_H__
|
||||
#pragma once
|
||||
|
||||
#if defined ( _WIN32 )
|
||||
// This can be a macro under Windows, confusing FMT
|
||||
#undef isnan
|
||||
#include <climits>
|
||||
#include <string>
|
||||
|
||||
#if defined(_WIN32)
|
||||
// Tell Windows not to define min() and max() macros
|
||||
#define NOMINMAX
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
#define FBX2GLTF_VERSION std::string("0.9.7")
|
||||
|
||||
#include <fmt/printf.h>
|
||||
|
||||
#include <fbxsdk.h>
|
||||
|
||||
#include "mathfu.h"
|
||||
#if defined(_WIN32)
|
||||
// this is defined in fbxmath.h
|
||||
#undef isnan
|
||||
#undef snprintf
|
||||
#endif
|
||||
|
||||
#endif // !__FBX2GLTF_H__
|
||||
#include "mathfu.hpp"
|
||||
|
||||
// give all modules access to our tweaked JSON
|
||||
#include <fifo_map.hpp>
|
||||
#include <json.hpp>
|
||||
|
||||
template <class K, class V, class ignore, class A>
|
||||
using workaround_fifo_map = nlohmann::fifo_map<K, V, nlohmann::fifo_map_compare<K>, A>;
|
||||
|
||||
using json = nlohmann::basic_json<workaround_fifo_map>;
|
||||
|
||||
extern bool verboseOutput;
|
||||
|
||||
/**
|
||||
* Centralises all the laborious downcasting from your OS' 64-bit
|
||||
* index variables down to the uint32s that glTF is built out of.
|
||||
*/
|
||||
inline uint32_t to_uint32(size_t n) {
|
||||
assert(n < UINT_MAX);
|
||||
return static_cast<uint32_t>(n);
|
||||
}
|
||||
|
||||
/**
|
||||
* The variuos situations in which the user may wish for us to (re-)compute normals for our
|
||||
* vertices.
|
||||
*/
|
||||
enum class ComputeNormalsOption {
|
||||
NEVER, // do not ever compute any normals (results in broken glTF for some sources)
|
||||
BROKEN, // replace zero-length normals in any mesh that has a normal layer
|
||||
MISSING, // if a mesh lacks normals, compute them all
|
||||
ALWAYS // compute a new normal for every vertex, obliterating whatever may have been there before
|
||||
};
|
||||
|
||||
enum class UseLongIndicesOptions {
|
||||
NEVER, // only ever use 16-bit indices
|
||||
AUTO, // use shorts or longs depending on vertex count
|
||||
ALWAYS, // only ever use 32-bit indices
|
||||
};
|
||||
|
||||
enum class AnimationFramerateOptions {
|
||||
BAKE24, // bake animations at 24 fps
|
||||
BAKE30, // bake animations at 30 fps
|
||||
BAKE60, // bake animations at 60 fps
|
||||
};
|
||||
|
||||
/**
|
||||
* User-supplied options that dictate the nature of the glTF being generated.
|
||||
*/
|
||||
struct GltfOptions {
|
||||
/**
|
||||
* If negative, disabled. Otherwise, a bitfield of RawVertexAttributes that
|
||||
* specify the largest set of attributes that'll ever be kept for a vertex.
|
||||
* The special bit RAW_VERTEX_ATTRIBUTE_AUTO triggers smart mode, where the
|
||||
* attributes to keep are inferred from which textures are supplied.
|
||||
*/
|
||||
int keepAttribs{-1};
|
||||
/** Whether to output a .glb file, the binary format of glTF. */
|
||||
bool outputBinary{false};
|
||||
/** If non-binary, whether to inline all resources, for a single (large) .glTF file. */
|
||||
bool embedResources{false};
|
||||
|
||||
/** Whether and how to use KHR_draco_mesh_compression to minimize static geometry size. */
|
||||
struct {
|
||||
bool enabled = false;
|
||||
int compressionLevel = 7;
|
||||
int quantBitsPosition = 14;
|
||||
int quantBitsTexCoord = 10;
|
||||
int quantBitsNormal = 10;
|
||||
int quantBitsColor = 8;
|
||||
int quantBitsGeneric = 8;
|
||||
} draco;
|
||||
|
||||
/** Whether to include FBX User Properties as 'extras' metadata in glTF nodes. */
|
||||
bool enableUserProperties{false};
|
||||
|
||||
/** Whether to use KHR_materials_unlit to extend materials definitions. */
|
||||
bool useKHRMatUnlit{false};
|
||||
/** Whether to populate the pbrMetallicRoughness substruct in materials. */
|
||||
bool usePBRMetRough{false};
|
||||
|
||||
/** Whether to include lights through the KHR_punctual_lights extension. */
|
||||
bool useKHRLightsPunctual{true};
|
||||
|
||||
/** Whether to include blend shape normals, if present according to the SDK. */
|
||||
bool useBlendShapeNormals{false};
|
||||
/** Whether to include blend shape tangents, if present according to the SDK. */
|
||||
bool useBlendShapeTangents{false};
|
||||
/** When to compute vertex normals from geometry. */
|
||||
ComputeNormalsOption computeNormals = ComputeNormalsOption::BROKEN;
|
||||
/** When to use 32-bit indices. */
|
||||
UseLongIndicesOptions useLongIndices = UseLongIndicesOptions::AUTO;
|
||||
/** Select baked animation framerate. */
|
||||
AnimationFramerateOptions animationFramerate = AnimationFramerateOptions::BAKE24;
|
||||
|
||||
/** Temporary directory used by FBX SDK. */
|
||||
std::string fbxTempDir;
|
||||
};
|
||||
|
|
1064
src/Fbx2Raw.cpp
1064
src/Fbx2Raw.cpp
File diff suppressed because it is too large
Load Diff
|
@ -1,17 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#ifndef __FBX2RAW_H__
|
||||
#define __FBX2RAW_H__
|
||||
|
||||
#include "RawModel.h"
|
||||
|
||||
bool LoadFBXFile(RawModel &raw, const char *fbxFileName, const char *textureExtensions);
|
||||
|
||||
#endif // !__FBX2RAW_H__
|
751
src/Raw2Gltf.cpp
751
src/Raw2Gltf.cpp
|
@ -1,751 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#include <cstdint>
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
#include "FBX2glTF.h"
|
||||
#include "utils/String_Utils.h"
|
||||
#include "RawModel.h"
|
||||
#include "Raw2Gltf.h"
|
||||
|
||||
#include "glTF/AccessorData.h"
|
||||
#include "glTF/AnimationData.h"
|
||||
#include "glTF/BufferData.h"
|
||||
#include "glTF/BufferViewData.h"
|
||||
#include "glTF/CameraData.h"
|
||||
#include "glTF/ImageData.h"
|
||||
#include "glTF/MaterialData.h"
|
||||
#include "glTF/MeshData.h"
|
||||
#include "glTF/NodeData.h"
|
||||
#include "glTF/PrimitiveData.h"
|
||||
#include "glTF/SamplerData.h"
|
||||
#include "glTF/SceneData.h"
|
||||
#include "glTF/SkinData.h"
|
||||
#include "glTF/TextureData.h"
|
||||
|
||||
typedef unsigned short TriangleIndex;
|
||||
|
||||
extern bool verboseOutput;
|
||||
|
||||
const static std::string defaultSceneName = "Root Scene";
|
||||
|
||||
/**
|
||||
* glTF 2.0 is based on the idea that data structs within a file are referenced by index; an accessor will
|
||||
* point to the n:th buffer view, and so on. The Holder class takes a freshly instantiated class, and then
|
||||
* creates, stored, and returns a shared_ptr<T> for it.
|
||||
*
|
||||
* The idea is that every glTF resource in the file will live as long as the Holder does, and the Holders
|
||||
* are all kept in the GLTFData struct. Clients may certainly cnhoose to perpetuate the full shared_ptr<T>
|
||||
* reference counting type, but generally speaking we pass around simple T& and T* types because the GLTFData
|
||||
* struct will, by design, outlive all other activity that takes place during in a single conversion run.
|
||||
*/
|
||||
template<typename T>
|
||||
struct Holder
|
||||
{
|
||||
std::vector<std::shared_ptr<T>> ptrs;
|
||||
std::shared_ptr<T> hold(T *ptr)
|
||||
{
|
||||
ptr->ix = ptrs.size();
|
||||
ptrs.emplace_back(ptr);
|
||||
return ptrs.back();
|
||||
}
|
||||
};
|
||||
|
||||
struct GLTFData
|
||||
{
|
||||
explicit GLTFData(bool _isGlb)
|
||||
: binary(new std::vector<uint8_t>),
|
||||
isGlb(_isGlb)
|
||||
{
|
||||
}
|
||||
|
||||
std::shared_ptr<BufferViewData> GetAlignedBufferView(BufferData &buffer, const BufferViewData::GL_ArrayType target)
|
||||
{
|
||||
unsigned long bufferSize = this->binary->size();
|
||||
if ((bufferSize % 4) > 0) {
|
||||
bufferSize += (4 - (bufferSize % 4));
|
||||
this->binary->resize(bufferSize);
|
||||
}
|
||||
return this->bufferViews.hold(new BufferViewData(buffer, bufferSize, target));
|
||||
}
|
||||
|
||||
// add a bufferview on the fly and copy data into it
|
||||
std::shared_ptr<BufferViewData> AddRawBufferView(BufferData &buffer, const char *source, uint32_t bytes)
|
||||
{
|
||||
auto bufferView = GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_NONE);
|
||||
bufferView->byteLength = bytes;
|
||||
|
||||
// make space for the new bytes (possibly moving the underlying data)
|
||||
unsigned long bufferSize = this->binary->size();
|
||||
this->binary->resize(bufferSize + bytes);
|
||||
|
||||
// and copy them into place
|
||||
memcpy(&(*this->binary)[bufferSize], source, bytes);
|
||||
return bufferView;
|
||||
}
|
||||
|
||||
std::shared_ptr<BufferViewData> AddBufferViewForFile(BufferData &buffer, const std::string &filename)
|
||||
{
|
||||
// see if we've already created a BufferViewData for this precise file
|
||||
auto iter = filenameToBufferView.find(filename);
|
||||
if (iter != filenameToBufferView.end()) {
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
std::shared_ptr<BufferViewData> result;
|
||||
std::ifstream file(filename, std::ios::binary | std::ios::ate);
|
||||
if (file) {
|
||||
std::streamsize size = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
std::vector<char> fileBuffer(size);
|
||||
if (file.read(fileBuffer.data(), size)) {
|
||||
result = AddRawBufferView(buffer, fileBuffer.data(), size);
|
||||
} else {
|
||||
fmt::printf("Warning: Couldn't read %lu bytes from %s, skipping\n", size, filename);
|
||||
}
|
||||
} else {
|
||||
fmt::printf("Warning: Couldn't open texture file %s, skipping\n", filename);
|
||||
}
|
||||
// note that we persist here not only success, but also failure, as nullptr
|
||||
filenameToBufferView[filename] = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
template<class T>
|
||||
std::shared_ptr<AccessorData> AddAccessorWithView(
|
||||
BufferViewData &bufferView, const GLType &type, const std::vector<T> &source)
|
||||
{
|
||||
auto accessor = accessors.hold(new AccessorData(bufferView, type));
|
||||
accessor->appendAsBinaryArray(source, *binary);
|
||||
bufferView.byteLength = accessor->byteLength();
|
||||
return accessor;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
std::shared_ptr<AccessorData> AddAccessorAndView(
|
||||
BufferData &buffer, const GLType &type, const std::vector<T> &source)
|
||||
{
|
||||
auto bufferView = GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_NONE);
|
||||
return AddAccessorWithView(*bufferView, type, source);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
std::shared_ptr<AccessorData> AddAttributeToPrimitive(
|
||||
BufferData &buffer, const RawModel &surfaceModel, PrimitiveData &primitive,
|
||||
const AttributeDefinition<T> &attrDef)
|
||||
{
|
||||
// copy attribute data into vector
|
||||
std::vector<T> attribArr;
|
||||
surfaceModel.GetAttributeArray<T>(attribArr, attrDef.rawAttributeIx);
|
||||
|
||||
std::shared_ptr<AccessorData> accessor;
|
||||
if (attrDef.dracoComponentType != draco::DT_INVALID && primitive.dracoMesh != nullptr) {
|
||||
primitive.AddDracoAttrib(attrDef, attribArr);
|
||||
|
||||
accessor = accessors.hold(new AccessorData(attrDef.glType));
|
||||
accessor->count = attribArr.size();
|
||||
} else {
|
||||
auto bufferView = GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_BUFFER);
|
||||
accessor = AddAccessorWithView(*bufferView, attrDef.glType, attribArr);
|
||||
}
|
||||
primitive.AddAttrib(attrDef.gltfName, *accessor);
|
||||
return accessor;
|
||||
};
|
||||
|
||||
template<class T>
|
||||
void serializeHolder(json &glTFJson, std::string key, const Holder<T> holder)
|
||||
{
|
||||
if (!holder.ptrs.empty()) {
|
||||
std::vector<json> bits;
|
||||
for (const auto &ptr : holder.ptrs) {
|
||||
bits.push_back(ptr->serialize());
|
||||
}
|
||||
glTFJson[key] = bits;
|
||||
}
|
||||
}
|
||||
|
||||
void serializeHolders(json &glTFJson)
|
||||
{
|
||||
serializeHolder(glTFJson, "buffers", buffers);
|
||||
serializeHolder(glTFJson, "bufferViews", bufferViews);
|
||||
serializeHolder(glTFJson, "scenes", scenes);
|
||||
serializeHolder(glTFJson, "accessors", accessors);
|
||||
serializeHolder(glTFJson, "images", images);
|
||||
serializeHolder(glTFJson, "samplers", samplers);
|
||||
serializeHolder(glTFJson, "textures", textures);
|
||||
serializeHolder(glTFJson, "materials", materials);
|
||||
serializeHolder(glTFJson, "meshes", meshes);
|
||||
serializeHolder(glTFJson, "skins", skins);
|
||||
serializeHolder(glTFJson, "animations", animations);
|
||||
serializeHolder(glTFJson, "cameras", cameras);
|
||||
serializeHolder(glTFJson, "nodes", nodes);
|
||||
}
|
||||
|
||||
const bool isGlb;
|
||||
|
||||
// cache BufferViewData instances that've already been created from a given filename
|
||||
std::map<std::string, std::shared_ptr<BufferViewData>> filenameToBufferView;
|
||||
|
||||
std::shared_ptr<std::vector<uint8_t> > binary;
|
||||
|
||||
|
||||
Holder<BufferData> buffers;
|
||||
Holder<BufferViewData> bufferViews;
|
||||
Holder<AccessorData> accessors;
|
||||
Holder<ImageData> images;
|
||||
Holder<SamplerData> samplers;
|
||||
Holder<TextureData> textures;
|
||||
Holder<MaterialData> materials;
|
||||
Holder<MeshData> meshes;
|
||||
Holder<SkinData> skins;
|
||||
Holder<AnimationData> animations;
|
||||
Holder<CameraData> cameras;
|
||||
Holder<NodeData> nodes;
|
||||
Holder<SceneData> scenes;
|
||||
};
|
||||
|
||||
/**
|
||||
* This method sanity-checks existance and then returns a *reference* to the *Data instance
|
||||
* registered under that name. This is safe in the context of this tool, where all such data
|
||||
* classes are guaranteed to stick around for the duration of the process.
|
||||
*/
|
||||
template<typename T>
|
||||
T &require(std::map<std::string, std::shared_ptr<T>> map, std::string key)
|
||||
{
|
||||
auto iter = map.find(key);
|
||||
assert(iter != map.end());
|
||||
T &result = *iter->second;
|
||||
return result;
|
||||
}
|
||||
|
||||
static const std::vector<TriangleIndex> getIndexArray(const RawModel &raw)
|
||||
{
|
||||
std::vector<TriangleIndex> result;
|
||||
|
||||
for (int i = 0; i < raw.GetTriangleCount(); i++) {
|
||||
result.push_back((TriangleIndex) raw.GetTriangle(i).verts[0]);
|
||||
result.push_back((TriangleIndex) raw.GetTriangle(i).verts[1]);
|
||||
result.push_back((TriangleIndex) raw.GetTriangle(i).verts[2]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// TODO: replace with a proper MaterialHasher class
|
||||
static const std::string materialHash(const RawMaterial &m) {
|
||||
return m.name + "_" + std::to_string(m.type);
|
||||
}
|
||||
|
||||
ModelData *Raw2Gltf(
|
||||
std::ofstream &gltfOutStream,
|
||||
const RawModel &raw,
|
||||
const GltfOptions &options
|
||||
)
|
||||
{
|
||||
if (verboseOutput) {
|
||||
fmt::printf("Building render model...\n");
|
||||
for (int i = 0; i < raw.GetMaterialCount(); i++) {
|
||||
fmt::printf(
|
||||
"Material %d: %s [shading: %s]\n", i, raw.GetMaterial(i).name.c_str(),
|
||||
raw.GetMaterial(i).shadingModel.c_str());
|
||||
}
|
||||
if (raw.GetVertexCount() > 2 * raw.GetTriangleCount()) {
|
||||
fmt::printf(
|
||||
"Warning: High vertex count. Make sure there are no unnecessary vertex attributes. (see -keepAttribute cmd-line option)");
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<RawModel> materialModels;
|
||||
raw.CreateMaterialModels(materialModels, (1 << (sizeof(TriangleIndex) * 8)), options.keepAttribs, true);
|
||||
|
||||
if (verboseOutput) {
|
||||
fmt::printf("%7d vertices\n", raw.GetVertexCount());
|
||||
fmt::printf("%7d triangles\n", raw.GetTriangleCount());
|
||||
fmt::printf("%7d textures\n", raw.GetTextureCount());
|
||||
fmt::printf("%7d nodes\n", raw.GetNodeCount());
|
||||
fmt::printf("%7d surfaces\n", (int) materialModels.size());
|
||||
fmt::printf("%7d animations\n", raw.GetAnimationCount());
|
||||
}
|
||||
|
||||
std::unique_ptr<GLTFData> gltf(new GLTFData(options.outputBinary));
|
||||
|
||||
std::map<std::string, std::shared_ptr<NodeData>> nodesByName;
|
||||
std::map<std::string, std::shared_ptr<MaterialData>> materialsByName;
|
||||
std::map<std::string, std::shared_ptr<MeshData>> meshByNodeName;
|
||||
|
||||
// for now, we only have one buffer; data->binary points to the same vector as that BufferData does.
|
||||
BufferData &buffer = *gltf->buffers.hold(
|
||||
options.outputBinary ?
|
||||
new BufferData(gltf->binary) :
|
||||
new BufferData(extBufferFilename, gltf->binary, options.embedResources));
|
||||
{
|
||||
//
|
||||
// nodes
|
||||
//
|
||||
|
||||
for (int i = 0; i < raw.GetNodeCount(); i++) {
|
||||
// assumption: RawNode index == NodeData index
|
||||
const RawNode &node = raw.GetNode(i);
|
||||
|
||||
auto nodeData = gltf->nodes.hold(
|
||||
new NodeData(node.name, node.translation, node.rotation, node.scale, node.isJoint));
|
||||
|
||||
for (const auto &childName : node.childNames) {
|
||||
int childIx = raw.GetNodeByName(childName.c_str());
|
||||
assert(childIx >= 0);
|
||||
nodeData->AddChildNode(childIx);
|
||||
}
|
||||
assert(nodesByName.find(nodeData->name) == nodesByName.end());
|
||||
nodesByName.insert(std::make_pair(nodeData->name, nodeData));
|
||||
}
|
||||
|
||||
//
|
||||
// animations
|
||||
//
|
||||
|
||||
for (int i = 0; i < raw.GetAnimationCount(); i++) {
|
||||
const RawAnimation &animation = raw.GetAnimation(i);
|
||||
|
||||
auto accessor = gltf->AddAccessorAndView(buffer, GLT_FLOAT, animation.times);
|
||||
accessor->min = { *std::min_element(std::begin(animation.times), std::end(animation.times)) };
|
||||
accessor->max = { *std::max_element(std::begin(animation.times), std::end(animation.times)) };
|
||||
|
||||
AnimationData &aDat = *gltf->animations.hold(new AnimationData(animation.name, *accessor));
|
||||
if (verboseOutput) {
|
||||
fmt::printf("Animation '%s' has %lu channels:\n", animation.name.c_str(), animation.channels.size());
|
||||
}
|
||||
|
||||
for (size_t j = 0; j < animation.channels.size(); j++) {
|
||||
const RawChannel &channel = animation.channels[j];
|
||||
const RawNode &node = raw.GetNode(channel.nodeIndex);
|
||||
|
||||
if (verboseOutput) {
|
||||
fmt::printf(
|
||||
" Channel %lu (%s) has translations/rotations/scales: [%lu, %lu, %lu]\n",
|
||||
j, node.name.c_str(), channel.translations.size(),
|
||||
channel.rotations.size(), channel.scales.size());
|
||||
}
|
||||
|
||||
NodeData &nDat = require(nodesByName, node.name);
|
||||
if (!channel.translations.empty()) {
|
||||
aDat.AddNodeChannel(nDat, *gltf->AddAccessorAndView(buffer, GLT_VEC3F, channel.translations), "translation");
|
||||
}
|
||||
if (!channel.rotations.empty()) {
|
||||
aDat.AddNodeChannel(nDat, *gltf->AddAccessorAndView(buffer, GLT_QUATF, channel.rotations), "rotation");
|
||||
}
|
||||
if (!channel.scales.empty()) {
|
||||
aDat.AddNodeChannel(nDat, *gltf->AddAccessorAndView(buffer, GLT_VEC3F, channel.scales), "scale");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// samplers
|
||||
//
|
||||
|
||||
SamplerData &defaultSampler = *gltf->samplers.hold(new SamplerData());
|
||||
|
||||
//
|
||||
// textures
|
||||
//
|
||||
|
||||
for (int textureIndex = 0; textureIndex < raw.GetTextureCount(); textureIndex++) {
|
||||
const RawTexture &texture = raw.GetTexture(textureIndex);
|
||||
const std::string textureName = Gltf::StringUtils::GetFileBaseString(texture.name);
|
||||
const std::string texFilename = texture.fileName;
|
||||
|
||||
ImageData *source = nullptr;
|
||||
if (options.outputBinary) {
|
||||
auto bufferView = gltf->AddBufferViewForFile(buffer, texFilename);
|
||||
if (bufferView) {
|
||||
source = new ImageData(textureName, *bufferView, "image/png");
|
||||
}
|
||||
|
||||
} else {
|
||||
// TODO: don't add .ktx here; try to work out a reasonable relative path.
|
||||
source = new ImageData(textureName, textureName + ".ktx");
|
||||
}
|
||||
if (!source) {
|
||||
// fallback is tiny transparent gif
|
||||
source = new ImageData(textureName, "data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=");
|
||||
}
|
||||
|
||||
TextureData &texDat = *gltf->textures.hold(
|
||||
new TextureData(textureName, defaultSampler, *gltf->images.hold(source)));
|
||||
assert(texDat.ix == textureIndex);
|
||||
}
|
||||
|
||||
//
|
||||
// materials
|
||||
//
|
||||
|
||||
for (int materialIndex = 0; materialIndex < raw.GetMaterialCount(); materialIndex++) {
|
||||
const RawMaterial &material = raw.GetMaterial(materialIndex);
|
||||
const bool isTransparent =
|
||||
material.type == RAW_MATERIAL_TYPE_VERTEX_COLORED ||
|
||||
material.type == RAW_MATERIAL_TYPE_SKINNED_VERTEX_COLORED ||
|
||||
material.type == RAW_MATERIAL_TYPE_TRANSPARENT ||
|
||||
material.type == RAW_MATERIAL_TYPE_SKINNED_TRANSPARENT;
|
||||
|
||||
// find a texture by usage and return it as a TextureData*, or nullptr if none exists.
|
||||
auto getTex = [&](RawTextureUsage usage)
|
||||
{
|
||||
// note that we depend on TextureData.ix == rawTexture's index
|
||||
return (material.textures[usage] >= 0) ? gltf->textures.ptrs[material.textures[usage]].get() : nullptr;
|
||||
};
|
||||
|
||||
std::shared_ptr<PBRMetallicRoughness> pbrMetRough;
|
||||
if (options.usePBRMetRough) {
|
||||
pbrMetRough.reset(new PBRMetallicRoughness(getTex(RAW_TEXTURE_USAGE_DIFFUSE), material.diffuseFactor));
|
||||
}
|
||||
std::shared_ptr<PBRSpecularGlossiness> pbrSpecGloss;
|
||||
if (options.usePBRSpecGloss) {
|
||||
pbrSpecGloss.reset(
|
||||
new PBRSpecularGlossiness(
|
||||
getTex(RAW_TEXTURE_USAGE_DIFFUSE), material.diffuseFactor,
|
||||
getTex(RAW_TEXTURE_USAGE_SPECULAR), material.specularFactor, material.shininess));
|
||||
}
|
||||
|
||||
std::shared_ptr<KHRCommonMats> khrComMat;
|
||||
if (options.useKHRMatCom) {
|
||||
auto type = KHRCommonMats::MaterialType::Constant;
|
||||
if (material.shadingModel == "Lambert") {
|
||||
type = KHRCommonMats::MaterialType::Lambert;
|
||||
} else if (material.shadingModel == "Blinn") {
|
||||
type = KHRCommonMats::MaterialType::Blinn;
|
||||
} else if (material.shadingModel == "Phong") {
|
||||
type = KHRCommonMats::MaterialType::Phong;
|
||||
}
|
||||
khrComMat.reset(
|
||||
new KHRCommonMats(
|
||||
type,
|
||||
getTex(RAW_TEXTURE_USAGE_SHININESS), material.shininess,
|
||||
getTex(RAW_TEXTURE_USAGE_AMBIENT), material.ambientFactor,
|
||||
getTex(RAW_TEXTURE_USAGE_DIFFUSE), material.diffuseFactor,
|
||||
getTex(RAW_TEXTURE_USAGE_SPECULAR), material.specularFactor));
|
||||
}
|
||||
std::shared_ptr<MaterialData> mData = gltf->materials.hold(
|
||||
new MaterialData(
|
||||
material.name, isTransparent, getTex(RAW_TEXTURE_USAGE_NORMAL),
|
||||
getTex(RAW_TEXTURE_USAGE_EMISSIVE), material.emissiveFactor,
|
||||
khrComMat, pbrMetRough, pbrSpecGloss));
|
||||
materialsByName[materialHash(material)] = mData;
|
||||
}
|
||||
|
||||
//
|
||||
// surfaces
|
||||
//
|
||||
|
||||
// in GLTF 2.0, the structural limitation is that a node can
|
||||
// only belong to a single mesh. A mesh can however contain any
|
||||
// number of primitives, which are essentially little meshes.
|
||||
//
|
||||
// so each RawSurface turns into a primitive, and we sort them
|
||||
// by root node using this map; one mesh per node.
|
||||
|
||||
for (size_t surfaceIndex = 0; surfaceIndex < materialModels.size(); surfaceIndex++) {
|
||||
const RawModel &surfaceModel = materialModels[surfaceIndex];
|
||||
assert(surfaceModel.GetSurfaceCount() == 1);
|
||||
|
||||
const RawSurface &rawSurface = surfaceModel.GetSurface(0);
|
||||
const std::string surfaceName = std::to_string(surfaceIndex) + "_" + rawSurface.name;
|
||||
|
||||
const RawMaterial &rawMaterial = surfaceModel.GetMaterial(surfaceModel.GetTriangle(0).materialIndex);
|
||||
if (rawMaterial.textures[RAW_TEXTURE_USAGE_DIFFUSE] < 0 &&
|
||||
(surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_COLOR) == 0) {
|
||||
if (verboseOutput) {
|
||||
fmt::printf("Warning: surface %s has neither texture nor vertex colors.\n", surfaceName.c_str());
|
||||
}
|
||||
}
|
||||
const MaterialData &mData = require(materialsByName, materialHash(rawMaterial));
|
||||
|
||||
std::string nodeName = rawSurface.nodeName;
|
||||
NodeData &meshNode = require(nodesByName, nodeName);
|
||||
|
||||
MeshData *mesh = nullptr;
|
||||
auto meshIter = meshByNodeName.find(nodeName);
|
||||
if (meshIter != meshByNodeName.end()) {
|
||||
mesh = meshIter->second.get();
|
||||
|
||||
} else {
|
||||
auto meshPtr = gltf->meshes.hold(new MeshData(rawSurface.name));
|
||||
meshByNodeName[nodeName] = meshPtr;
|
||||
meshNode.SetMesh(meshPtr->ix);
|
||||
mesh = meshPtr.get();
|
||||
}
|
||||
|
||||
//
|
||||
// surface skin
|
||||
//
|
||||
if (!rawSurface.jointNames.empty()) {
|
||||
if (meshNode.skin == -1) {
|
||||
// glTF uses column-major matrices
|
||||
std::vector<Mat4f> inverseBindMatrices;
|
||||
for (const auto &inverseBindMatrice : rawSurface.inverseBindMatrices) {
|
||||
inverseBindMatrices.push_back(inverseBindMatrice.Transpose());
|
||||
}
|
||||
|
||||
std::vector<uint32_t> jointIndexes;
|
||||
for (const auto &jointName : rawSurface.jointNames) {
|
||||
jointIndexes.push_back(require(nodesByName, jointName).ix);
|
||||
}
|
||||
|
||||
// Write out inverseBindMatrices
|
||||
auto accIBM = gltf->AddAccessorAndView(buffer, GLT_MAT4F, inverseBindMatrices);
|
||||
|
||||
auto skeletonRoot = require(nodesByName, rawSurface.skeletonRootName);
|
||||
auto skin = *gltf->skins.hold(new SkinData(jointIndexes, *accIBM, skeletonRoot));
|
||||
meshNode.SetSkin(skin.ix);
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<PrimitiveData> primitive;
|
||||
if (options.useDraco) {
|
||||
int triangleCount = surfaceModel.GetTriangleCount();
|
||||
|
||||
// initialize Draco mesh with vertex index information
|
||||
std::shared_ptr<draco::Mesh> dracoMesh;
|
||||
dracoMesh->SetNumFaces(static_cast<size_t>(triangleCount));
|
||||
|
||||
for (uint32_t ii = 0; ii < triangleCount; ii++) {
|
||||
draco::Mesh::Face face;
|
||||
face[0] = surfaceModel.GetTriangle(ii).verts[0];
|
||||
face[1] = surfaceModel.GetTriangle(ii).verts[1];
|
||||
face[2] = surfaceModel.GetTriangle(ii).verts[2];
|
||||
dracoMesh->SetFace(draco::FaceIndex(ii), face);
|
||||
}
|
||||
|
||||
AccessorData &indexes = *gltf->accessors.hold(new AccessorData(GLT_USHORT));
|
||||
indexes.count = 3 * triangleCount;
|
||||
primitive.reset(new PrimitiveData(indexes, mData, dracoMesh));
|
||||
} else {
|
||||
const AccessorData &indexes = *gltf->AddAccessorWithView(
|
||||
*gltf->GetAlignedBufferView(buffer, BufferViewData::GL_ELEMENT_ARRAY_BUFFER),
|
||||
GLT_USHORT, getIndexArray(surfaceModel));
|
||||
primitive.reset(new PrimitiveData(indexes, mData));
|
||||
};
|
||||
|
||||
//
|
||||
// surface vertices
|
||||
//
|
||||
{
|
||||
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_POSITION) != 0) {
|
||||
const AttributeDefinition<Vec3f> ATTR_POSITION("POSITION", &RawVertex::position,
|
||||
GLT_VEC3F, draco::GeometryAttribute::POSITION, draco::DT_FLOAT32);
|
||||
auto accessor = gltf->AddAttributeToPrimitive<Vec3f>(
|
||||
buffer, surfaceModel, *primitive, ATTR_POSITION);
|
||||
|
||||
accessor->min = toStdVec(rawSurface.bounds.min);
|
||||
accessor->max = toStdVec(rawSurface.bounds.max);
|
||||
}
|
||||
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_NORMAL) != 0) {
|
||||
const AttributeDefinition<Vec3f> ATTR_NORMAL("NORMAL", &RawVertex::normal,
|
||||
GLT_VEC3F, draco::GeometryAttribute::NORMAL, draco::DT_FLOAT32);
|
||||
gltf->AddAttributeToPrimitive<Vec3f>(buffer, surfaceModel, *primitive, ATTR_NORMAL);
|
||||
}
|
||||
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_TANGENT) != 0) {
|
||||
const AttributeDefinition<Vec4f> ATTR_TANGENT("TANGENT", &RawVertex::tangent, GLT_VEC4F);
|
||||
gltf->AddAttributeToPrimitive<Vec4f>(buffer, surfaceModel, *primitive, ATTR_TANGENT);
|
||||
}
|
||||
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_COLOR) != 0) {
|
||||
const AttributeDefinition<Vec4f> ATTR_COLOR("COLOR_0", &RawVertex::color, GLT_VEC4F,
|
||||
draco::GeometryAttribute::COLOR, draco::DT_FLOAT32);
|
||||
gltf->AddAttributeToPrimitive<Vec4f>(buffer, surfaceModel, *primitive, ATTR_COLOR);
|
||||
}
|
||||
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_UV0) != 0) {
|
||||
const AttributeDefinition<Vec2f> ATTR_TEXCOORD_0("TEXCOORD_0", &RawVertex::uv0,
|
||||
GLT_VEC2F, draco::GeometryAttribute::TEX_COORD, draco::DT_FLOAT32);
|
||||
gltf->AddAttributeToPrimitive<Vec2f>(buffer, surfaceModel, *primitive, ATTR_TEXCOORD_0);
|
||||
}
|
||||
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_UV1) != 0) {
|
||||
const AttributeDefinition<Vec2f> ATTR_TEXCOORD_1("TEXCOORD_1", &RawVertex::uv1,
|
||||
GLT_VEC2F, draco::GeometryAttribute::TEX_COORD, draco::DT_FLOAT32);
|
||||
gltf->AddAttributeToPrimitive<Vec2f>(buffer, surfaceModel, *primitive, ATTR_TEXCOORD_1);
|
||||
}
|
||||
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_JOINT_INDICES) != 0) {
|
||||
const AttributeDefinition<Vec4i> ATTR_JOINTS("JOINTS_0", &RawVertex::jointIndices,
|
||||
GLT_VEC4I, draco::GeometryAttribute::GENERIC, draco::DT_UINT16);
|
||||
gltf->AddAttributeToPrimitive<Vec4i>(buffer, surfaceModel, *primitive, ATTR_JOINTS);
|
||||
}
|
||||
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS) != 0) {
|
||||
const AttributeDefinition<Vec4f> ATTR_WEIGHTS("WEIGHTS_0", &RawVertex::jointWeights,
|
||||
GLT_VEC4F, draco::GeometryAttribute::GENERIC, draco::DT_FLOAT32);
|
||||
gltf->AddAttributeToPrimitive<Vec4f>(buffer, surfaceModel, *primitive, ATTR_WEIGHTS);
|
||||
}
|
||||
}
|
||||
if (options.useDraco) {
|
||||
// Set up the encoder.
|
||||
draco::Encoder encoder;
|
||||
|
||||
// TODO: generalize / allow configuration
|
||||
encoder.SetSpeedOptions(5, 5);
|
||||
encoder.SetAttributeQuantization(draco::GeometryAttribute::POSITION, 14);
|
||||
encoder.SetAttributeQuantization(draco::GeometryAttribute::TEX_COORD, 10);
|
||||
encoder.SetAttributeQuantization(draco::GeometryAttribute::NORMAL, 10);
|
||||
encoder.SetAttributeQuantization(draco::GeometryAttribute::COLOR, 8);
|
||||
encoder.SetAttributeQuantization(draco::GeometryAttribute::GENERIC, 8);
|
||||
encoder.SetEncodingMethod(draco::MeshEncoderMethod::MESH_EDGEBREAKER_ENCODING);
|
||||
|
||||
draco::EncoderBuffer dracoBuffer;
|
||||
draco::Status status = encoder.EncodeMeshToBuffer(*primitive->dracoMesh, &dracoBuffer);
|
||||
assert(status.code() == draco::Status::OK);
|
||||
|
||||
auto view = gltf->AddRawBufferView(buffer, dracoBuffer.data(), dracoBuffer.size());
|
||||
primitive->NoteDracoBuffer(*view);
|
||||
}
|
||||
mesh->AddPrimitive(primitive);
|
||||
}
|
||||
|
||||
//
|
||||
// cameras
|
||||
//
|
||||
|
||||
for (int i = 0; i < raw.GetCameraCount(); i++) {
|
||||
const RawCamera &cam = raw.GetCamera(i);
|
||||
CameraData &camera = *gltf->cameras.hold(new CameraData());
|
||||
camera.name = cam.name;
|
||||
|
||||
if (cam.mode == RawCamera::CAMERA_MODE_PERSPECTIVE) {
|
||||
camera.type = "perspective";
|
||||
camera.aspectRatio = cam.perspective.aspectRatio;
|
||||
camera.yfov = cam.perspective.fovDegreesY * ((float) M_PI / 180.0f);
|
||||
camera.znear = cam.perspective.nearZ;
|
||||
camera.zfar = cam.perspective.farZ;
|
||||
} else {
|
||||
camera.type = "orthographic";
|
||||
camera.xmag = cam.orthographic.magX;
|
||||
camera.ymag = cam.orthographic.magY;
|
||||
camera.znear = cam.orthographic.nearZ;
|
||||
camera.zfar = cam.orthographic.farZ;
|
||||
}
|
||||
// Add the camera to the node hierarchy.
|
||||
|
||||
auto iter = nodesByName.find(cam.nodeName);
|
||||
if (iter == nodesByName.end()) {
|
||||
fmt::printf("Warning: Camera node name %s does not exist.\n", cam.nodeName);
|
||||
continue;
|
||||
}
|
||||
iter->second->AddCamera(cam.name);
|
||||
}
|
||||
}
|
||||
|
||||
NodeData &rootNode = require(nodesByName, "RootNode");
|
||||
const SceneData &rootScene = *gltf->scenes.hold(new SceneData(defaultSceneName, rootNode));
|
||||
|
||||
if (options.outputBinary) {
|
||||
// note: glTF binary is little-endian
|
||||
const char glbHeader[] = {
|
||||
'g', 'l', 'T', 'F', // magic
|
||||
0x02, 0x00, 0x00, 0x00, // version
|
||||
0x00, 0x00, 0x00, 0x00, // total length: written in later
|
||||
};
|
||||
gltfOutStream.write(glbHeader, 12);
|
||||
|
||||
// binary glTF 2.0 has a sub-header for each of the JSON and BIN chunks
|
||||
const char glb2JsonHeader[] = {
|
||||
0x00, 0x00, 0x00, 0x00, // chunk length: written in later
|
||||
'J', 'S', 'O', 'N', // chunk type: 0x4E4F534A aka JSON
|
||||
};
|
||||
gltfOutStream.write(glb2JsonHeader, 8);
|
||||
}
|
||||
|
||||
{
|
||||
std::vector<std::string> extensionsUsed, extensionsRequired;
|
||||
if (options.useKHRMatCom) {
|
||||
extensionsUsed.push_back(KHR_MATERIALS_COMMON);
|
||||
if (!options.usePBRSpecGloss && !options.usePBRMetRough) {
|
||||
extensionsRequired.push_back(KHR_MATERIALS_COMMON);
|
||||
}
|
||||
}
|
||||
if (options.usePBRSpecGloss) {
|
||||
extensionsUsed.push_back(KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS);
|
||||
if (!options.useKHRMatCom && !options.usePBRMetRough) {
|
||||
extensionsRequired.push_back(KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS);
|
||||
}
|
||||
}
|
||||
if (options.useDraco) {
|
||||
extensionsUsed.push_back(KHR_DRACO_MESH_COMPRESSION);
|
||||
extensionsRequired.push_back(KHR_DRACO_MESH_COMPRESSION);
|
||||
}
|
||||
|
||||
json glTFJson {
|
||||
{ "asset", {
|
||||
{ "generator", "FBX2glTF" },
|
||||
{ "version", "2.0" }}},
|
||||
{ "scene", rootScene.ix }
|
||||
};
|
||||
if (!extensionsUsed.empty()) {
|
||||
glTFJson["extensionsUsed"] = extensionsUsed;
|
||||
}
|
||||
if (!extensionsRequired.empty()) {
|
||||
glTFJson["extensionsRequired"] = extensionsRequired;
|
||||
}
|
||||
|
||||
gltf->serializeHolders(glTFJson);
|
||||
gltfOutStream << glTFJson.dump(options.outputBinary ? 0 : 4);
|
||||
}
|
||||
if (options.outputBinary) {
|
||||
uint32_t jsonLength = (uint32_t) gltfOutStream.tellp() - 20;
|
||||
// the binary body must begin on a 4-aligned address, so pad json with spaces if necessary
|
||||
while ((jsonLength % 4) != 0) {
|
||||
gltfOutStream.put(' ');
|
||||
jsonLength++;
|
||||
}
|
||||
|
||||
uint32_t binHeader = (uint32_t) gltfOutStream.tellp();
|
||||
// binary glTF 2.0 has a sub-header for each of the JSON and BIN chunks
|
||||
const char glb2BinaryHeader[] = {
|
||||
0x00, 0x00, 0x00, 0x00, // chunk length: written in later
|
||||
'B', 'I', 'N', 0x00, // chunk type: 0x004E4942 aka BIN
|
||||
};
|
||||
gltfOutStream.write(glb2BinaryHeader, 8);
|
||||
|
||||
// append binary buffer directly to .glb file
|
||||
uint32_t binaryLength = gltf->binary->size();
|
||||
gltfOutStream.write((const char *) &(*gltf->binary)[0], binaryLength);
|
||||
while ((binaryLength % 4) != 0) {
|
||||
gltfOutStream.put('\0');
|
||||
binaryLength++;
|
||||
}
|
||||
uint32_t totalLength = (uint32_t) gltfOutStream.tellp();
|
||||
|
||||
// seek back to sub-header for json chunk
|
||||
gltfOutStream.seekp(8);
|
||||
|
||||
// write total length, little-endian
|
||||
gltfOutStream.put((totalLength >> 0) & 0xFF);
|
||||
gltfOutStream.put((totalLength >> 8) & 0xFF);
|
||||
gltfOutStream.put((totalLength >> 16) & 0xFF);
|
||||
gltfOutStream.put((totalLength >> 24) & 0xFF);
|
||||
|
||||
// write JSON length, little-endian
|
||||
gltfOutStream.put((jsonLength >> 0) & 0xFF);
|
||||
gltfOutStream.put((jsonLength >> 8) & 0xFF);
|
||||
gltfOutStream.put((jsonLength >> 16) & 0xFF);
|
||||
gltfOutStream.put((jsonLength >> 24) & 0xFF);
|
||||
|
||||
// seek back to the gltf 2.0 binary chunk header
|
||||
gltfOutStream.seekp(binHeader);
|
||||
|
||||
// write total length, little-endian
|
||||
gltfOutStream.put((binaryLength >> 0) & 0xFF);
|
||||
gltfOutStream.put((binaryLength >> 8) & 0xFF);
|
||||
gltfOutStream.put((binaryLength >> 16) & 0xFF);
|
||||
gltfOutStream.put((binaryLength >> 24) & 0xFF);
|
||||
|
||||
// be tidy and return write pointer to end-of-file
|
||||
gltfOutStream.seekp(0, std::ios::end);
|
||||
}
|
||||
|
||||
return new ModelData(gltf->binary);
|
||||
}
|
204
src/Raw2Gltf.h
204
src/Raw2Gltf.h
|
@ -1,204 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#ifndef __RAW2GLTF_H__
|
||||
#define __RAW2GLTF_H__
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
// This can be a macro under Windows, confusing Draco
|
||||
#undef ERROR
|
||||
#include <draco/compression/encode.h>
|
||||
|
||||
#include <json.hpp>
|
||||
using json = nlohmann::json;
|
||||
|
||||
#include "FBX2glTF.h"
|
||||
#include "RawModel.h"
|
||||
|
||||
static const std::string KHR_DRACO_MESH_COMPRESSION = "KHR_draco_mesh_compression";
|
||||
static const std::string KHR_MATERIALS_COMMON = "KHR_materials_common";
|
||||
static const std::string KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS = "KHR_materials_pbrSpecularGlossiness";
|
||||
|
||||
static const std::string extBufferFilename = "buffer.bin";
|
||||
|
||||
/**
|
||||
* User-supplied options that dictate the nature of the glTF being generated.
|
||||
*/
|
||||
struct GltfOptions
|
||||
{
|
||||
/**
|
||||
* If negative, disabled. Otherwise, a bitfield of RawVertexAttributes that
|
||||
* specify the largest set of attributes that'll ever be kept for a vertex.
|
||||
* The special bit RAW_VERTEX_ATTRIBUTE_AUTO triggers smart mode, where the
|
||||
* attributes to keep are inferred from which textures are supplied.
|
||||
*/
|
||||
int keepAttribs;
|
||||
/** Whether to output a .glb file, the binary format of glTF. */
|
||||
bool outputBinary;
|
||||
/** If non-binary, whether to inline all resources, for a single (large) .glTF file. */
|
||||
bool embedResources;
|
||||
/** Whether to use KHR_draco_mesh_compression to minimize static geometry size. */
|
||||
bool useDraco;
|
||||
/** Whether to use KHR_materials_common to extend materials definitions. */
|
||||
bool useKHRMatCom;
|
||||
/** Whether to populate the pbrMetallicRoughness substruct in materials. */
|
||||
bool usePBRMetRough;
|
||||
/** Whether to use KHR_materials_pbrSpecularGlossiness to extend material definitions. */
|
||||
bool usePBRSpecGloss;
|
||||
};
|
||||
|
||||
struct ComponentType {
|
||||
// OpenGL Datatype enums
|
||||
enum GL_DataType
|
||||
{
|
||||
GL_BYTE = 5120,
|
||||
GL_UNSIGNED_BYTE,
|
||||
GL_SHORT,
|
||||
GL_UNSIGNED_SHORT,
|
||||
GL_INT,
|
||||
GL_UNSIGNED_INT,
|
||||
GL_FLOAT
|
||||
};
|
||||
|
||||
const GL_DataType glType;
|
||||
const unsigned int size;
|
||||
};
|
||||
|
||||
static const ComponentType CT_USHORT = {ComponentType::GL_UNSIGNED_SHORT, 2};
|
||||
static const ComponentType CT_FLOAT = {ComponentType::GL_FLOAT, 4};
|
||||
|
||||
// Map our low-level data types for glTF output
|
||||
struct GLType {
|
||||
GLType(const ComponentType &componentType, unsigned int count, const std::string dataType)
|
||||
: componentType(componentType),
|
||||
count(count),
|
||||
dataType(dataType)
|
||||
{}
|
||||
|
||||
unsigned int byteStride() const { return componentType.size * count; }
|
||||
|
||||
void write(uint8_t *buf, const float scalar) const { *((float *) buf) = scalar; }
|
||||
void write(uint8_t *buf, const uint16_t scalar) const { *((uint16_t *) buf) = scalar; }
|
||||
|
||||
template<class T, int d>
|
||||
void write(uint8_t *buf, const mathfu::Vector<T, d> &vector) const {
|
||||
for (int ii = 0; ii < d; ii ++) {
|
||||
((T *)buf)[ii] = vector(ii);
|
||||
}
|
||||
}
|
||||
template<class T, int d>
|
||||
void write(uint8_t *buf, const mathfu::Matrix<T, d> &matrix) const {
|
||||
// three matrix types require special alignment considerations that we don't handle
|
||||
// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#data-alignment
|
||||
assert(!(sizeof(T) == 1 && d == 2));
|
||||
assert(!(sizeof(T) == 1 && d == 3));
|
||||
assert(!(sizeof(T) == 2 && d == 2));
|
||||
for (int col = 0; col < d; col ++) {
|
||||
for (int row = 0; row < d; row ++) {
|
||||
// glTF matrices are column-major
|
||||
((T *)buf)[col * d + row] = matrix(row, col);
|
||||
}
|
||||
}
|
||||
}
|
||||
template<class T>
|
||||
void write(uint8_t *buf, const mathfu::Quaternion<T> &quaternion) const {
|
||||
for (int ii = 0; ii < 3; ii++) {
|
||||
((T *)buf)[ii] = quaternion.vector()(ii);
|
||||
}
|
||||
((T *)buf)[3] = quaternion.scalar();
|
||||
}
|
||||
|
||||
const ComponentType componentType;
|
||||
const uint8_t count;
|
||||
const std::string dataType;
|
||||
};
|
||||
|
||||
static const GLType GLT_FLOAT = {CT_FLOAT, 1, "SCALAR"};
|
||||
static const GLType GLT_USHORT = {CT_USHORT, 1, "SCALAR"};
|
||||
static const GLType GLT_VEC2F = {CT_FLOAT, 2, "VEC2"};
|
||||
static const GLType GLT_VEC3F = {CT_FLOAT, 3, "VEC3"};
|
||||
static const GLType GLT_VEC4F = {CT_FLOAT, 4, "VEC4"};
|
||||
static const GLType GLT_VEC4I = {CT_USHORT, 4, "VEC4"};
|
||||
static const GLType GLT_MAT2F = {CT_USHORT, 4, "MAT2"};
|
||||
static const GLType GLT_MAT3F = {CT_USHORT, 9, "MAT3"};
|
||||
static const GLType GLT_MAT4F = {CT_FLOAT, 16, "MAT4"};
|
||||
static const GLType GLT_QUATF = {CT_FLOAT, 4, "VEC4"};
|
||||
|
||||
/**
|
||||
* The base of any indexed glTF entity.
|
||||
*/
|
||||
struct Holdable
|
||||
{
|
||||
uint32_t ix;
|
||||
|
||||
virtual json serialize() const = 0;
|
||||
};
|
||||
|
||||
template<class T>
|
||||
struct AttributeDefinition
|
||||
{
|
||||
const std::string gltfName;
|
||||
const T RawVertex::* rawAttributeIx;
|
||||
const GLType glType;
|
||||
const draco::GeometryAttribute::Type dracoAttribute;
|
||||
const draco::DataType dracoComponentType;
|
||||
|
||||
AttributeDefinition(
|
||||
const std::string gltfName, const T RawVertex::*rawAttributeIx, const GLType &_glType,
|
||||
const draco::GeometryAttribute::Type dracoAttribute, const draco::DataType dracoComponentType)
|
||||
: gltfName(gltfName),
|
||||
rawAttributeIx(rawAttributeIx),
|
||||
glType(_glType),
|
||||
dracoAttribute(dracoAttribute),
|
||||
dracoComponentType(dracoComponentType) {}
|
||||
|
||||
AttributeDefinition(
|
||||
const std::string gltfName, const T RawVertex::*rawAttributeIx, const GLType &_glType)
|
||||
: gltfName(gltfName),
|
||||
rawAttributeIx(rawAttributeIx),
|
||||
glType(_glType),
|
||||
dracoAttribute(draco::GeometryAttribute::INVALID),
|
||||
dracoComponentType(draco::DataType::DT_INVALID) {}
|
||||
};
|
||||
|
||||
struct AccessorData;
|
||||
struct AnimationData;
|
||||
struct BufferData;
|
||||
struct BufferViewData;
|
||||
struct CameraData;
|
||||
struct GLTFData;
|
||||
struct ImageData;
|
||||
struct MaterialData;
|
||||
struct MeshData;
|
||||
struct NodeData;
|
||||
struct PrimitiveData;
|
||||
struct SamplerData;
|
||||
struct SceneData;
|
||||
struct SkinData;
|
||||
struct TextureData;
|
||||
|
||||
struct ModelData
|
||||
{
|
||||
explicit ModelData(std::shared_ptr<const std::vector<uint8_t> > const &_binary)
|
||||
: binary(_binary)
|
||||
{
|
||||
}
|
||||
|
||||
std::shared_ptr<const std::vector<uint8_t> > const binary;
|
||||
};
|
||||
|
||||
ModelData *Raw2Gltf(
|
||||
std::ofstream &gltfOutStream,
|
||||
const RawModel &raw,
|
||||
const GltfOptions &options
|
||||
);
|
||||
|
||||
#endif // !__RAW2GLTF_H__
|
542
src/RawModel.cpp
542
src/RawModel.cpp
|
@ -1,542 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <map>
|
||||
|
||||
#if defined( __unix__ )
|
||||
#include <algorithm>
|
||||
#endif
|
||||
|
||||
#include "FBX2glTF.h"
|
||||
#include "utils/String_Utils.h"
|
||||
#include "utils/Image_Utils.h"
|
||||
#include "RawModel.h"
|
||||
|
||||
static float Log2f(float f)
|
||||
{
|
||||
return logf(f) * 1.442695041f;
|
||||
}
|
||||
|
||||
bool RawVertex::operator==(const RawVertex &other) const
|
||||
{
|
||||
return (position == other.position) &&
|
||||
(normal == other.normal) &&
|
||||
(tangent == other.tangent) &&
|
||||
(binormal == other.binormal) &&
|
||||
(color == other.color) &&
|
||||
(uv0 == other.uv0) &&
|
||||
(uv1 == other.uv1) &&
|
||||
(jointIndices == other.jointIndices) &&
|
||||
(jointWeights == other.jointWeights) &&
|
||||
(polarityUv0 == other.polarityUv0);
|
||||
}
|
||||
|
||||
size_t RawVertex::Difference(const RawVertex &other) const
|
||||
{
|
||||
size_t attributes = 0;
|
||||
if (position != other.position) { attributes |= RAW_VERTEX_ATTRIBUTE_POSITION; }
|
||||
if (normal != other.normal) { attributes |= RAW_VERTEX_ATTRIBUTE_NORMAL; }
|
||||
if (tangent != other.tangent) { attributes |= RAW_VERTEX_ATTRIBUTE_TANGENT; }
|
||||
if (binormal != other.binormal) { attributes |= RAW_VERTEX_ATTRIBUTE_BINORMAL; }
|
||||
if (color != other.color) { attributes |= RAW_VERTEX_ATTRIBUTE_COLOR; }
|
||||
if (uv0 != other.uv0) { attributes |= RAW_VERTEX_ATTRIBUTE_UV0; }
|
||||
if (uv1 != other.uv1) { attributes |= RAW_VERTEX_ATTRIBUTE_UV1; }
|
||||
// Always need both or neither.
|
||||
if (jointIndices != other.jointIndices) { attributes |= RAW_VERTEX_ATTRIBUTE_JOINT_INDICES | RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS; }
|
||||
if (jointWeights != other.jointWeights) { attributes |= RAW_VERTEX_ATTRIBUTE_JOINT_INDICES | RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS; }
|
||||
return attributes;
|
||||
}
|
||||
|
||||
RawModel::RawModel()
|
||||
: vertexAttributes(0)
|
||||
{
|
||||
}
|
||||
|
||||
void RawModel::AddVertexAttribute(const RawVertexAttribute attrib)
|
||||
{
|
||||
vertexAttributes |= attrib;
|
||||
}
|
||||
|
||||
int RawModel::AddVertex(const RawVertex &vertex)
|
||||
{
|
||||
auto it = vertexHash.find(vertex);
|
||||
if (it != vertexHash.end()) {
|
||||
return it->second;
|
||||
}
|
||||
vertexHash.emplace(vertex, (int) vertices.size());
|
||||
vertices.push_back(vertex);
|
||||
return (int) vertices.size() - 1;
|
||||
}
|
||||
|
||||
int RawModel::AddTriangle(const int v0, const int v1, const int v2, const int materialIndex, const int surfaceIndex)
|
||||
{
|
||||
const RawTriangle triangle = {{v0, v1, v2}, materialIndex, surfaceIndex};
|
||||
triangles.push_back(triangle);
|
||||
return (int) triangles.size() - 1;
|
||||
}
|
||||
|
||||
int RawModel::AddTexture(const char *name, const char *fileName, const RawTextureUsage usage)
|
||||
{
|
||||
if (name[0] == '\0') {
|
||||
return -1;
|
||||
}
|
||||
for (size_t i = 0; i < textures.size(); i++) {
|
||||
if (Gltf::StringUtils::CompareNoCase(textures[i].name, name) == 0 && textures[i].usage == usage) {
|
||||
return (int) i;
|
||||
}
|
||||
}
|
||||
|
||||
const ImageProperties properties = GetImageProperties(fileName);
|
||||
|
||||
RawTexture texture;
|
||||
texture.name = name;
|
||||
texture.width = properties.width;
|
||||
texture.height = properties.height;
|
||||
texture.mipLevels = (int) ceilf(Log2f(std::max((float) properties.width, (float) properties.height)));
|
||||
texture.usage = usage;
|
||||
texture.occlusion = (properties.occlusion == IMAGE_TRANSPARENT) ?
|
||||
RAW_TEXTURE_OCCLUSION_TRANSPARENT : RAW_TEXTURE_OCCLUSION_OPAQUE;
|
||||
texture.fileName = fileName;
|
||||
textures.emplace_back(texture);
|
||||
return (int) textures.size() - 1;
|
||||
}
|
||||
|
||||
int RawModel::AddMaterial(const RawMaterial &material)
|
||||
{
|
||||
return AddMaterial(
|
||||
material.name.c_str(), material.shadingModel.c_str(), material.type, material.textures, material.ambientFactor,
|
||||
material.diffuseFactor, material.specularFactor, material.emissiveFactor, material.shininess);
|
||||
}
|
||||
|
||||
int RawModel::AddMaterial(
|
||||
const char *name, const char *shadingModel, const RawMaterialType materialType,
|
||||
const int textures[RAW_TEXTURE_USAGE_MAX], const Vec3f ambientFactor,
|
||||
const Vec4f diffuseFactor, const Vec3f specularFactor,
|
||||
const Vec3f emissiveFactor, float shinineness)
|
||||
{
|
||||
for (size_t i = 0; i < materials.size(); i++) {
|
||||
if (materials[i].name != name) {
|
||||
continue;
|
||||
}
|
||||
if (materials[i].shadingModel != shadingModel) {
|
||||
continue;
|
||||
}
|
||||
if (materials[i].type != materialType) {
|
||||
continue;
|
||||
}
|
||||
if (materials[i].ambientFactor != ambientFactor ||
|
||||
materials[i].diffuseFactor != diffuseFactor ||
|
||||
materials[i].specularFactor != specularFactor ||
|
||||
materials[i].emissiveFactor != emissiveFactor ||
|
||||
materials[i].shininess != shinineness) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool match = true;
|
||||
for (int j = 0; match && j < RAW_TEXTURE_USAGE_MAX; j++) {
|
||||
match = match && (materials[i].textures[j] == textures[j]);
|
||||
}
|
||||
if (match) {
|
||||
return (int) i;
|
||||
}
|
||||
}
|
||||
|
||||
RawMaterial material;
|
||||
material.name = name;
|
||||
material.shadingModel = shadingModel;
|
||||
material.type = materialType;
|
||||
material.ambientFactor = ambientFactor;
|
||||
material.diffuseFactor = diffuseFactor;
|
||||
material.specularFactor = specularFactor;
|
||||
material.emissiveFactor = emissiveFactor;
|
||||
material.shininess = shinineness;
|
||||
|
||||
for (int i = 0; i < RAW_TEXTURE_USAGE_MAX; i++) {
|
||||
material.textures[i] = textures[i];
|
||||
}
|
||||
|
||||
materials.emplace_back(material);
|
||||
|
||||
return (int) materials.size() - 1;
|
||||
}
|
||||
|
||||
int RawModel::AddSurface(const RawSurface &surface)
|
||||
{
|
||||
for (size_t i = 0; i < surfaces.size(); i++) {
|
||||
if (Gltf::StringUtils::CompareNoCase(surfaces[i].name, surface.name) == 0) {
|
||||
return (int) i;
|
||||
}
|
||||
}
|
||||
|
||||
surfaces.emplace_back(surface);
|
||||
return (int) (surfaces.size() - 1);
|
||||
}
|
||||
|
||||
int RawModel::AddSurface(const char *name, const char *nodeName)
|
||||
{
|
||||
assert(name[0] != '\0');
|
||||
|
||||
for (size_t i = 0; i < surfaces.size(); i++) {
|
||||
if (Gltf::StringUtils::CompareNoCase(surfaces[i].name, name) == 0) {
|
||||
return (int) i;
|
||||
}
|
||||
}
|
||||
RawSurface surface;
|
||||
surface.name = name;
|
||||
surface.nodeName = nodeName;
|
||||
surface.bounds.Clear();
|
||||
surface.discrete = false;
|
||||
surface.skinRigid = false;
|
||||
|
||||
surfaces.emplace_back(surface);
|
||||
return (int) (surfaces.size() - 1);
|
||||
}
|
||||
|
||||
int RawModel::AddAnimation(const RawAnimation &animation)
|
||||
{
|
||||
animations.emplace_back(animation);
|
||||
return (int) (animations.size() - 1);
|
||||
}
|
||||
|
||||
int RawModel::AddNode(const RawNode &node)
|
||||
{
|
||||
for (size_t i = 0; i < nodes.size(); i++) {
|
||||
if (Gltf::StringUtils::CompareNoCase(nodes[i].name.c_str(), node.name) == 0) {
|
||||
return (int) i;
|
||||
}
|
||||
}
|
||||
|
||||
nodes.emplace_back(node);
|
||||
return (int) nodes.size() - 1;
|
||||
}
|
||||
|
||||
int RawModel::AddCameraPerspective(
|
||||
const char *name, const char *nodeName, const float aspectRatio, const float fovDegreesX, const float fovDegreesY, const float nearZ,
|
||||
const float farZ)
|
||||
{
|
||||
RawCamera camera;
|
||||
camera.name = name;
|
||||
camera.nodeName = nodeName;
|
||||
camera.mode = RawCamera::CAMERA_MODE_PERSPECTIVE;
|
||||
camera.perspective.aspectRatio = aspectRatio;
|
||||
camera.perspective.fovDegreesX = fovDegreesX;
|
||||
camera.perspective.fovDegreesY = fovDegreesY;
|
||||
camera.perspective.nearZ = nearZ;
|
||||
camera.perspective.farZ = farZ;
|
||||
cameras.emplace_back(camera);
|
||||
return (int) cameras.size() - 1;
|
||||
}
|
||||
|
||||
int RawModel::AddCameraOrthographic(
|
||||
const char *name, const char *nodeName, const float magX, const float magY, const float nearZ, const float farZ)
|
||||
{
|
||||
RawCamera camera;
|
||||
camera.name = name;
|
||||
camera.nodeName = nodeName;
|
||||
camera.mode = RawCamera::CAMERA_MODE_ORTHOGRAPHIC;
|
||||
camera.orthographic.magX = magX;
|
||||
camera.orthographic.magY = magY;
|
||||
camera.orthographic.nearZ = nearZ;
|
||||
camera.orthographic.farZ = farZ;
|
||||
cameras.emplace_back(camera);
|
||||
return (int) cameras.size() - 1;
|
||||
}
|
||||
|
||||
int RawModel::AddNode(const char *name, const char *parentName)
|
||||
{
|
||||
assert(name[0] != '\0');
|
||||
|
||||
for (size_t i = 0; i < nodes.size(); i++) {
|
||||
if (Gltf::StringUtils::CompareNoCase(nodes[i].name, name) == 0) {
|
||||
return (int) i;
|
||||
}
|
||||
}
|
||||
|
||||
RawNode joint;
|
||||
joint.isJoint = false;
|
||||
joint.name = name;
|
||||
joint.parentName = parentName;
|
||||
joint.translation = Vec3f(0, 0, 0);
|
||||
joint.rotation = Quatf(0, 0, 0, 1);
|
||||
joint.scale = Vec3f(1, 1, 1);
|
||||
|
||||
nodes.emplace_back(joint);
|
||||
return (int) nodes.size() - 1;
|
||||
}
|
||||
|
||||
void RawModel::Condense()
|
||||
{
|
||||
// Only keep surfaces that are referenced by one or more triangles.
|
||||
{
|
||||
std::vector<RawSurface> oldSurfaces = surfaces;
|
||||
|
||||
surfaces.clear();
|
||||
|
||||
for (auto &triangle : triangles) {
|
||||
const RawSurface &surface = oldSurfaces[triangle.surfaceIndex];
|
||||
const int surfaceIndex = AddSurface(surface.name.c_str(), surface.nodeName.c_str());
|
||||
surfaces[surfaceIndex] = surface;
|
||||
triangle.surfaceIndex = surfaceIndex;
|
||||
}
|
||||
}
|
||||
|
||||
// Only keep materials that are referenced by one or more triangles.
|
||||
{
|
||||
std::vector<RawMaterial> oldMaterials = materials;
|
||||
|
||||
materials.clear();
|
||||
|
||||
for (auto &triangle : triangles) {
|
||||
const RawMaterial &material = oldMaterials[triangle.materialIndex];
|
||||
const int materialIndex = AddMaterial(material);
|
||||
materials[materialIndex] = material;
|
||||
triangle.materialIndex = materialIndex;
|
||||
}
|
||||
}
|
||||
|
||||
// Only keep textures that are referenced by one or more materials.
|
||||
{
|
||||
std::vector<RawTexture> oldTextures = textures;
|
||||
|
||||
textures.clear();
|
||||
|
||||
for (auto &material : materials) {
|
||||
for (int j = 0; j < RAW_TEXTURE_USAGE_MAX; j++) {
|
||||
if (material.textures[j] >= 0) {
|
||||
const RawTexture &texture = oldTextures[material.textures[j]];
|
||||
const int textureIndex = AddTexture(texture.name.c_str(), texture.fileName.c_str(), texture.usage);
|
||||
textures[textureIndex] = texture;
|
||||
material.textures[j] = textureIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Only keep vertices that are referenced by one or more triangles.
|
||||
{
|
||||
std::vector<RawVertex> oldVertices = vertices;
|
||||
|
||||
vertexHash.clear();
|
||||
vertices.clear();
|
||||
|
||||
for (auto &triangle : triangles) {
|
||||
for (int j = 0; j < 3; j++) {
|
||||
triangle.verts[j] = AddVertex(oldVertices[triangle.verts[j]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RawModel::TransformTextures(const std::vector<std::function<Vec2f(Vec2f)>> &transforms)
|
||||
{
|
||||
for (auto &vertice : vertices) {
|
||||
if ((vertexAttributes & RAW_VERTEX_ATTRIBUTE_UV0) != 0) {
|
||||
for (const auto &fun : transforms) {
|
||||
vertice.uv0 = fun(vertice.uv0);
|
||||
}
|
||||
}
|
||||
if ((vertexAttributes & RAW_VERTEX_ATTRIBUTE_UV1) != 0) {
|
||||
for (const auto &fun : transforms) {
|
||||
vertice.uv1 = fun(vertice.uv1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TriangleModelSortPos
|
||||
{
|
||||
static bool Compare(const RawTriangle &a, const RawTriangle &b)
|
||||
{
|
||||
if (a.materialIndex != b.materialIndex) {
|
||||
return a.materialIndex < b.materialIndex;
|
||||
}
|
||||
if (a.surfaceIndex != b.surfaceIndex) {
|
||||
return a.surfaceIndex < b.surfaceIndex;
|
||||
}
|
||||
return a.verts[0] < b.verts[0];
|
||||
}
|
||||
};
|
||||
|
||||
struct TriangleModelSortNeg
|
||||
{
|
||||
static bool Compare(const RawTriangle &a, const RawTriangle &b)
|
||||
{
|
||||
if (a.materialIndex != b.materialIndex) {
|
||||
return a.materialIndex < b.materialIndex;
|
||||
}
|
||||
if (a.surfaceIndex != b.surfaceIndex) {
|
||||
return a.surfaceIndex < b.surfaceIndex;
|
||||
}
|
||||
return a.verts[0] > b.verts[0];
|
||||
}
|
||||
};
|
||||
|
||||
void RawModel::CreateMaterialModels(
|
||||
std::vector<RawModel> &materialModels, const int maxModelVertices, const int keepAttribs, const bool forceDiscrete) const
|
||||
{
|
||||
// Sort all triangles based on material first, then surface, then first vertex index.
|
||||
std::vector<RawTriangle> sortedTriangles;
|
||||
|
||||
bool invertedTransparencySort = true;
|
||||
if (invertedTransparencySort) {
|
||||
// Split the triangles into opaque and transparent triangles.
|
||||
std::vector<RawTriangle> opaqueTriangles;
|
||||
std::vector<RawTriangle> transparentTriangles;
|
||||
for (const auto &triangle : triangles) {
|
||||
const int materialIndex = triangle.materialIndex;
|
||||
if (materialIndex < 0) {
|
||||
opaqueTriangles.push_back(triangle);
|
||||
continue;
|
||||
}
|
||||
const int textureIndex = materials[materialIndex].textures[RAW_TEXTURE_USAGE_DIFFUSE];
|
||||
if (textureIndex < 0) {
|
||||
if (vertices[triangle.verts[0]].color.w < 1.0f ||
|
||||
vertices[triangle.verts[1]].color.w < 1.0f ||
|
||||
vertices[triangle.verts[2]].color.w < 1.0f) {
|
||||
transparentTriangles.push_back(triangle);
|
||||
continue;
|
||||
}
|
||||
opaqueTriangles.push_back(triangle);
|
||||
continue;
|
||||
}
|
||||
if (textures[textureIndex].occlusion == RAW_TEXTURE_OCCLUSION_TRANSPARENT) {
|
||||
transparentTriangles.push_back(triangle);
|
||||
} else {
|
||||
opaqueTriangles.push_back(triangle);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the opaque triangles.
|
||||
std::sort(opaqueTriangles.begin(), opaqueTriangles.end(), TriangleModelSortPos::Compare);
|
||||
|
||||
// Sort the transparent triangles in the reverse direction.
|
||||
std::sort(transparentTriangles.begin(), transparentTriangles.end(), TriangleModelSortNeg::Compare);
|
||||
|
||||
// Add the triangles to the sorted list.
|
||||
for (const auto &opaqueTriangle : opaqueTriangles) {
|
||||
sortedTriangles.push_back(opaqueTriangle);
|
||||
}
|
||||
for (const auto &transparentTriangle : transparentTriangles) {
|
||||
sortedTriangles.push_back(transparentTriangle);
|
||||
}
|
||||
} else {
|
||||
sortedTriangles = triangles;
|
||||
std::sort(sortedTriangles.begin(), sortedTriangles.end(), TriangleModelSortPos::Compare);
|
||||
}
|
||||
|
||||
// Overestimate the number of models that will be created to avoid massive reallocation.
|
||||
int discreteCount = 0;
|
||||
for (const auto &surface : surfaces) {
|
||||
discreteCount += (surface.discrete != false);
|
||||
}
|
||||
|
||||
materialModels.clear();
|
||||
materialModels.reserve(materials.size() + discreteCount);
|
||||
|
||||
const RawVertex defaultVertex;
|
||||
|
||||
// Create a separate model for each material.
|
||||
RawModel *model;
|
||||
for (size_t i = 0; i < sortedTriangles.size(); i++) {
|
||||
|
||||
if (sortedTriangles[i].materialIndex < 0 || sortedTriangles[i].surfaceIndex < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i == 0 ||
|
||||
model->GetVertexCount() > maxModelVertices - 3 ||
|
||||
sortedTriangles[i].materialIndex != sortedTriangles[i - 1].materialIndex ||
|
||||
(sortedTriangles[i].surfaceIndex != sortedTriangles[i - 1].surfaceIndex &&
|
||||
(forceDiscrete || surfaces[sortedTriangles[i].surfaceIndex].discrete ||
|
||||
surfaces[sortedTriangles[i - 1].surfaceIndex].discrete))) {
|
||||
materialModels.resize(materialModels.size() + 1);
|
||||
model = &materialModels[materialModels.size() - 1];
|
||||
}
|
||||
|
||||
// FIXME: will have to unlink from the nodes, transform both surfaces into a
|
||||
// common space, and reparent to a new node with appropriate transform.
|
||||
|
||||
const int prevSurfaceCount = model->GetSurfaceCount();
|
||||
const int materialIndex = model->AddMaterial(materials[sortedTriangles[i].materialIndex]);
|
||||
const int surfaceIndex = model->AddSurface(surfaces[sortedTriangles[i].surfaceIndex]);
|
||||
RawSurface &rawSurface = model->GetSurface(surfaceIndex);
|
||||
|
||||
if (model->GetSurfaceCount() > prevSurfaceCount) {
|
||||
const std::vector<std::string> &jointNames = surfaces[sortedTriangles[i].surfaceIndex].jointNames;
|
||||
for (const auto &jointName : jointNames) {
|
||||
const int nodeIndex = GetNodeByName(jointName.c_str());
|
||||
assert(nodeIndex != -1);
|
||||
model->AddNode(GetNode(nodeIndex));
|
||||
}
|
||||
rawSurface.bounds.Clear();
|
||||
}
|
||||
|
||||
int verts[3];
|
||||
for (int j = 0; j < 3; j++) {
|
||||
RawVertex vertex = vertices[sortedTriangles[i].verts[j]];
|
||||
|
||||
if (keepAttribs != -1) {
|
||||
int keep = keepAttribs;
|
||||
if ((keepAttribs & RAW_VERTEX_ATTRIBUTE_POSITION) != 0) {
|
||||
keep |= RAW_VERTEX_ATTRIBUTE_JOINT_INDICES | RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS;
|
||||
}
|
||||
if ((keepAttribs & RAW_VERTEX_ATTRIBUTE_AUTO) != 0) {
|
||||
keep |= RAW_VERTEX_ATTRIBUTE_POSITION;
|
||||
|
||||
const RawMaterial &mat = model->GetMaterial(materialIndex);
|
||||
if (mat.textures[RAW_TEXTURE_USAGE_DIFFUSE] != -1) {
|
||||
keep |= RAW_VERTEX_ATTRIBUTE_UV0;
|
||||
}
|
||||
if (mat.textures[RAW_TEXTURE_USAGE_NORMAL] != -1) {
|
||||
keep |= RAW_VERTEX_ATTRIBUTE_NORMAL |
|
||||
RAW_VERTEX_ATTRIBUTE_TANGENT |
|
||||
RAW_VERTEX_ATTRIBUTE_BINORMAL |
|
||||
RAW_VERTEX_ATTRIBUTE_UV0;
|
||||
}
|
||||
if (mat.textures[RAW_TEXTURE_USAGE_SPECULAR] != -1) {
|
||||
keep |= RAW_VERTEX_ATTRIBUTE_NORMAL |
|
||||
RAW_VERTEX_ATTRIBUTE_UV0;
|
||||
}
|
||||
if (mat.textures[RAW_TEXTURE_USAGE_EMISSIVE] != -1) {
|
||||
keep |= RAW_VERTEX_ATTRIBUTE_UV1;
|
||||
}
|
||||
}
|
||||
if ((keep & RAW_VERTEX_ATTRIBUTE_POSITION) == 0) { vertex.position = defaultVertex.position; }
|
||||
if ((keep & RAW_VERTEX_ATTRIBUTE_NORMAL) == 0) { vertex.normal = defaultVertex.normal; }
|
||||
if ((keep & RAW_VERTEX_ATTRIBUTE_TANGENT) == 0) { vertex.tangent = defaultVertex.tangent; }
|
||||
if ((keep & RAW_VERTEX_ATTRIBUTE_BINORMAL) == 0) { vertex.binormal = defaultVertex.binormal; }
|
||||
if ((keep & RAW_VERTEX_ATTRIBUTE_COLOR) == 0) { vertex.color = defaultVertex.color; }
|
||||
if ((keep & RAW_VERTEX_ATTRIBUTE_UV0) == 0) { vertex.uv0 = defaultVertex.uv0; }
|
||||
if ((keep & RAW_VERTEX_ATTRIBUTE_UV1) == 0) { vertex.uv1 = defaultVertex.uv1; }
|
||||
if ((keep & RAW_VERTEX_ATTRIBUTE_JOINT_INDICES) == 0) { vertex.jointIndices = defaultVertex.jointIndices; }
|
||||
if ((keep & RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS) == 0) { vertex.jointWeights = defaultVertex.jointWeights; }
|
||||
}
|
||||
|
||||
verts[j] = model->AddVertex(vertex);
|
||||
model->vertexAttributes |= vertex.Difference(defaultVertex);
|
||||
|
||||
rawSurface.bounds.AddPoint(vertex.position);
|
||||
}
|
||||
|
||||
model->AddTriangle(verts[0], verts[1], verts[2], materialIndex, surfaceIndex);
|
||||
}
|
||||
}
|
||||
|
||||
int RawModel::GetNodeByName(const char *name) const
|
||||
{
|
||||
for (size_t i = 0; i < nodes.size(); i++) {
|
||||
if (nodes[i].name == name) {
|
||||
return (int) i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
333
src/RawModel.h
333
src/RawModel.h
|
@ -1,333 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#ifndef __RAWMODEL_H__
|
||||
#define __RAWMODEL_H__
|
||||
|
||||
#include <unordered_map>
|
||||
#include <functional>
|
||||
|
||||
enum RawVertexAttribute
|
||||
{
|
||||
RAW_VERTEX_ATTRIBUTE_POSITION = 1 << 0,
|
||||
RAW_VERTEX_ATTRIBUTE_NORMAL = 1 << 1,
|
||||
RAW_VERTEX_ATTRIBUTE_TANGENT = 1 << 2,
|
||||
RAW_VERTEX_ATTRIBUTE_BINORMAL = 1 << 3,
|
||||
RAW_VERTEX_ATTRIBUTE_COLOR = 1 << 4,
|
||||
RAW_VERTEX_ATTRIBUTE_UV0 = 1 << 5,
|
||||
RAW_VERTEX_ATTRIBUTE_UV1 = 1 << 6,
|
||||
RAW_VERTEX_ATTRIBUTE_JOINT_INDICES = 1 << 7,
|
||||
RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS = 1 << 8,
|
||||
|
||||
RAW_VERTEX_ATTRIBUTE_AUTO = 1 << 31
|
||||
};
|
||||
|
||||
struct RawVertex
|
||||
{
|
||||
RawVertex() :
|
||||
polarityUv0(false),
|
||||
pad1(false),
|
||||
pad2(false),
|
||||
pad3(false) {}
|
||||
|
||||
Vec3f position { 0.0f };
|
||||
Vec3f normal { 0.0f };
|
||||
Vec3f binormal { 0.0f };
|
||||
Vec4f tangent { 0.0f };
|
||||
Vec4f color { 0.0f };
|
||||
Vec2f uv0 { 0.0f };
|
||||
Vec2f uv1 { 0.0f };
|
||||
Vec4i jointIndices { 0, 0, 0, 0 };
|
||||
Vec4f jointWeights { 0.0f };
|
||||
|
||||
bool polarityUv0;
|
||||
bool pad1;
|
||||
bool pad2;
|
||||
bool pad3;
|
||||
|
||||
bool operator==(const RawVertex &other) const;
|
||||
size_t Difference(const RawVertex &other) const;
|
||||
};
|
||||
|
||||
class VertexHasher
|
||||
{
|
||||
public:
|
||||
size_t operator()(const RawVertex &v) const
|
||||
{
|
||||
size_t seed = 5381;
|
||||
const auto hasher = std::hash<float>{};
|
||||
seed ^= hasher(v.position[0]) + 0x9e3779b9 + (seed<<6) + (seed>>2);
|
||||
seed ^= hasher(v.position[1]) + 0x9e3779b9 + (seed<<6) + (seed>>2);
|
||||
seed ^= hasher(v.position[2]) + 0x9e3779b9 + (seed<<6) + (seed>>2);
|
||||
return seed;
|
||||
}
|
||||
};
|
||||
|
||||
struct RawTriangle
|
||||
{
|
||||
int verts[3];
|
||||
int materialIndex;
|
||||
int surfaceIndex;
|
||||
};
|
||||
|
||||
enum RawTextureUsage
|
||||
{
|
||||
RAW_TEXTURE_USAGE_AMBIENT,
|
||||
RAW_TEXTURE_USAGE_DIFFUSE,
|
||||
RAW_TEXTURE_USAGE_NORMAL,
|
||||
RAW_TEXTURE_USAGE_SPECULAR,
|
||||
RAW_TEXTURE_USAGE_SHININESS,
|
||||
RAW_TEXTURE_USAGE_EMISSIVE,
|
||||
RAW_TEXTURE_USAGE_REFLECTION,
|
||||
RAW_TEXTURE_USAGE_MAX
|
||||
};
|
||||
|
||||
inline std::string DescribeTextureUsage(int usage)
|
||||
{
|
||||
if (usage < 0) {
|
||||
return "<none>";
|
||||
}
|
||||
switch (static_cast<RawTextureUsage>(usage)) {
|
||||
case RAW_TEXTURE_USAGE_AMBIENT:
|
||||
return "ambient";
|
||||
case RAW_TEXTURE_USAGE_DIFFUSE:
|
||||
return "diffuse";
|
||||
case RAW_TEXTURE_USAGE_NORMAL:
|
||||
return "normal";
|
||||
case RAW_TEXTURE_USAGE_SPECULAR:
|
||||
return "specuar";
|
||||
case RAW_TEXTURE_USAGE_SHININESS:
|
||||
return "shininess";
|
||||
case RAW_TEXTURE_USAGE_EMISSIVE:
|
||||
return "emissive";
|
||||
case RAW_TEXTURE_USAGE_REFLECTION:
|
||||
return "reflection";
|
||||
case RAW_TEXTURE_USAGE_MAX:
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
};
|
||||
|
||||
enum RawTextureOcclusion
|
||||
{
|
||||
RAW_TEXTURE_OCCLUSION_OPAQUE,
|
||||
RAW_TEXTURE_OCCLUSION_TRANSPARENT
|
||||
};
|
||||
|
||||
struct RawTexture
|
||||
{
|
||||
std::string name;
|
||||
int width;
|
||||
int height;
|
||||
int mipLevels;
|
||||
RawTextureUsage usage;
|
||||
RawTextureOcclusion occlusion;
|
||||
std::string fileName;
|
||||
};
|
||||
|
||||
enum RawMaterialType
|
||||
{
|
||||
RAW_MATERIAL_TYPE_OPAQUE,
|
||||
RAW_MATERIAL_TYPE_TRANSPARENT,
|
||||
RAW_MATERIAL_TYPE_VERTEX_COLORED,
|
||||
RAW_MATERIAL_TYPE_SKINNED_OPAQUE,
|
||||
RAW_MATERIAL_TYPE_SKINNED_TRANSPARENT,
|
||||
RAW_MATERIAL_TYPE_SKINNED_VERTEX_COLORED
|
||||
};
|
||||
|
||||
struct RawMaterial
|
||||
{
|
||||
|
||||
std::string name;
|
||||
std::string shadingModel; // typically "Surface", "Anisotropic", "Blinn", "Lambert", "Phong", "Phone E"
|
||||
RawMaterialType type;
|
||||
Vec3f ambientFactor;
|
||||
Vec4f diffuseFactor;
|
||||
Vec3f specularFactor;
|
||||
Vec3f emissiveFactor;
|
||||
float shininess;
|
||||
int textures[RAW_TEXTURE_USAGE_MAX];
|
||||
};
|
||||
|
||||
struct RawSurface
|
||||
{
|
||||
std::string name; // The name of this surface
|
||||
std::string nodeName; // The node that links to this surface.
|
||||
std::string skeletonRootName; // The name of the root of the skeleton.
|
||||
Bounds<float, 3> bounds;
|
||||
std::vector<std::string> jointNames;
|
||||
std::vector<Vec3f> jointGeometryMins;
|
||||
std::vector<Vec3f> jointGeometryMaxs;
|
||||
std::vector<Mat4f> inverseBindMatrices;
|
||||
bool discrete;
|
||||
bool skinRigid;
|
||||
};
|
||||
|
||||
struct RawChannel
|
||||
{
|
||||
int nodeIndex;
|
||||
std::vector<Vec3f> translations;
|
||||
std::vector<Quatf> rotations;
|
||||
std::vector<Vec3f> scales;
|
||||
};
|
||||
|
||||
struct RawAnimation
|
||||
{
|
||||
std::string name;
|
||||
std::vector<float> times;
|
||||
std::vector<RawChannel> channels;
|
||||
};
|
||||
|
||||
struct RawCamera
|
||||
{
|
||||
std::string name;
|
||||
std::string nodeName;
|
||||
|
||||
enum
|
||||
{
|
||||
CAMERA_MODE_PERSPECTIVE,
|
||||
CAMERA_MODE_ORTHOGRAPHIC
|
||||
} mode;
|
||||
|
||||
struct
|
||||
{
|
||||
float aspectRatio;
|
||||
float fovDegreesX;
|
||||
float fovDegreesY;
|
||||
float nearZ;
|
||||
float farZ;
|
||||
} perspective;
|
||||
|
||||
struct
|
||||
{
|
||||
float magX;
|
||||
float magY;
|
||||
float nearZ;
|
||||
float farZ;
|
||||
} orthographic;
|
||||
};
|
||||
|
||||
struct RawNode
|
||||
{
|
||||
bool isJoint;
|
||||
std::string name;
|
||||
std::string parentName;
|
||||
std::vector<std::string> childNames;
|
||||
Vec3f translation;
|
||||
Quatf rotation;
|
||||
Vec3f scale;
|
||||
};
|
||||
|
||||
class RawModel
|
||||
{
|
||||
public:
|
||||
RawModel();
|
||||
|
||||
// Add geometry.
|
||||
void AddVertexAttribute(const RawVertexAttribute attrib);
|
||||
int AddVertex(const RawVertex &vertex);
|
||||
int AddTriangle(const int v0, const int v1, const int v2, const int materialIndex, const int surfaceIndex);
|
||||
int AddTexture(const char *name, const char *fileName, const RawTextureUsage usage);
|
||||
int AddMaterial(const RawMaterial &material);
|
||||
int AddMaterial(
|
||||
const char *name, const char *shadingModel, RawMaterialType materialType,
|
||||
const int textures[RAW_TEXTURE_USAGE_MAX], Vec3f ambientFactor,
|
||||
Vec4f diffuseFactor, Vec3f specularFactor,
|
||||
Vec3f emissiveFactor, float shinineness);
|
||||
int AddSurface(const RawSurface &suface);
|
||||
int AddSurface(const char *name, const char *nodeName);
|
||||
int AddAnimation(const RawAnimation &animation);
|
||||
int AddCameraPerspective(
|
||||
const char *name, const char *nodeName, const float aspectRatio, const float fovDegreesX, const float fovDegreesY,
|
||||
const float nearZ, const float farZ);
|
||||
int
|
||||
AddCameraOrthographic(const char *name, const char *nodeName, const float magX, const float magY, const float nearZ, const float farZ);
|
||||
int AddNode(const RawNode &node);
|
||||
int AddNode(const char *name, const char *parentName);
|
||||
void SetRootNode(const char *name) { rootNodeName = name; }
|
||||
const char *GetRootNode() const { return rootNodeName.c_str(); }
|
||||
|
||||
// Remove unused vertices, textures or materials after removing vertex attributes, textures, materials or surfaces.
|
||||
void Condense();
|
||||
|
||||
void TransformTextures(const std::vector<std::function<Vec2f(Vec2f)>> &transforms);
|
||||
|
||||
// Get the attributes stored per vertex.
|
||||
int GetVertexAttributes() const { return vertexAttributes; }
|
||||
|
||||
// Iterate over the vertices.
|
||||
int GetVertexCount() const { return (int) vertices.size(); }
|
||||
const RawVertex &GetVertex(const int index) const { return vertices[index]; }
|
||||
|
||||
// Iterate over the triangles.
|
||||
int GetTriangleCount() const { return (int) triangles.size(); }
|
||||
const RawTriangle &GetTriangle(const int index) const { return triangles[index]; }
|
||||
|
||||
// Iterate over the textures.
|
||||
int GetTextureCount() const { return (int) textures.size(); }
|
||||
const RawTexture &GetTexture(const int index) const { return textures[index]; }
|
||||
|
||||
// Iterate over the materials.
|
||||
int GetMaterialCount() const { return (int) materials.size(); }
|
||||
const RawMaterial &GetMaterial(const int index) const { return materials[index]; }
|
||||
|
||||
// Iterate over the surfaces.
|
||||
int GetSurfaceCount() const { return (int) surfaces.size(); }
|
||||
const RawSurface &GetSurface(const int index) const { return surfaces[index]; }
|
||||
RawSurface &GetSurface(const int index) { return surfaces[index]; }
|
||||
|
||||
// Iterate over the animations.
|
||||
int GetAnimationCount() const { return (int) animations.size(); }
|
||||
const RawAnimation &GetAnimation(const int index) const { return animations[index]; }
|
||||
|
||||
// Iterate over the cameras.
|
||||
int GetCameraCount() const { return (int) cameras.size(); }
|
||||
const RawCamera &GetCamera(const int index) const { return cameras[index]; }
|
||||
|
||||
// Iterate over the nodes.
|
||||
int GetNodeCount() const { return (int) nodes.size(); }
|
||||
const RawNode &GetNode(const int index) const { return nodes[index]; }
|
||||
RawNode &GetNode(const int index) { return nodes[index]; }
|
||||
int GetNodeByName(const char *name) const;
|
||||
|
||||
// Create individual attribute arrays.
|
||||
// Returns true if the vertices store the particular attribute.
|
||||
template<typename _attrib_type_>
|
||||
void GetAttributeArray(std::vector<_attrib_type_> &out, const _attrib_type_ RawVertex::* ptr) const;
|
||||
|
||||
// Create an array with a raw model for each material.
|
||||
// Multiple surfaces with the same material will turn into a single model.
|
||||
// However, surfaces that are marked as 'discrete' will turn into separate models.
|
||||
void CreateMaterialModels(
|
||||
std::vector<RawModel> &materialModels, const int maxModelVertices, const int keepAttribs, const bool forceDiscrete) const;
|
||||
|
||||
private:
|
||||
std::string rootNodeName;
|
||||
int vertexAttributes;
|
||||
std::unordered_map<RawVertex, int, VertexHasher> vertexHash;
|
||||
std::vector<RawVertex> vertices;
|
||||
std::vector<RawTriangle> triangles;
|
||||
std::vector<RawTexture> textures;
|
||||
std::vector<RawMaterial> materials;
|
||||
std::vector<RawSurface> surfaces;
|
||||
std::vector<RawAnimation> animations;
|
||||
std::vector<RawCamera> cameras;
|
||||
std::vector<RawNode> nodes;
|
||||
};
|
||||
|
||||
template<typename _attrib_type_>
|
||||
void RawModel::GetAttributeArray(std::vector<_attrib_type_> &out, const _attrib_type_ RawVertex::* ptr) const
|
||||
{
|
||||
out.resize(vertices.size());
|
||||
for (size_t i = 0; i < vertices.size(); i++) {
|
||||
out[i] = vertices[i].*ptr;
|
||||
}
|
||||
}
|
||||
|
||||
#endif // !__RAWMODEL_H__
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "raw/RawModel.hpp"
|
||||
|
||||
bool LoadFBXFile(
|
||||
RawModel& raw,
|
||||
const std::string fbxFileName,
|
||||
const std::set<std::string>& textureExtensions,
|
||||
const GltfOptions& options);
|
||||
|
||||
json TranscribeProperty(FbxProperty& prop);
|
|
@ -0,0 +1,72 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include "FbxBlendShapesAccess.hpp"
|
||||
|
||||
FbxBlendShapesAccess::TargetShape::TargetShape(const FbxShape* shape, FbxDouble fullWeight)
|
||||
: shape(shape),
|
||||
fullWeight(fullWeight),
|
||||
count(shape->GetControlPointsCount()),
|
||||
positions(shape->GetControlPoints()),
|
||||
normals(FbxLayerElementAccess<FbxVector4>(
|
||||
shape->GetElementNormal(),
|
||||
shape->GetElementNormalCount())),
|
||||
tangents(FbxLayerElementAccess<FbxVector4>(
|
||||
shape->GetElementTangent(),
|
||||
shape->GetElementTangentCount())) {}
|
||||
|
||||
FbxAnimCurve* FbxBlendShapesAccess::BlendChannel::ExtractAnimation(unsigned int animIx) const {
|
||||
FbxAnimStack* stack = mesh->GetScene()->GetSrcObject<FbxAnimStack>(animIx);
|
||||
FbxAnimLayer* layer = stack->GetMember<FbxAnimLayer>(0);
|
||||
return mesh->GetShapeChannel(blendShapeIx, channelIx, layer, true);
|
||||
}
|
||||
|
||||
FbxBlendShapesAccess::BlendChannel::BlendChannel(
|
||||
FbxMesh* mesh,
|
||||
const unsigned int blendShapeIx,
|
||||
const unsigned int channelIx,
|
||||
const FbxDouble deformPercent,
|
||||
const std::vector<FbxBlendShapesAccess::TargetShape>& targetShapes,
|
||||
std::string name)
|
||||
: mesh(mesh),
|
||||
blendShapeIx(blendShapeIx),
|
||||
channelIx(channelIx),
|
||||
deformPercent(deformPercent),
|
||||
targetShapes(targetShapes),
|
||||
name(name) {}
|
||||
|
||||
std::vector<FbxBlendShapesAccess::BlendChannel> FbxBlendShapesAccess::extractChannels(
|
||||
FbxMesh* mesh) const {
|
||||
std::vector<BlendChannel> channels;
|
||||
for (int shapeIx = 0; shapeIx < mesh->GetDeformerCount(FbxDeformer::eBlendShape); shapeIx++) {
|
||||
auto* fbxBlendShape =
|
||||
static_cast<FbxBlendShape*>(mesh->GetDeformer(shapeIx, FbxDeformer::eBlendShape));
|
||||
|
||||
for (int channelIx = 0; channelIx < fbxBlendShape->GetBlendShapeChannelCount(); ++channelIx) {
|
||||
FbxBlendShapeChannel* fbxChannel = fbxBlendShape->GetBlendShapeChannel(channelIx);
|
||||
|
||||
if (fbxChannel->GetTargetShapeCount() > 0) {
|
||||
std::vector<TargetShape> targetShapes;
|
||||
const double* fullWeights = fbxChannel->GetTargetShapeFullWeights();
|
||||
std::string name = std::string(fbxChannel->GetName());
|
||||
|
||||
if (verboseOutput) {
|
||||
fmt::printf("\rblendshape channel: %s\n", name);
|
||||
}
|
||||
|
||||
for (int targetIx = 0; targetIx < fbxChannel->GetTargetShapeCount(); targetIx++) {
|
||||
FbxShape* fbxShape = fbxChannel->GetTargetShape(targetIx);
|
||||
targetShapes.emplace_back(fbxShape, fullWeights[targetIx]);
|
||||
}
|
||||
channels.emplace_back(
|
||||
mesh, shapeIx, channelIx, fbxChannel->DeformPercent * 0.01, targetShapes, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
return channels;
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "FBX2glTF.h"
|
||||
#include "FbxLayerElementAccess.hpp"
|
||||
|
||||
/**
|
||||
* At the FBX level, each Mesh can have a set of FbxBlendShape deformers; organisational units that
|
||||
* contain no data of their own. The actual deformation is determined by one or more
|
||||
* FbxBlendShapeChannels, whose influences are all additively applied to the mesh. In a simpler
|
||||
* world, each such channel would extend each base vertex with alternate position, and optionally
|
||||
* normal and tangent.
|
||||
*
|
||||
* It's not quite so simple, though. We also have progressive morphing, where one logical morph
|
||||
* actually consists of several concrete ones, each applied in sequence. For us, this means each
|
||||
* channel contains a sequence of FbxShapes (aka target shape); these are the actual data-holding
|
||||
* entities that provide the alternate vertex attributes. As such a channel is given more weight, it
|
||||
* moves from one target shape to another.
|
||||
*
|
||||
* The total number of alternate sets of attributes, then, is the total number of target shapes
|
||||
* across all the channels of all the blend shapes of the mesh.
|
||||
*
|
||||
* Each animation in the scene stack can yield one or zero FbxAnimCurves per channel (not target
|
||||
* shape). We evaluate these curves to get the weight of the channel: this weight is further
|
||||
* introspected on to figure out which target shapes we're currently interpolation between.
|
||||
*/
|
||||
class FbxBlendShapesAccess {
|
||||
public:
|
||||
/**
|
||||
* A target shape is on a 1:1 basis with the eventual glTF morph target, and is the object which
|
||||
* contains the actual morphed vertex data.
|
||||
*/
|
||||
struct TargetShape {
|
||||
explicit TargetShape(const FbxShape* shape, FbxDouble fullWeight);
|
||||
|
||||
const FbxShape* shape;
|
||||
const FbxDouble fullWeight;
|
||||
const unsigned int count;
|
||||
const FbxVector4* positions;
|
||||
const FbxLayerElementAccess<FbxVector4> normals;
|
||||
const FbxLayerElementAccess<FbxVector4> tangents;
|
||||
};
|
||||
|
||||
/**
|
||||
* A channel collects a sequence (often of length 1) of target shapes.
|
||||
*/
|
||||
struct BlendChannel {
|
||||
BlendChannel(
|
||||
FbxMesh* mesh,
|
||||
const unsigned int blendShapeIx,
|
||||
const unsigned int channelIx,
|
||||
const FbxDouble deformPercent,
|
||||
const std::vector<TargetShape>& targetShapes,
|
||||
const std::string name);
|
||||
|
||||
FbxAnimCurve* ExtractAnimation(unsigned int animIx) const;
|
||||
|
||||
FbxMesh* const mesh;
|
||||
|
||||
const unsigned int blendShapeIx;
|
||||
const unsigned int channelIx;
|
||||
const std::vector<TargetShape> targetShapes;
|
||||
const std::string name;
|
||||
|
||||
const FbxDouble deformPercent;
|
||||
};
|
||||
|
||||
explicit FbxBlendShapesAccess(FbxMesh* mesh) : channels(extractChannels(mesh)) {}
|
||||
|
||||
size_t GetChannelCount() const {
|
||||
return channels.size();
|
||||
}
|
||||
const BlendChannel& GetBlendChannel(size_t channelIx) const {
|
||||
return channels.at(channelIx);
|
||||
}
|
||||
|
||||
size_t GetTargetShapeCount(size_t channelIx) const {
|
||||
return channels[channelIx].targetShapes.size();
|
||||
}
|
||||
const TargetShape& GetTargetShape(size_t channelIx, size_t targetShapeIx) const {
|
||||
return channels.at(channelIx).targetShapes[targetShapeIx];
|
||||
}
|
||||
|
||||
FbxAnimCurve* GetAnimation(size_t channelIx, size_t animIx) const {
|
||||
return channels.at(channelIx).ExtractAnimation(to_uint32(animIx));
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<BlendChannel> extractChannels(FbxMesh* mesh) const;
|
||||
|
||||
const std::vector<BlendChannel> channels;
|
||||
};
|
|
@ -0,0 +1,94 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
#pragma once
|
||||
#include "FBX2glTF.h"
|
||||
|
||||
template <typename _type_>
|
||||
class FbxLayerElementAccess {
|
||||
public:
|
||||
FbxLayerElementAccess(const FbxLayerElementTemplate<_type_>* layer, int count);
|
||||
|
||||
bool LayerPresent() const {
|
||||
return (mappingMode != FbxLayerElement::eNone);
|
||||
}
|
||||
|
||||
_type_ GetElement(
|
||||
const int polygonIndex,
|
||||
const int polygonVertexIndex,
|
||||
const int controlPointIndex,
|
||||
const _type_ defaultValue) const;
|
||||
_type_ GetElement(
|
||||
const int polygonIndex,
|
||||
const int polygonVertexIndex,
|
||||
const int controlPointIndex,
|
||||
const _type_ defaultValue,
|
||||
const FbxMatrix& transform,
|
||||
const bool normalize) const;
|
||||
|
||||
private:
|
||||
FbxLayerElement::EMappingMode mappingMode;
|
||||
const FbxLayerElementArrayTemplate<_type_>* elements;
|
||||
const FbxLayerElementArrayTemplate<int>* indices;
|
||||
};
|
||||
|
||||
template <typename _type_>
|
||||
FbxLayerElementAccess<_type_>::FbxLayerElementAccess(
|
||||
const FbxLayerElementTemplate<_type_>* layer,
|
||||
int count)
|
||||
: mappingMode(FbxLayerElement::eNone), elements(nullptr), indices(nullptr) {
|
||||
if (count <= 0 || layer == nullptr) {
|
||||
return;
|
||||
}
|
||||
const FbxLayerElement::EMappingMode newMappingMode = layer->GetMappingMode();
|
||||
if (newMappingMode == FbxLayerElement::eByControlPoint ||
|
||||
newMappingMode == FbxLayerElement::eByPolygonVertex ||
|
||||
newMappingMode == FbxLayerElement::eByPolygon) {
|
||||
mappingMode = newMappingMode;
|
||||
elements = &layer->GetDirectArray();
|
||||
indices = (layer->GetReferenceMode() == FbxLayerElement::eIndexToDirect ||
|
||||
layer->GetReferenceMode() == FbxLayerElement::eIndex)
|
||||
? &layer->GetIndexArray()
|
||||
: nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename _type_>
|
||||
_type_ FbxLayerElementAccess<_type_>::GetElement(
|
||||
const int polygonIndex,
|
||||
const int polygonVertexIndex,
|
||||
const int controlPointIndex,
|
||||
const _type_ defaultValue) const {
|
||||
if (mappingMode != FbxLayerElement::eNone) {
|
||||
int index = (mappingMode == FbxLayerElement::eByControlPoint)
|
||||
? controlPointIndex
|
||||
: ((mappingMode == FbxLayerElement::eByPolygonVertex) ? polygonVertexIndex : polygonIndex);
|
||||
index = (indices != nullptr) ? (*indices)[index] : index;
|
||||
_type_ element = elements->GetAt(index);
|
||||
return element;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
template <typename _type_>
|
||||
_type_ FbxLayerElementAccess<_type_>::GetElement(
|
||||
const int polygonIndex,
|
||||
const int polygonVertexIndex,
|
||||
const int controlPointIndex,
|
||||
const _type_ defaultValue,
|
||||
const FbxMatrix& transform,
|
||||
const bool normalize) const {
|
||||
if (mappingMode != FbxLayerElement::eNone) {
|
||||
_type_ element = transform.MultNormalize(
|
||||
GetElement(polygonIndex, polygonVertexIndex, controlPointIndex, defaultValue));
|
||||
if (normalize) {
|
||||
element.Normalize();
|
||||
}
|
||||
return element;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include "FbxSkinningAccess.hpp"
|
||||
|
||||
FbxSkinningAccess::FbxSkinningAccess(const FbxMesh* pMesh, FbxScene* pScene, FbxNode* pNode)
|
||||
: rootIndex(-1) {
|
||||
for (int deformerIndex = 0; deformerIndex < pMesh->GetDeformerCount(); deformerIndex++) {
|
||||
FbxSkin* skin =
|
||||
reinterpret_cast<FbxSkin*>(pMesh->GetDeformer(deformerIndex, FbxDeformer::eSkin));
|
||||
if (skin != nullptr) {
|
||||
const int clusterCount = skin->GetClusterCount();
|
||||
if (clusterCount == 0) {
|
||||
continue;
|
||||
}
|
||||
int controlPointCount = pMesh->GetControlPointsCount();
|
||||
vertexJointIndices.resize(controlPointCount, Vec4i(0, 0, 0, 0));
|
||||
vertexJointWeights.resize(controlPointCount, Vec4f(0.0f, 0.0f, 0.0f, 0.0f));
|
||||
|
||||
for (int clusterIndex = 0; clusterIndex < clusterCount; clusterIndex++) {
|
||||
FbxCluster* cluster = skin->GetCluster(clusterIndex);
|
||||
const int indexCount = cluster->GetControlPointIndicesCount();
|
||||
const int* clusterIndices = cluster->GetControlPointIndices();
|
||||
const double* clusterWeights = cluster->GetControlPointWeights();
|
||||
|
||||
assert(
|
||||
cluster->GetLinkMode() == FbxCluster::eNormalize ||
|
||||
cluster->GetLinkMode() == FbxCluster::eTotalOne);
|
||||
|
||||
// Transform link matrix.
|
||||
FbxAMatrix transformLinkMatrix;
|
||||
cluster->GetTransformLinkMatrix(transformLinkMatrix);
|
||||
|
||||
// The transformation of the mesh at binding time
|
||||
FbxAMatrix transformMatrix;
|
||||
cluster->GetTransformMatrix(transformMatrix);
|
||||
|
||||
// Inverse bind matrix.
|
||||
FbxAMatrix globalBindposeInverseMatrix = transformLinkMatrix.Inverse() * transformMatrix;
|
||||
inverseBindMatrices.emplace_back(globalBindposeInverseMatrix);
|
||||
|
||||
jointNodes.push_back(cluster->GetLink());
|
||||
jointIds.push_back(cluster->GetLink()->GetUniqueID());
|
||||
|
||||
const FbxAMatrix globalNodeTransform = cluster->GetLink()->EvaluateGlobalTransform();
|
||||
jointSkinningTransforms.push_back(
|
||||
FbxMatrix(globalNodeTransform * globalBindposeInverseMatrix));
|
||||
jointInverseGlobalTransforms.push_back(FbxMatrix(globalNodeTransform.Inverse()));
|
||||
|
||||
for (int i = 0; i < indexCount; i++) {
|
||||
if (clusterIndices[i] < 0 || clusterIndices[i] >= controlPointCount) {
|
||||
continue;
|
||||
}
|
||||
if (clusterWeights[i] <= vertexJointWeights[clusterIndices[i]][MAX_WEIGHTS - 1]) {
|
||||
continue;
|
||||
}
|
||||
vertexJointIndices[clusterIndices[i]][MAX_WEIGHTS - 1] = clusterIndex;
|
||||
vertexJointWeights[clusterIndices[i]][MAX_WEIGHTS - 1] = (float)clusterWeights[i];
|
||||
for (int j = MAX_WEIGHTS - 1; j > 0; j--) {
|
||||
if (vertexJointWeights[clusterIndices[i]][j - 1] >=
|
||||
vertexJointWeights[clusterIndices[i]][j]) {
|
||||
break;
|
||||
}
|
||||
std::swap(
|
||||
vertexJointIndices[clusterIndices[i]][j - 1],
|
||||
vertexJointIndices[clusterIndices[i]][j]);
|
||||
std::swap(
|
||||
vertexJointWeights[clusterIndices[i]][j - 1],
|
||||
vertexJointWeights[clusterIndices[i]][j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < controlPointCount; i++) {
|
||||
const float weightSumRcp = 1.0 /
|
||||
(vertexJointWeights[i][0] + vertexJointWeights[i][1] + vertexJointWeights[i][2] +
|
||||
vertexJointWeights[i][3]);
|
||||
vertexJointWeights[i] *= weightSumRcp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rootIndex = -1;
|
||||
for (size_t i = 0; i < jointNodes.size() && rootIndex == -1; i++) {
|
||||
rootIndex = (int)i;
|
||||
FbxNode* parent = jointNodes[i]->GetParent();
|
||||
if (parent == nullptr) {
|
||||
break;
|
||||
}
|
||||
for (size_t j = 0; j < jointNodes.size(); j++) {
|
||||
if (jointNodes[j] == parent) {
|
||||
rootIndex = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "FBX2glTF.h"
|
||||
|
||||
class FbxSkinningAccess {
|
||||
public:
|
||||
static const int MAX_WEIGHTS = 4;
|
||||
|
||||
FbxSkinningAccess(const FbxMesh* pMesh, FbxScene* pScene, FbxNode* pNode);
|
||||
|
||||
bool IsSkinned() const {
|
||||
return (vertexJointWeights.size() > 0);
|
||||
}
|
||||
|
||||
int GetNodeCount() const {
|
||||
return (int)jointNodes.size();
|
||||
}
|
||||
|
||||
FbxNode* GetJointNode(const int jointIndex) const {
|
||||
return jointNodes[jointIndex];
|
||||
}
|
||||
|
||||
const uint64_t GetJointId(const int jointIndex) const {
|
||||
return jointIds[jointIndex];
|
||||
}
|
||||
|
||||
const FbxMatrix& GetJointSkinningTransform(const int jointIndex) const {
|
||||
return jointSkinningTransforms[jointIndex];
|
||||
}
|
||||
|
||||
const FbxMatrix& GetJointInverseGlobalTransforms(const int jointIndex) const {
|
||||
return jointInverseGlobalTransforms[jointIndex];
|
||||
}
|
||||
|
||||
const uint64_t GetRootNode() const {
|
||||
assert(rootIndex != -1);
|
||||
return jointIds[rootIndex];
|
||||
}
|
||||
|
||||
const FbxAMatrix& GetInverseBindMatrix(const int jointIndex) const {
|
||||
return inverseBindMatrices[jointIndex];
|
||||
}
|
||||
|
||||
const Vec4i GetVertexIndices(const int controlPointIndex) const {
|
||||
return (!vertexJointIndices.empty()) ? vertexJointIndices[controlPointIndex]
|
||||
: Vec4i(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
const Vec4f GetVertexWeights(const int controlPointIndex) const {
|
||||
return (!vertexJointWeights.empty()) ? vertexJointWeights[controlPointIndex]
|
||||
: Vec4f(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
private:
|
||||
int rootIndex;
|
||||
std::vector<uint64_t> jointIds;
|
||||
std::vector<FbxNode*> jointNodes;
|
||||
std::vector<FbxMatrix> jointSkinningTransforms;
|
||||
std::vector<FbxMatrix> jointInverseGlobalTransforms;
|
||||
std::vector<FbxAMatrix> inverseBindMatrices;
|
||||
std::vector<Vec4i> vertexJointIndices;
|
||||
std::vector<Vec4f> vertexJointWeights;
|
||||
};
|
|
@ -0,0 +1,171 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include "RoughnessMetallicMaterials.hpp"
|
||||
|
||||
std::unique_ptr<FbxRoughMetMaterialInfo> Fbx3dsMaxPhysicalMaterialResolver::resolve() const {
|
||||
const FbxProperty topProp = fbxMaterial->FindProperty("3dsMax", false);
|
||||
if (topProp.GetPropertyDataType() != FbxCompoundDT) {
|
||||
return nullptr;
|
||||
}
|
||||
const FbxProperty props = topProp.Find("Parameters", false);
|
||||
if (!props.IsValid()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FbxString shadingModel = fbxMaterial->ShadingModel.Get();
|
||||
if (!shadingModel.IsEmpty() && shadingModel != "unknown") {
|
||||
::fmt::printf(
|
||||
"Warning: Material %s has surprising shading model: %s\n",
|
||||
fbxMaterial->GetName(),
|
||||
shadingModel);
|
||||
}
|
||||
|
||||
auto getTex = [&](std::string propName) -> const FbxFileTexture* {
|
||||
const FbxFileTexture* ptr = nullptr;
|
||||
const FbxProperty texProp = props.Find((propName + "_map").c_str(), false);
|
||||
if (texProp.IsValid()) {
|
||||
const FbxProperty useProp = props.Find((propName + "_map_on").c_str(), false);
|
||||
if (useProp.IsValid() && !useProp.Get<FbxBool>()) {
|
||||
// skip this texture if the _on property exists *and* is explicitly false
|
||||
return nullptr;
|
||||
}
|
||||
ptr = texProp.GetSrcObject<FbxFileTexture>();
|
||||
if (ptr != nullptr && textureLocations.find(ptr) == textureLocations.end()) {
|
||||
ptr = nullptr;
|
||||
}
|
||||
}
|
||||
return ptr;
|
||||
};
|
||||
|
||||
FbxDouble baseWeight = getValue(props, "base_weight", 1.0);
|
||||
const auto* baseWeightMap = getTex("base_weight");
|
||||
FbxDouble4 baseCol = getValue(props, "base_color", FbxDouble4(0.5, 0.5, 0.5, 1.0));
|
||||
const auto* baseTex = getTex("base_color");
|
||||
|
||||
double emissiveWeight = getValue(props, "emission", 0.0);
|
||||
const auto* emissiveWeightMap = getTex("emission");
|
||||
FbxDouble4 emissiveColor = getValue(props, "emit_color", FbxDouble4(1, 1, 1, 1));
|
||||
const auto* emissiveColorMap = getTex("emit_color");
|
||||
|
||||
double roughness = getValue(props, "roughness", 0.0);
|
||||
const auto* roughnessMap = getTex("roughness");
|
||||
double metalness = getValue(props, "metalness", 0.0);
|
||||
const auto* metalnessMap = getTex("metalness");
|
||||
|
||||
// TODO: we need this to affect roughness map, too.
|
||||
bool invertRoughness = getValue(props, "inv_roughness", false);
|
||||
if (invertRoughness) {
|
||||
roughness = 1.0f - roughness;
|
||||
}
|
||||
|
||||
std::string unsupported;
|
||||
const auto addUnsupported = [&](const std::string bit) {
|
||||
if (!unsupported.empty()) {
|
||||
unsupported += ", ";
|
||||
}
|
||||
unsupported += bit;
|
||||
};
|
||||
|
||||
// TODO: turn this into a normal map through simple numerial differentiation
|
||||
const auto* bumpMap = getTex("bump");
|
||||
if (bumpMap != nullptr) {
|
||||
addUnsupported("bump map");
|
||||
}
|
||||
|
||||
// TODO: bake transparency > 0.0f into the alpha of baseColor?
|
||||
double transparency = getValue(props, "transparency", 0.0);
|
||||
const auto* transparencyMap = getTex("transparency");
|
||||
if (transparency != 0.0 || transparencyMap != nullptr) {
|
||||
addUnsupported("transparency");
|
||||
}
|
||||
|
||||
// TODO: if/when we bake transparency, we'll need this
|
||||
// double transparencyDepth = getValue(props, "trans_depth", 0.0);
|
||||
// if (transparencyDepth != 0.0) {
|
||||
// addUnsupported("transparency depth");
|
||||
// }
|
||||
// double transparencyColor = getValue(props, "trans_color", 0.0);
|
||||
// const auto* transparencyColorMap = getTex("trans_color");
|
||||
// if (transparencyColor != 0.0 || transparencyColorMap != nullptr) {
|
||||
// addUnsupported("transparency color");
|
||||
// }
|
||||
// double thinWalledTransparency = getValue(props, "thin_walled", false);
|
||||
// if (thinWalledTransparency) {
|
||||
// addUnsupported("thin-walled transparency");
|
||||
// }
|
||||
|
||||
const auto* displacementMap = getTex("displacement");
|
||||
if (displacementMap != nullptr) {
|
||||
addUnsupported("displacement");
|
||||
}
|
||||
|
||||
double reflectivityWeight = getValue(props, "reflectivity", 1.0);
|
||||
const auto* reflectivityWeightMap = getTex("reflectivity");
|
||||
FbxDouble4 reflectivityColor = getValue(props, "refl_color", FbxDouble4(1, 1, 1, 1));
|
||||
const auto* reflectivityColorMap = getTex("refl_color");
|
||||
if (reflectivityWeight != 1.0 || reflectivityWeightMap != nullptr ||
|
||||
reflectivityColor != FbxDouble4(1, 1, 1, 1) || reflectivityColorMap != nullptr) {
|
||||
addUnsupported("reflectivity");
|
||||
}
|
||||
|
||||
double scattering = getValue(props, "scattering", 0.0);
|
||||
const auto* scatteringMap = getTex("scattering");
|
||||
if (scattering != 0.0 || scatteringMap != nullptr) {
|
||||
addUnsupported("sub-surface scattering");
|
||||
}
|
||||
|
||||
double coating = getValue(props, "coating", 0.0);
|
||||
if (coating != 0.0) {
|
||||
addUnsupported("coating");
|
||||
}
|
||||
|
||||
double diffuseRoughness = getValue(props, "diff_roughness", 0.0);
|
||||
if (diffuseRoughness != 0.0) {
|
||||
addUnsupported("diffuse roughness");
|
||||
}
|
||||
|
||||
bool isBrdfMode = getValue(props, "brdf_mode", false);
|
||||
if (isBrdfMode) {
|
||||
addUnsupported("advanced reflectance custom curve");
|
||||
}
|
||||
|
||||
double anisotropy = getValue(props, "anisotropy", 1.0);
|
||||
if (anisotropy != 1.0) {
|
||||
addUnsupported("anisotropy");
|
||||
}
|
||||
|
||||
if (verboseOutput && !unsupported.empty()) {
|
||||
fmt::printf(
|
||||
"Warning: 3dsMax Physical Material %s uses features glTF cannot express:\n %s\n",
|
||||
fbxMaterial->GetName(),
|
||||
unsupported);
|
||||
}
|
||||
|
||||
std::unique_ptr<FbxRoughMetMaterialInfo> res(new FbxRoughMetMaterialInfo(
|
||||
fbxMaterial->GetUniqueID(),
|
||||
fbxMaterial->GetName(),
|
||||
FbxRoughMetMaterialInfo::FBX_SHADER_METROUGH,
|
||||
baseCol,
|
||||
metalness,
|
||||
roughness));
|
||||
res->texBaseColor = baseTex;
|
||||
res->baseWeight = baseWeight;
|
||||
res->texBaseWeight = baseWeightMap;
|
||||
|
||||
res->texMetallic = metalnessMap;
|
||||
res->texRoughness = roughnessMap;
|
||||
res->invertRoughnessMap = invertRoughness;
|
||||
|
||||
res->emissive = emissiveColor;
|
||||
res->emissiveIntensity = emissiveWeight;
|
||||
res->texEmissive = emissiveColorMap;
|
||||
res->texEmissiveWeight = emissiveWeightMap;
|
||||
|
||||
return res;
|
||||
}
|
|
@ -0,0 +1,122 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include "fbx/Fbx2Raw.hpp"
|
||||
|
||||
#include "FbxMaterials.hpp"
|
||||
#include "RoughnessMetallicMaterials.hpp"
|
||||
#include "TraditionalMaterials.hpp"
|
||||
|
||||
static int warnMtrCount = 0;
|
||||
|
||||
FbxMaterialsAccess::FbxMaterialsAccess(
|
||||
const FbxMesh* pMesh,
|
||||
const std::map<const FbxTexture*, FbxString>& textureLocations)
|
||||
: mappingMode(FbxGeometryElement::eNone), mesh(nullptr), indices(nullptr) {
|
||||
if (pMesh->GetElementMaterialCount() <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const FbxGeometryElement::EMappingMode materialMappingMode =
|
||||
pMesh->GetElementMaterial()->GetMappingMode();
|
||||
if (materialMappingMode != FbxGeometryElement::eByPolygon &&
|
||||
materialMappingMode != FbxGeometryElement::eAllSame) {
|
||||
return;
|
||||
}
|
||||
|
||||
const FbxGeometryElement::EReferenceMode materialReferenceMode =
|
||||
pMesh->GetElementMaterial()->GetReferenceMode();
|
||||
if (materialReferenceMode != FbxGeometryElement::eIndexToDirect) {
|
||||
return;
|
||||
}
|
||||
|
||||
mappingMode = materialMappingMode;
|
||||
mesh = pMesh;
|
||||
indices = &pMesh->GetElementMaterial()->GetIndexArray();
|
||||
|
||||
for (int ii = 0; ii < indices->GetCount(); ii++) {
|
||||
int materialNum = indices->GetAt(ii);
|
||||
if (materialNum < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto* surfaceMaterial =
|
||||
mesh->GetNode()->GetSrcObject<FbxSurfaceMaterial>(materialNum);
|
||||
|
||||
if (!surfaceMaterial) {
|
||||
if (++warnMtrCount == 1) {
|
||||
fmt::printf("Warning: Reference to missing surface material.\n");
|
||||
fmt::printf(" (Further warnings of this type squelched.)\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (materialNum >= summaries.size()) {
|
||||
summaries.resize(materialNum + 1);
|
||||
}
|
||||
auto summary = summaries[materialNum];
|
||||
if (summary == nullptr) {
|
||||
summary = summaries[materialNum] = GetMaterialInfo(surfaceMaterial, textureLocations);
|
||||
}
|
||||
|
||||
if (materialNum >= userProperties.size()) {
|
||||
userProperties.resize(materialNum + 1);
|
||||
}
|
||||
if (surfaceMaterial && userProperties[materialNum].empty()) {
|
||||
|
||||
FbxProperty objectProperty = surfaceMaterial->GetFirstProperty();
|
||||
while (objectProperty.IsValid()) {
|
||||
if (objectProperty.GetFlag(FbxPropertyFlags::eUserDefined)) {
|
||||
userProperties[materialNum].push_back(TranscribeProperty(objectProperty).dump());
|
||||
}
|
||||
objectProperty = surfaceMaterial->GetNextProperty(objectProperty);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::shared_ptr<FbxMaterialInfo> FbxMaterialsAccess::GetMaterial(
|
||||
const int polygonIndex) const {
|
||||
if (mappingMode != FbxGeometryElement::eNone) {
|
||||
const int materialNum =
|
||||
indices->GetAt((mappingMode == FbxGeometryElement::eByPolygon) ? polygonIndex : 0);
|
||||
if (materialNum < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
return summaries.at((unsigned long)materialNum);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const std::vector<std::string> FbxMaterialsAccess::GetUserProperties(const int polygonIndex) const {
|
||||
if (mappingMode != FbxGeometryElement::eNone) {
|
||||
const int materialNum =
|
||||
indices->GetAt((mappingMode == FbxGeometryElement::eByPolygon) ? polygonIndex : 0);
|
||||
if (materialNum < 0) {
|
||||
return std::vector<std::string>();
|
||||
}
|
||||
return userProperties.at((unsigned long)materialNum);
|
||||
}
|
||||
return std::vector<std::string>();
|
||||
}
|
||||
|
||||
std::unique_ptr<FbxMaterialInfo> FbxMaterialsAccess::GetMaterialInfo(
|
||||
FbxSurfaceMaterial* material,
|
||||
const std::map<const FbxTexture*, FbxString>& textureLocations) {
|
||||
if (!material) {
|
||||
return nullptr;
|
||||
}
|
||||
std::unique_ptr<FbxMaterialInfo> res =
|
||||
FbxStingrayPBSMaterialResolver(material, textureLocations).resolve();
|
||||
if (res == nullptr) {
|
||||
res = Fbx3dsMaxPhysicalMaterialResolver(material, textureLocations).resolve();
|
||||
if (res == nullptr) {
|
||||
res = FbxTraditionalMaterialResolver(material, textureLocations).resolve();
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include "FBX2glTF.h"
|
||||
|
||||
class FbxMaterialInfo {
|
||||
public:
|
||||
FbxMaterialInfo(const FbxUInt64 id, const FbxString& name, const FbxString& shadingModel)
|
||||
: id(id), name(name), shadingModel(shadingModel) {}
|
||||
|
||||
const FbxUInt64 id;
|
||||
const FbxString name;
|
||||
const FbxString shadingModel;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class FbxMaterialResolver {
|
||||
public:
|
||||
FbxMaterialResolver(
|
||||
FbxSurfaceMaterial* fbxMaterial,
|
||||
const std::map<const FbxTexture*, FbxString>& textureLocations)
|
||||
: fbxMaterial(fbxMaterial), textureLocations(textureLocations) {}
|
||||
virtual std::unique_ptr<T> resolve() const = 0;
|
||||
|
||||
protected:
|
||||
const FbxSurfaceMaterial* fbxMaterial;
|
||||
const std::map<const FbxTexture*, FbxString> textureLocations;
|
||||
};
|
||||
|
||||
class FbxMaterialsAccess {
|
||||
public:
|
||||
FbxMaterialsAccess(
|
||||
const FbxMesh* pMesh,
|
||||
const std::map<const FbxTexture*, FbxString>& textureLocations);
|
||||
|
||||
const std::shared_ptr<FbxMaterialInfo> GetMaterial(const int polygonIndex) const;
|
||||
|
||||
const std::vector<std::string> GetUserProperties(const int polygonIndex) const;
|
||||
|
||||
std::unique_ptr<FbxMaterialInfo> GetMaterialInfo(
|
||||
FbxSurfaceMaterial* material,
|
||||
const std::map<const FbxTexture*, FbxString>& textureLocations);
|
||||
|
||||
private:
|
||||
FbxGeometryElement::EMappingMode mappingMode;
|
||||
std::vector<std::shared_ptr<FbxMaterialInfo>> summaries{};
|
||||
std::vector<std::vector<std::string>> userProperties;
|
||||
const FbxMesh* mesh;
|
||||
const FbxLayerElementArrayTemplate<int>* indices;
|
||||
};
|
|
@ -0,0 +1,78 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
|
||||
#include "FbxMaterials.hpp"
|
||||
|
||||
struct FbxRoughMetMaterialInfo : FbxMaterialInfo {
|
||||
static constexpr const char* FBX_SHADER_METROUGH = "MetallicRoughness";
|
||||
|
||||
static std::unique_ptr<FbxRoughMetMaterialInfo> From(
|
||||
FbxSurfaceMaterial* fbxMaterial,
|
||||
const std::map<const FbxTexture*, FbxString>& textureLocations);
|
||||
|
||||
FbxRoughMetMaterialInfo(
|
||||
const FbxUInt64 id,
|
||||
const FbxString& name,
|
||||
const FbxString& shadingModel,
|
||||
FbxDouble4 baseColor,
|
||||
FbxDouble metallic,
|
||||
FbxDouble roughness)
|
||||
: FbxMaterialInfo(id, name, shadingModel),
|
||||
baseColor(baseColor),
|
||||
metallic(metallic),
|
||||
roughness(roughness) {}
|
||||
|
||||
const FbxVector4 baseColor;
|
||||
const FbxDouble metallic;
|
||||
const FbxDouble roughness;
|
||||
|
||||
FbxBool invertRoughnessMap = false;
|
||||
FbxDouble baseWeight = 1;
|
||||
FbxVector4 emissive = FbxVector4(0, 0, 0, 1);
|
||||
FbxDouble emissiveIntensity = 1;
|
||||
|
||||
const FbxFileTexture* texNormal = nullptr;
|
||||
const FbxFileTexture* texBaseColor = nullptr;
|
||||
const FbxFileTexture* texBaseWeight = nullptr;
|
||||
const FbxFileTexture* texMetallic = nullptr;
|
||||
const FbxFileTexture* texRoughness = nullptr;
|
||||
const FbxFileTexture* texEmissive = nullptr;
|
||||
const FbxFileTexture* texEmissiveWeight = nullptr;
|
||||
const FbxFileTexture* texAmbientOcclusion = nullptr;
|
||||
};
|
||||
|
||||
class FbxStingrayPBSMaterialResolver : FbxMaterialResolver<FbxRoughMetMaterialInfo> {
|
||||
public:
|
||||
FbxStingrayPBSMaterialResolver(
|
||||
FbxSurfaceMaterial* fbxMaterial,
|
||||
const std::map<const FbxTexture*, FbxString>& textureLocations)
|
||||
: FbxMaterialResolver(fbxMaterial, textureLocations) {}
|
||||
|
||||
virtual std::unique_ptr<FbxRoughMetMaterialInfo> resolve() const;
|
||||
};
|
||||
|
||||
class Fbx3dsMaxPhysicalMaterialResolver : FbxMaterialResolver<FbxRoughMetMaterialInfo> {
|
||||
public:
|
||||
Fbx3dsMaxPhysicalMaterialResolver(
|
||||
FbxSurfaceMaterial* fbxMaterial,
|
||||
const std::map<const FbxTexture*, FbxString>& textureLocations)
|
||||
: FbxMaterialResolver(fbxMaterial, textureLocations) {}
|
||||
|
||||
virtual std::unique_ptr<FbxRoughMetMaterialInfo> resolve() const;
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
T getValue(const FbxProperty& props, std::string propName, const T& def) const {
|
||||
const FbxProperty prop = props.FindHierarchical(propName.c_str());
|
||||
return prop.IsValid() ? prop.Get<T>() : def;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,67 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include "RoughnessMetallicMaterials.hpp"
|
||||
|
||||
std::unique_ptr<FbxRoughMetMaterialInfo> FbxStingrayPBSMaterialResolver::resolve() const {
|
||||
const FbxProperty mayaProp = fbxMaterial->FindProperty("Maya");
|
||||
if (mayaProp.GetPropertyDataType() != FbxCompoundDT) {
|
||||
return nullptr;
|
||||
}
|
||||
if (!fbxMaterial->ShadingModel.Get().IsEmpty()) {
|
||||
::fmt::printf(
|
||||
"Warning: Material %s has surprising shading model: %s\n",
|
||||
fbxMaterial->GetName(),
|
||||
fbxMaterial->ShadingModel.Get());
|
||||
}
|
||||
|
||||
auto getTex = [&](std::string propName) {
|
||||
const FbxFileTexture* ptr = nullptr;
|
||||
|
||||
const FbxProperty useProp = mayaProp.FindHierarchical(("use_" + propName + "_map").c_str());
|
||||
if (useProp.IsValid() && useProp.Get<bool>()) {
|
||||
const FbxProperty texProp = mayaProp.FindHierarchical(("TEX_" + propName + "_map").c_str());
|
||||
if (texProp.IsValid()) {
|
||||
ptr = texProp.GetSrcObject<FbxFileTexture>();
|
||||
if (ptr != nullptr && textureLocations.find(ptr) == textureLocations.end()) {
|
||||
ptr = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ptr;
|
||||
};
|
||||
|
||||
auto getVec = [&](std::string propName) -> FbxDouble3 {
|
||||
const FbxProperty vecProp = mayaProp.FindHierarchical(propName.c_str());
|
||||
return vecProp.IsValid() ? vecProp.Get<FbxDouble3>() : FbxDouble3(1, 1, 1);
|
||||
};
|
||||
|
||||
auto getVal = [&](std::string propName) -> FbxDouble {
|
||||
const FbxProperty vecProp = mayaProp.FindHierarchical(propName.c_str());
|
||||
return vecProp.IsValid() ? vecProp.Get<FbxDouble>() : 0;
|
||||
};
|
||||
|
||||
FbxDouble3 baseColor = getVec("base_color");
|
||||
std::unique_ptr<FbxRoughMetMaterialInfo> res(new FbxRoughMetMaterialInfo(
|
||||
fbxMaterial->GetUniqueID(),
|
||||
fbxMaterial->GetName(),
|
||||
FbxRoughMetMaterialInfo::FBX_SHADER_METROUGH,
|
||||
FbxDouble4(baseColor[0], baseColor[1], baseColor[2], 1),
|
||||
getVal("metallic"),
|
||||
getVal("roughness")));
|
||||
res->texNormal = getTex("normal");
|
||||
res->texBaseColor = getTex("color");
|
||||
res->texAmbientOcclusion = getTex("ao");
|
||||
res->texEmissive = getTex("emissive");
|
||||
res->emissive = getVec("emissive");
|
||||
res->emissiveIntensity = getVal("emissive_intensity");
|
||||
res->texMetallic = getTex("metallic");
|
||||
res->texRoughness = getTex("roughness");
|
||||
|
||||
return res;
|
||||
};
|
|
@ -0,0 +1,134 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include "TraditionalMaterials.hpp"
|
||||
|
||||
std::unique_ptr<FbxTraditionalMaterialInfo> FbxTraditionalMaterialResolver::resolve() const {
|
||||
auto getSurfaceScalar = [&](const char* propName) -> std::tuple<FbxDouble, FbxFileTexture*> {
|
||||
const FbxProperty prop = fbxMaterial->FindProperty(propName);
|
||||
|
||||
FbxDouble val(0);
|
||||
FbxFileTexture* tex = prop.GetSrcObject<FbxFileTexture>();
|
||||
if (tex != nullptr && textureLocations.find(tex) == textureLocations.end()) {
|
||||
tex = nullptr;
|
||||
}
|
||||
if (tex == nullptr && prop.IsValid()) {
|
||||
val = prop.Get<FbxDouble>();
|
||||
}
|
||||
return std::make_tuple(val, tex);
|
||||
};
|
||||
|
||||
auto getSurfaceVector = [&](const char* propName) -> std::tuple<FbxDouble3, FbxFileTexture*> {
|
||||
const FbxProperty prop = fbxMaterial->FindProperty(propName);
|
||||
|
||||
FbxDouble3 val(1, 1, 1);
|
||||
FbxFileTexture* tex = prop.GetSrcObject<FbxFileTexture>();
|
||||
if (tex != nullptr && textureLocations.find(tex) == textureLocations.end()) {
|
||||
tex = nullptr;
|
||||
}
|
||||
if (tex == nullptr && prop.IsValid()) {
|
||||
val = prop.Get<FbxDouble3>();
|
||||
}
|
||||
return std::make_tuple(val, tex);
|
||||
};
|
||||
|
||||
auto getSurfaceValues =
|
||||
[&](const char* colName,
|
||||
const char* facName) -> std::tuple<FbxVector4, FbxFileTexture*, FbxFileTexture*> {
|
||||
const FbxProperty colProp = fbxMaterial->FindProperty(colName);
|
||||
const FbxProperty facProp = fbxMaterial->FindProperty(facName);
|
||||
|
||||
FbxDouble3 colorVal(1, 1, 1);
|
||||
FbxDouble factorVal(1);
|
||||
|
||||
FbxFileTexture* colTex = colProp.GetSrcObject<FbxFileTexture>();
|
||||
if (colTex != nullptr && textureLocations.find(colTex) == textureLocations.end()) {
|
||||
colTex = nullptr;
|
||||
}
|
||||
if (colTex == nullptr && colProp.IsValid()) {
|
||||
colorVal = colProp.Get<FbxDouble3>();
|
||||
}
|
||||
FbxFileTexture* facTex = facProp.GetSrcObject<FbxFileTexture>();
|
||||
if (facTex != nullptr && textureLocations.find(facTex) == textureLocations.end()) {
|
||||
facTex = nullptr;
|
||||
}
|
||||
if (facTex == nullptr && facProp.IsValid()) {
|
||||
factorVal = facProp.Get<FbxDouble>();
|
||||
}
|
||||
|
||||
auto val = FbxVector4(
|
||||
colorVal[0] * factorVal, colorVal[1] * factorVal, colorVal[2] * factorVal, factorVal);
|
||||
return std::make_tuple(val, colTex, facTex);
|
||||
};
|
||||
|
||||
std::string name = fbxMaterial->GetName();
|
||||
std::unique_ptr<FbxTraditionalMaterialInfo> res(new FbxTraditionalMaterialInfo(
|
||||
fbxMaterial->GetUniqueID(), name.c_str(), fbxMaterial->ShadingModel.Get()));
|
||||
|
||||
// four properties are on the same structure and follow the same rules
|
||||
auto handleBasicProperty = [&](const char* colName,
|
||||
const char* facName) -> std::tuple<FbxVector4, FbxFileTexture*> {
|
||||
FbxFileTexture *colTex, *facTex;
|
||||
FbxVector4 vec;
|
||||
|
||||
std::tie(vec, colTex, facTex) = getSurfaceValues(colName, facName);
|
||||
if (colTex) {
|
||||
if (facTex) {
|
||||
fmt::printf(
|
||||
"Warning: Mat [%s]: Can't handle both %s and %s textures; discarding %s.\n",
|
||||
name,
|
||||
colName,
|
||||
facName,
|
||||
facName);
|
||||
}
|
||||
return std::make_tuple(vec, colTex);
|
||||
}
|
||||
return std::make_tuple(vec, facTex);
|
||||
};
|
||||
|
||||
std::tie(res->colAmbient, res->texAmbient) =
|
||||
handleBasicProperty(FbxSurfaceMaterial::sAmbient, FbxSurfaceMaterial::sAmbientFactor);
|
||||
std::tie(res->colSpecular, res->texSpecular) =
|
||||
handleBasicProperty(FbxSurfaceMaterial::sSpecular, FbxSurfaceMaterial::sSpecularFactor);
|
||||
std::tie(res->colDiffuse, res->texDiffuse) =
|
||||
handleBasicProperty(FbxSurfaceMaterial::sDiffuse, FbxSurfaceMaterial::sDiffuseFactor);
|
||||
std::tie(res->colEmissive, res->texEmissive) =
|
||||
handleBasicProperty(FbxSurfaceMaterial::sEmissive, FbxSurfaceMaterial::sEmissiveFactor);
|
||||
|
||||
// the normal map can only ever be a map, ignore everything else
|
||||
tie(std::ignore, res->texNormal) = getSurfaceVector(FbxSurfaceMaterial::sNormalMap);
|
||||
|
||||
// shininess can be a map or a factor; afaict the map is always 'ShininessExponent' and the
|
||||
// value is always found in 'Shininess' but only sometimes in 'ShininessExponent'.
|
||||
tie(std::ignore, res->texShininess) = getSurfaceScalar("ShininessExponent");
|
||||
tie(res->shininess, std::ignore) = getSurfaceScalar("Shininess");
|
||||
|
||||
// for transparency we just want a constant vector value;
|
||||
FbxVector4 transparency;
|
||||
// extract any existing textures only so we can warn that we're throwing them away
|
||||
FbxFileTexture *colTex, *facTex;
|
||||
std::tie(transparency, colTex, facTex) = getSurfaceValues(
|
||||
FbxSurfaceMaterial::sTransparentColor, FbxSurfaceMaterial::sTransparencyFactor);
|
||||
if (colTex) {
|
||||
fmt::printf(
|
||||
"Warning: Mat [%s]: Can't handle texture for %s; discarding.\n",
|
||||
name,
|
||||
FbxSurfaceMaterial::sTransparentColor);
|
||||
}
|
||||
if (facTex) {
|
||||
fmt::printf(
|
||||
"Warning: Mat [%s]: Can't handle texture for %s; discarding.\n",
|
||||
name,
|
||||
FbxSurfaceMaterial::sTransparencyFactor);
|
||||
}
|
||||
// FBX color is RGB, so we calculate the A channel as the average of the FBX transparency color
|
||||
// vector
|
||||
res->colDiffuse[3] = 1.0 - (transparency[0] + transparency[1] + transparency[2]) / 3.0;
|
||||
|
||||
return res;
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include "FbxMaterials.hpp"
|
||||
|
||||
struct FbxTraditionalMaterialInfo : FbxMaterialInfo {
|
||||
static constexpr const char* FBX_SHADER_LAMBERT = "Lambert";
|
||||
static constexpr const char* FBX_SHADER_BLINN = "Blinn";
|
||||
static constexpr const char* FBX_SHADER_PHONG = "Phong";
|
||||
|
||||
FbxTraditionalMaterialInfo(
|
||||
const FbxUInt64 id,
|
||||
const FbxString& name,
|
||||
const FbxString& shadingModel)
|
||||
: FbxMaterialInfo(id, name, shadingModel) {}
|
||||
|
||||
FbxFileTexture* texAmbient{};
|
||||
FbxVector4 colAmbient{};
|
||||
FbxFileTexture* texSpecular{};
|
||||
FbxVector4 colSpecular{};
|
||||
FbxFileTexture* texDiffuse{};
|
||||
FbxVector4 colDiffuse{};
|
||||
FbxFileTexture* texEmissive{};
|
||||
FbxVector4 colEmissive{};
|
||||
FbxFileTexture* texNormal{};
|
||||
FbxFileTexture* texShininess{};
|
||||
FbxDouble shininess{};
|
||||
};
|
||||
|
||||
class FbxTraditionalMaterialResolver : FbxMaterialResolver<FbxTraditionalMaterialInfo> {
|
||||
public:
|
||||
FbxTraditionalMaterialResolver(
|
||||
FbxSurfaceMaterial* fbxMaterial,
|
||||
const std::map<const FbxTexture*, FbxString>& textureLocations)
|
||||
: FbxMaterialResolver(fbxMaterial, textureLocations) {}
|
||||
|
||||
virtual std::unique_ptr<FbxTraditionalMaterialInfo> resolve() const;
|
||||
};
|
|
@ -1,49 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#include "AccessorData.h"
|
||||
#include "BufferViewData.h"
|
||||
|
||||
AccessorData::AccessorData(const BufferViewData &bufferView, GLType type)
|
||||
: Holdable(),
|
||||
bufferView(bufferView.ix),
|
||||
type(std::move(type)),
|
||||
byteOffset(0),
|
||||
count(0)
|
||||
{
|
||||
}
|
||||
|
||||
AccessorData::AccessorData(GLType type)
|
||||
: Holdable(),
|
||||
bufferView(-1),
|
||||
type(std::move(type)),
|
||||
byteOffset(0),
|
||||
count(0)
|
||||
{
|
||||
}
|
||||
|
||||
json AccessorData::serialize() const
|
||||
{
|
||||
json result {
|
||||
{ "componentType", type.componentType.glType },
|
||||
{ "type", type.dataType },
|
||||
{ "count", count }
|
||||
};
|
||||
if (bufferView >= 0) {
|
||||
result["bufferView"] = bufferView;
|
||||
result["byteOffset"] = byteOffset;
|
||||
}
|
||||
if (!min.empty()) {
|
||||
result["min"] = min;
|
||||
}
|
||||
if (!max.empty()) {
|
||||
result["max"] = max;
|
||||
}
|
||||
return result;
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#ifndef FBX2GLTF_ACCESSORDATA_H
|
||||
#define FBX2GLTF_ACCESSORDATA_H
|
||||
|
||||
#include "Raw2Gltf.h"
|
||||
|
||||
struct AccessorData : Holdable
|
||||
{
|
||||
AccessorData(const BufferViewData &bufferView, GLType type);
|
||||
explicit AccessorData(GLType type);
|
||||
|
||||
json serialize() const override;
|
||||
|
||||
template<class T>
|
||||
void appendAsBinaryArray(const std::vector<T> &in, std::vector<uint8_t> &out)
|
||||
{
|
||||
const unsigned int stride = type.byteStride();
|
||||
const size_t offset = out.size();
|
||||
const size_t count = in.size();
|
||||
|
||||
this->count = (unsigned int) count;
|
||||
|
||||
out.resize(offset + count * stride);
|
||||
for (int ii = 0; ii < count; ii ++) {
|
||||
type.write(&out[offset + ii * stride], in[ii]);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int byteLength() const { return type.byteStride() * count; }
|
||||
|
||||
const int bufferView;
|
||||
const GLType type;
|
||||
|
||||
unsigned int byteOffset;
|
||||
unsigned int count;
|
||||
std::vector<float> min;
|
||||
std::vector<float> max;
|
||||
};
|
||||
|
||||
#endif //FBX2GLTF_ACCESSORDATA_H
|
|
@ -1,70 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#include "AnimationData.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "AccessorData.h"
|
||||
#include "NodeData.h"
|
||||
|
||||
AnimationData::AnimationData(std::string name, const AccessorData &timeAccessor)
|
||||
: Holdable(),
|
||||
name(std::move(name)),
|
||||
timeAccessor(timeAccessor.ix) {}
|
||||
|
||||
// assumption: 1-to-1 relationship between channels and samplers; this is a simplification on what
|
||||
// glTF can express, but it means we can rely on samplerIx == channelIx throughout an animation
|
||||
void AnimationData::AddNodeChannel(const NodeData &node, const AccessorData &accessor, std::string path)
|
||||
{
|
||||
assert(channels.size() == samplers.size());
|
||||
uint32_t ix = channels.size();
|
||||
channels.emplace_back(channel_t(ix, node, std::move(path)));
|
||||
samplers.emplace_back(sampler_t(timeAccessor, accessor.ix));
|
||||
}
|
||||
|
||||
json AnimationData::serialize() const
|
||||
{
|
||||
return {
|
||||
{ "name", name },
|
||||
{ "channels", channels },
|
||||
{ "samplers", samplers }
|
||||
};
|
||||
}
|
||||
|
||||
AnimationData::channel_t::channel_t(uint32_t ix, const NodeData &node, std::string path)
|
||||
: ix(ix),
|
||||
node(node.ix),
|
||||
path(std::move(path))
|
||||
{
|
||||
}
|
||||
|
||||
AnimationData::sampler_t::sampler_t(uint32_t time, uint32_t output)
|
||||
: time(time),
|
||||
output(output)
|
||||
{
|
||||
}
|
||||
|
||||
void to_json(json &j, const AnimationData::channel_t &data) {
|
||||
j = json {
|
||||
{ "sampler", data.ix },
|
||||
{ "target", {
|
||||
{ "node", data.node },
|
||||
{ "path", data.path }},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void to_json(json &j, const AnimationData::sampler_t &data) {
|
||||
j = json {
|
||||
{ "input", data.time },
|
||||
{ "interpolation", "LINEAR" },
|
||||
{ "output", data.output },
|
||||
};
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#ifndef FBX2GLTF_ANIMATIONDATA_H
|
||||
#define FBX2GLTF_ANIMATIONDATA_H
|
||||
|
||||
#include "Raw2Gltf.h"
|
||||
|
||||
struct AnimationData : Holdable
|
||||
{
|
||||
AnimationData(std::string name, const AccessorData &timeAccessor);
|
||||
|
||||
// assumption: 1-to-1 relationship between channels and samplers; this is a simplification on what
|
||||
// glTF can express, but it means we can rely on samplerIx == channelIx throughout an animation
|
||||
void AddNodeChannel(const NodeData &node, const AccessorData &accessor, std::string path);
|
||||
|
||||
json serialize() const override;
|
||||
|
||||
struct channel_t
|
||||
{
|
||||
channel_t(uint32_t _ix, const NodeData &node, std::string path);
|
||||
|
||||
const uint32_t ix;
|
||||
const uint32_t node;
|
||||
const std::string path;
|
||||
};
|
||||
|
||||
struct sampler_t
|
||||
{
|
||||
sampler_t(uint32_t time, uint32_t output);
|
||||
|
||||
const uint32_t time;
|
||||
const uint32_t output;
|
||||
};
|
||||
|
||||
const std::string name;
|
||||
const uint32_t timeAccessor;
|
||||
std::vector<channel_t> channels;
|
||||
std::vector<sampler_t> samplers;
|
||||
};
|
||||
|
||||
void to_json(json &j, const AnimationData::channel_t &data);
|
||||
void to_json(json &j, const AnimationData::sampler_t &data);
|
||||
|
||||
#endif //FBX2GLTF_ANIMATIONDATA_H
|
|
@ -1,43 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#include <cppcodec/base64_default_rfc4648.hpp>
|
||||
|
||||
#include "BufferData.h"
|
||||
|
||||
BufferData::BufferData(const std::shared_ptr<const std::vector<uint8_t> > &binData)
|
||||
: Holdable(),
|
||||
isGlb(true),
|
||||
binData(binData)
|
||||
{
|
||||
}
|
||||
|
||||
BufferData::BufferData(std::string uri, const std::shared_ptr<const std::vector<uint8_t> > &binData, bool isEmbedded)
|
||||
: Holdable(),
|
||||
isGlb(false),
|
||||
uri(isEmbedded ? "" : std::move(uri)),
|
||||
binData(binData)
|
||||
{
|
||||
}
|
||||
|
||||
json BufferData::serialize() const
|
||||
{
|
||||
json result{
|
||||
{"byteLength", binData->size()}
|
||||
};
|
||||
if (!isGlb) {
|
||||
if (!uri.empty()) {
|
||||
result["uri"] = uri;
|
||||
} else {
|
||||
std::string encoded = base64::encode(*binData);
|
||||
result["uri"] = "data:application/octet-stream;base64," + encoded;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#ifndef FBX2GLTF_BUFFERDATA_H
|
||||
#define FBX2GLTF_BUFFERDATA_H
|
||||
|
||||
#include "Raw2Gltf.h"
|
||||
|
||||
struct BufferData : Holdable
|
||||
{
|
||||
explicit BufferData(const std::shared_ptr<const std::vector<uint8_t> > &binData);
|
||||
|
||||
BufferData(std::string uri, const std::shared_ptr<const std::vector<uint8_t> > &binData, bool isEmbedded = false);
|
||||
|
||||
json serialize() const override;
|
||||
|
||||
const bool isGlb;
|
||||
const std::string uri;
|
||||
const std::shared_ptr<const std::vector<uint8_t> > binData; // TODO this is just weird
|
||||
};
|
||||
|
||||
|
||||
#endif //FBX2GLTF_BUFFERDATA_H
|
|
@ -1,32 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#include "BufferViewData.h"
|
||||
#include "BufferData.h"
|
||||
|
||||
BufferViewData::BufferViewData(const BufferData &_buffer, const size_t _byteOffset, const GL_ArrayType _target)
|
||||
: Holdable(),
|
||||
buffer(_buffer.ix),
|
||||
byteOffset((unsigned int) _byteOffset),
|
||||
target(_target)
|
||||
{
|
||||
}
|
||||
|
||||
json BufferViewData::serialize() const
|
||||
{
|
||||
json result {
|
||||
{ "buffer", buffer },
|
||||
{ "byteLength", byteLength },
|
||||
{ "byteOffset", byteOffset }
|
||||
};
|
||||
if (target != GL_ARRAY_NONE) {
|
||||
result["target"] = target;
|
||||
}
|
||||
return result;
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#ifndef FBX2GLTF_BUFFERVIEW_H
|
||||
#define FBX2GLTF_BUFFERVIEW_H
|
||||
|
||||
#include "Raw2Gltf.h"
|
||||
|
||||
struct BufferViewData : Holdable
|
||||
{
|
||||
enum GL_ArrayType
|
||||
{
|
||||
GL_ARRAY_NONE = 0, // no GL buffer is being set
|
||||
GL_ARRAY_BUFFER = 34962,
|
||||
GL_ELEMENT_ARRAY_BUFFER = 34963
|
||||
};
|
||||
|
||||
BufferViewData(const BufferData &_buffer, const size_t _byteOffset, const GL_ArrayType _target);
|
||||
|
||||
json serialize() const override;
|
||||
|
||||
const unsigned int buffer;
|
||||
const unsigned int byteOffset;
|
||||
const GL_ArrayType target;
|
||||
|
||||
unsigned int byteLength = 0;
|
||||
};
|
||||
|
||||
#endif //FBX2GLTF_BUFFERVIEW_H
|
|
@ -1,42 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#include "CameraData.h"
|
||||
|
||||
CameraData::CameraData()
|
||||
: Holdable(),
|
||||
aspectRatio(0.0f),
|
||||
yfov(0.0f),
|
||||
xmag(0.0f),
|
||||
ymag(0.0f),
|
||||
znear(0.0f),
|
||||
zfar(0.0f)
|
||||
{
|
||||
}
|
||||
|
||||
json CameraData::serialize() const
|
||||
{
|
||||
json result {
|
||||
{ "name", name },
|
||||
{ "type", type },
|
||||
};
|
||||
json subResult {
|
||||
{ "znear", znear },
|
||||
{ "zfar", zfar }
|
||||
};
|
||||
if (type == "perspective") {
|
||||
subResult["aspectRatio"] = aspectRatio;
|
||||
subResult["yfov"] = yfov;
|
||||
} else {
|
||||
subResult["xmag"] = xmag;
|
||||
subResult["ymag"] = ymag;
|
||||
}
|
||||
result[type] = subResult;
|
||||
return result;
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#ifndef FBX2GLTF_CAMERADATA_H
|
||||
#define FBX2GLTF_CAMERADATA_H
|
||||
|
||||
#include "Raw2Gltf.h"
|
||||
|
||||
// TODO: this class needs some work
|
||||
struct CameraData : Holdable
|
||||
{
|
||||
CameraData();
|
||||
json serialize() const override;
|
||||
|
||||
std::string name;
|
||||
std::string type;
|
||||
float aspectRatio;
|
||||
float yfov;
|
||||
float xmag;
|
||||
float ymag;
|
||||
float znear;
|
||||
float zfar;
|
||||
};
|
||||
|
||||
#endif //FBX2GLTF_CAMERADATA_H
|
|
@ -1,44 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#include "ImageData.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "BufferViewData.h"
|
||||
|
||||
ImageData::ImageData(std::string name, std::string uri)
|
||||
: Holdable(),
|
||||
name(std::move(name)),
|
||||
uri(std::move(uri)),
|
||||
bufferView(-1)
|
||||
{
|
||||
}
|
||||
|
||||
ImageData::ImageData(std::string name, const BufferViewData &bufferView, std::string mimeType)
|
||||
: Holdable(),
|
||||
name(std::move(name)),
|
||||
bufferView(bufferView.ix),
|
||||
mimeType(std::move(mimeType))
|
||||
{
|
||||
}
|
||||
|
||||
json ImageData::serialize() const
|
||||
{
|
||||
if (mimeType.empty()) {
|
||||
return {
|
||||
{ "uri", uri }
|
||||
};
|
||||
}
|
||||
return {
|
||||
{ "name", name },
|
||||
{ "bufferView", bufferView },
|
||||
{ "mimeType", mimeType }
|
||||
};
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#ifndef FBX2GLTF_IMAGEDATA_H
|
||||
#define FBX2GLTF_IMAGEDATA_H
|
||||
|
||||
#include "Raw2Gltf.h"
|
||||
|
||||
struct ImageData : Holdable
|
||||
{
|
||||
ImageData(std::string name, std::string uri);
|
||||
ImageData(std::string name, const BufferViewData &bufferView, std::string mimeType);
|
||||
|
||||
json serialize() const override;
|
||||
|
||||
const std::string name;
|
||||
const std::string uri; // non-empty in gltf mode
|
||||
const int32_t bufferView; // non-negative in glb mode
|
||||
const std::string mimeType;
|
||||
};
|
||||
|
||||
#endif //FBX2GLTF_IMAGEDATA_H
|
|
@ -1,202 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#include "MaterialData.h"
|
||||
#include "TextureData.h"
|
||||
|
||||
// TODO: retrieve & pass in correct UV set from FBX
|
||||
std::unique_ptr<Tex> Tex::ref(const TextureData *tex, uint32_t texCoord)
|
||||
{
|
||||
return std::unique_ptr<Tex> { (tex != nullptr) ? new Tex(tex->ix, texCoord) : nullptr };
|
||||
}
|
||||
|
||||
Tex::Tex(uint32_t texRef, uint32_t texCoord)
|
||||
: texRef(texRef),
|
||||
texCoord(texCoord) {}
|
||||
|
||||
void to_json(json &j, const Tex &data) {
|
||||
j = json {
|
||||
{ "index", data.texRef },
|
||||
{ "texCoord", data.texCoord }
|
||||
};
|
||||
}
|
||||
|
||||
static inline Vec4f toRGBA(const Vec3f &colour, float alpha = 1) {
|
||||
return { colour[0], colour[1], colour[2], alpha };
|
||||
};
|
||||
|
||||
KHRCommonMats::KHRCommonMats(
|
||||
MaterialType type,
|
||||
const TextureData *shininessTexture, float shininess,
|
||||
const TextureData *ambientTexture, const Vec3f &ambientFactor,
|
||||
const TextureData *diffuseTexture, const Vec4f &diffuseFactor,
|
||||
const TextureData *specularTexture, const Vec3f &specularFactor)
|
||||
: type(type),
|
||||
shininessTexture(Tex::ref(shininessTexture)),
|
||||
shininess(shininess),
|
||||
ambientTexture(Tex::ref(ambientTexture)),
|
||||
ambientFactor(ambientFactor),
|
||||
diffuseTexture(Tex::ref(diffuseTexture)),
|
||||
diffuseFactor(diffuseFactor),
|
||||
specularTexture(Tex::ref(specularTexture)),
|
||||
specularFactor(specularFactor)
|
||||
{
|
||||
}
|
||||
|
||||
std::string KHRCommonMats::typeDesc(MaterialType type)
|
||||
{
|
||||
switch (type) {
|
||||
case Blinn:
|
||||
return "commonBlinn";
|
||||
case Phong:
|
||||
return "commonPhong";
|
||||
case Lambert:
|
||||
return "commonLambert";
|
||||
case Constant:
|
||||
return "commonConstant";
|
||||
}
|
||||
}
|
||||
|
||||
void to_json(json &j, const KHRCommonMats &d)
|
||||
{
|
||||
j = {{"type", KHRCommonMats::typeDesc(d.type)}};
|
||||
|
||||
if (d.shininessTexture != nullptr) {
|
||||
j["shininessTexture"] = *d.shininessTexture;
|
||||
}
|
||||
if (d.shininess != 0) {
|
||||
j["shininess"] = d.shininess;
|
||||
}
|
||||
if (d.ambientTexture != nullptr) {
|
||||
j["ambientTexture"] = *d.ambientTexture;
|
||||
}
|
||||
if (d.ambientFactor.LengthSquared() > 0) {
|
||||
j["ambientFactor"] = toStdVec(toRGBA(d.ambientFactor));
|
||||
}
|
||||
if (d.diffuseTexture != nullptr) {
|
||||
j["diffuseTexture"] = *d.diffuseTexture;
|
||||
}
|
||||
if (d.diffuseFactor.LengthSquared() > 0) {
|
||||
j["diffuseFactor"] = toStdVec(d.diffuseFactor);
|
||||
}
|
||||
if (d.specularTexture != nullptr) {
|
||||
j["specularTexture"] = *d.specularTexture;
|
||||
}
|
||||
if (d.specularFactor.LengthSquared() > 0) {
|
||||
j["specularFactor"] = toStdVec(toRGBA(d.specularFactor));
|
||||
}
|
||||
}
|
||||
|
||||
PBRSpecularGlossiness::PBRSpecularGlossiness(
|
||||
const TextureData *diffuseTexture, const Vec4f &diffuseFactor,
|
||||
const TextureData *specularGlossinessTexture, const Vec3f &specularFactor,
|
||||
float glossinessFactor)
|
||||
: diffuseTexture(Tex::ref(diffuseTexture)),
|
||||
diffuseFactor(diffuseFactor),
|
||||
specularGlossinessTexture(Tex::ref(specularGlossinessTexture)),
|
||||
specularFactor(specularFactor),
|
||||
glossinessFactor(glossinessFactor)
|
||||
{
|
||||
}
|
||||
|
||||
void to_json(json &j, const PBRSpecularGlossiness &d)
|
||||
{
|
||||
j = {};
|
||||
if (d.diffuseTexture != nullptr) {
|
||||
j["diffuseTexture"] = *d.diffuseTexture;
|
||||
}
|
||||
if (d.diffuseFactor.LengthSquared() > 0) {
|
||||
j["diffuseFactor"] = toStdVec(d.diffuseFactor);
|
||||
}
|
||||
if (d.specularGlossinessTexture != nullptr) {
|
||||
j["specularGlossinessTexture"] = *d.specularGlossinessTexture;
|
||||
}
|
||||
if (d.specularFactor.LengthSquared() > 0) {
|
||||
j["specularFactor"] = toStdVec(d.specularFactor);
|
||||
}
|
||||
if (d.glossinessFactor != 0) {
|
||||
j["glossinessFactor"] = d.glossinessFactor;
|
||||
}
|
||||
}
|
||||
|
||||
PBRMetallicRoughness::PBRMetallicRoughness(
|
||||
const TextureData *baseColorTexture, const Vec4f &baseolorFactor,
|
||||
float metallic, float roughness)
|
||||
: baseColorTexture(Tex::ref(baseColorTexture)),
|
||||
baseColorFactor(baseolorFactor),
|
||||
metallic(metallic),
|
||||
roughness(roughness)
|
||||
{
|
||||
}
|
||||
|
||||
void to_json(json &j, const PBRMetallicRoughness &d)
|
||||
{
|
||||
j = { };
|
||||
if (d.baseColorTexture != nullptr) {
|
||||
j["baseColorTexture"] = *d.baseColorTexture;
|
||||
}
|
||||
if (d.baseColorFactor.LengthSquared() > 0) {
|
||||
j["baseColorFactor"] = toStdVec(d.baseColorFactor);
|
||||
}
|
||||
if (d.metallic != 1.0f) {
|
||||
j["metallicFactor"] = d.metallic;
|
||||
}
|
||||
if (d.roughness != 1.0f) {
|
||||
j["roughnessFactor"] = d.roughness;
|
||||
}
|
||||
}
|
||||
|
||||
MaterialData::MaterialData(
|
||||
std::string name, bool isTransparent, const TextureData *normalTexture,
|
||||
const TextureData *emissiveTexture, const Vec3f & emissiveFactor,
|
||||
std::shared_ptr<KHRCommonMats> const khrCommonMats,
|
||||
std::shared_ptr<PBRMetallicRoughness> const pbrMetallicRoughness,
|
||||
std::shared_ptr<PBRSpecularGlossiness> const pbrSpecularGlossiness)
|
||||
: Holdable(),
|
||||
name(std::move(name)),
|
||||
isTransparent(isTransparent),
|
||||
normalTexture(Tex::ref(normalTexture)),
|
||||
emissiveTexture(Tex::ref(emissiveTexture)),
|
||||
emissiveFactor(std::move(emissiveFactor)),
|
||||
khrCommonMats(khrCommonMats),
|
||||
pbrMetallicRoughness(pbrMetallicRoughness),
|
||||
pbrSpecularGlossiness(pbrSpecularGlossiness) {}
|
||||
|
||||
json MaterialData::serialize() const
|
||||
{
|
||||
json result = {
|
||||
{ "name", name },
|
||||
{ "alphaMode", isTransparent ? "BLEND" : "OPAQUE" }
|
||||
};
|
||||
|
||||
if (normalTexture != nullptr) {
|
||||
result["normalTexture"] = *normalTexture;
|
||||
}
|
||||
if (emissiveTexture != nullptr) {
|
||||
result["emissiveTexture"] = *emissiveTexture;
|
||||
}
|
||||
if (emissiveFactor.LengthSquared() > 0) {
|
||||
result["emissiveFactor"] = toStdVec(emissiveFactor);
|
||||
}
|
||||
if (pbrMetallicRoughness != nullptr) {
|
||||
result["pbrMetallicRoughness"] = *pbrMetallicRoughness;
|
||||
}
|
||||
if (khrCommonMats != nullptr || pbrSpecularGlossiness != nullptr) {
|
||||
json extensions = { };
|
||||
if (khrCommonMats != nullptr) {
|
||||
extensions[KHR_MATERIALS_COMMON] = *khrCommonMats;
|
||||
}
|
||||
if (pbrSpecularGlossiness != nullptr) {
|
||||
extensions[KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS] = *pbrSpecularGlossiness;
|
||||
}
|
||||
|
||||
result["extensions"] = extensions;
|
||||
}
|
||||
return result;
|
||||
}
|
|
@ -1,109 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#ifndef FBX2GLTF_MATERIALDATA_H
|
||||
#define FBX2GLTF_MATERIALDATA_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "Raw2Gltf.h"
|
||||
|
||||
struct Tex
|
||||
{
|
||||
static std::unique_ptr<Tex> ref(const TextureData *tex, uint32_t texCoord = 0);
|
||||
explicit Tex(uint32_t texRef, uint32_t texCoord);
|
||||
|
||||
const uint32_t texRef;
|
||||
const uint32_t texCoord;
|
||||
};
|
||||
|
||||
struct KHRCommonMats
|
||||
{
|
||||
enum MaterialType
|
||||
{
|
||||
Blinn,
|
||||
Phong,
|
||||
Lambert,
|
||||
Constant,
|
||||
};
|
||||
|
||||
KHRCommonMats(
|
||||
MaterialType type,
|
||||
const TextureData *shininessTexture, float shininess,
|
||||
const TextureData *ambientTexture, const Vec3f &ambientFactor,
|
||||
const TextureData *diffuseTexture, const Vec4f &diffuseFactor,
|
||||
const TextureData *specularTexture, const Vec3f &specularFactor);
|
||||
|
||||
static std::string typeDesc(MaterialType type);
|
||||
|
||||
const MaterialType type;
|
||||
const std::unique_ptr<Tex> shininessTexture;
|
||||
const float shininess;
|
||||
const std::unique_ptr<Tex> ambientTexture;
|
||||
const Vec3f ambientFactor;
|
||||
const std::unique_ptr<Tex> diffuseTexture;
|
||||
const Vec4f diffuseFactor;
|
||||
const std::unique_ptr<Tex> specularTexture;
|
||||
const Vec3f specularFactor;
|
||||
};
|
||||
|
||||
struct PBRSpecularGlossiness
|
||||
{
|
||||
PBRSpecularGlossiness(
|
||||
const TextureData *diffuseTexture, const Vec4f &diffuseFactor,
|
||||
const TextureData *specularGlossinessTexture,
|
||||
const Vec3f &specularFactor, float glossinessFactor);
|
||||
|
||||
std::unique_ptr<Tex> diffuseTexture;
|
||||
const Vec4f diffuseFactor;
|
||||
std::unique_ptr<Tex> specularGlossinessTexture;
|
||||
const Vec3f specularFactor;
|
||||
const float glossinessFactor;
|
||||
};
|
||||
|
||||
struct PBRMetallicRoughness
|
||||
{
|
||||
PBRMetallicRoughness(
|
||||
const TextureData *baseColorTexture, const Vec4f &baseolorFactor,
|
||||
float metallic = 0.1f, float roughness = 0.4f);
|
||||
|
||||
std::unique_ptr<Tex> baseColorTexture;
|
||||
const Vec4f baseColorFactor;
|
||||
const float metallic;
|
||||
const float roughness;
|
||||
};
|
||||
|
||||
struct MaterialData : Holdable
|
||||
{
|
||||
MaterialData(
|
||||
std::string name, bool isTransparent, const TextureData *normalTexture,
|
||||
const TextureData *emissiveTexture, const Vec3f &emissiveFactor,
|
||||
std::shared_ptr<KHRCommonMats> const khrCommonMats,
|
||||
std::shared_ptr<PBRMetallicRoughness> const pbrMetallicRoughness,
|
||||
std::shared_ptr<PBRSpecularGlossiness> const pbrSpecularGlossiness);
|
||||
|
||||
json serialize() const override;
|
||||
|
||||
const std::string name;
|
||||
const bool isTransparent;
|
||||
const std::unique_ptr<const Tex> normalTexture;
|
||||
const std::unique_ptr<const Tex> emissiveTexture;
|
||||
const Vec3f emissiveFactor;
|
||||
|
||||
const std::shared_ptr<const KHRCommonMats> khrCommonMats;
|
||||
const std::shared_ptr<const PBRMetallicRoughness> pbrMetallicRoughness;
|
||||
const std::shared_ptr<const PBRSpecularGlossiness> pbrSpecularGlossiness;
|
||||
};
|
||||
|
||||
void to_json(json &j, const Tex &data);
|
||||
void to_json(json &j, const KHRCommonMats &d);
|
||||
void to_json(json &j, const PBRSpecularGlossiness &d);
|
||||
void to_json(json &j, const PBRMetallicRoughness &d);
|
||||
|
||||
#endif //FBX2GLTF_MATERIALDATA_H
|
|
@ -1,29 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#include "MeshData.h"
|
||||
#include "PrimitiveData.h"
|
||||
|
||||
MeshData::MeshData(std::string name)
|
||||
: Holdable(),
|
||||
name(std::move(name))
|
||||
{
|
||||
}
|
||||
|
||||
json MeshData::serialize() const
|
||||
{
|
||||
json jsonPrimitivesArray = json::array();
|
||||
for (const auto &primitive : primitives) {
|
||||
jsonPrimitivesArray.push_back(*primitive);
|
||||
}
|
||||
return {
|
||||
{ "name", name },
|
||||
{ "primitives", jsonPrimitivesArray }
|
||||
};
|
||||
}
|
|
@ -1,36 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#ifndef FBX2GLTF_MESHDATA_H
|
||||
#define FBX2GLTF_MESHDATA_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <draco/compression/encode.h>
|
||||
|
||||
#include "Raw2Gltf.h"
|
||||
|
||||
#include "PrimitiveData.h"
|
||||
|
||||
struct MeshData : Holdable
|
||||
{
|
||||
explicit MeshData(std::string name);
|
||||
|
||||
void AddPrimitive(std::shared_ptr<PrimitiveData> primitive)
|
||||
{
|
||||
primitives.push_back(std::move(primitive));
|
||||
}
|
||||
|
||||
json serialize() const override;
|
||||
|
||||
const std::string name;
|
||||
std::vector<std::shared_ptr<PrimitiveData>> primitives;
|
||||
};
|
||||
|
||||
#endif //FBX2GLTF_MESHDATA_H
|
|
@ -1,83 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#include "NodeData.h"
|
||||
|
||||
NodeData::NodeData(
|
||||
std::string name, const Vec3f &translation,
|
||||
const Quatf &rotation, const Vec3f &scale, bool isJoint)
|
||||
: Holdable(),
|
||||
name(std::move(name)),
|
||||
isJoint(isJoint),
|
||||
translation(translation),
|
||||
rotation(rotation),
|
||||
scale(scale),
|
||||
children(),
|
||||
mesh(-1),
|
||||
cameraName(""),
|
||||
skin(-1)
|
||||
{
|
||||
}
|
||||
|
||||
void NodeData::AddChildNode(uint32_t childIx)
|
||||
{
|
||||
children.push_back(childIx);
|
||||
}
|
||||
|
||||
void NodeData::SetMesh(uint32_t meshIx)
|
||||
{
|
||||
assert(mesh < 0);
|
||||
assert(!isJoint);
|
||||
mesh = meshIx;
|
||||
}
|
||||
|
||||
void NodeData::SetSkin(uint32_t skinIx)
|
||||
{
|
||||
assert(skin < 0);
|
||||
assert(!isJoint);
|
||||
skin = skinIx;
|
||||
}
|
||||
|
||||
void NodeData::AddCamera(std::string camera)
|
||||
{
|
||||
assert(!isJoint);
|
||||
cameraName = std::move(camera);
|
||||
}
|
||||
|
||||
json NodeData::serialize() const
|
||||
{
|
||||
json result = {
|
||||
{ "name", name },
|
||||
{ "translation", toStdVec(translation) },
|
||||
{ "rotation", toStdVec(rotation) },
|
||||
{ "scale", toStdVec(scale) }
|
||||
};
|
||||
if (!children.empty()) {
|
||||
result["children"] = children;
|
||||
}
|
||||
if (isJoint) {
|
||||
// sanity-check joint node
|
||||
assert(mesh < 0 && skin < 0);
|
||||
} else {
|
||||
// non-joint node
|
||||
if (mesh >= 0) {
|
||||
result["mesh"] = mesh;
|
||||
}
|
||||
if (!skeletons.empty()) {
|
||||
result["skeletons"] = skeletons;
|
||||
}
|
||||
if (skin >= 0) {
|
||||
result["skin"] = skin;
|
||||
}
|
||||
if (!cameraName.empty()) {
|
||||
result["camera"] = cameraName;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#ifndef FBX2GLTF_NODEDATA_H
|
||||
#define FBX2GLTF_NODEDATA_H
|
||||
|
||||
#include "Raw2Gltf.h"
|
||||
|
||||
struct NodeData : Holdable
|
||||
{
|
||||
NodeData(std::string name, const Vec3f &translation, const Quatf &rotation, const Vec3f &scale, bool isJoint);
|
||||
|
||||
void AddChildNode(uint32_t childIx);
|
||||
void SetMesh(uint32_t meshIx);
|
||||
void SetSkin(uint32_t skinIx);
|
||||
void AddCamera(std::string camera);
|
||||
|
||||
json serialize() const override;
|
||||
|
||||
const std::string name;
|
||||
const bool isJoint;
|
||||
Vec3f translation;
|
||||
Quatf rotation;
|
||||
Vec3f scale;
|
||||
std::vector<uint32_t> children;
|
||||
int32_t mesh;
|
||||
std::string cameraName;
|
||||
int32_t skin;
|
||||
std::vector<std::string> skeletons;
|
||||
};
|
||||
|
||||
#endif //FBX2GLTF_NODEDATA_H
|
|
@ -1,60 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#include "PrimitiveData.h"
|
||||
|
||||
#include "MaterialData.h"
|
||||
#include "AccessorData.h"
|
||||
#include "BufferViewData.h"
|
||||
|
||||
PrimitiveData::PrimitiveData(const AccessorData &indices, const MaterialData &material, std::shared_ptr<draco::Mesh> dracoMesh)
|
||||
: indices(indices.ix),
|
||||
material(material.ix),
|
||||
mode(TRIANGLES),
|
||||
dracoMesh(dracoMesh),
|
||||
dracoBufferView(-1) {}
|
||||
|
||||
PrimitiveData::PrimitiveData(const AccessorData &indices, const MaterialData &material)
|
||||
: indices(indices.ix),
|
||||
material(material.ix),
|
||||
mode(TRIANGLES),
|
||||
dracoMesh(nullptr),
|
||||
dracoBufferView(-1)
|
||||
{
|
||||
}
|
||||
|
||||
void PrimitiveData::AddAttrib(std::string name, const AccessorData &accessor)
|
||||
{
|
||||
attributes[name] = accessor.ix;
|
||||
}
|
||||
|
||||
void PrimitiveData::NoteDracoBuffer(const BufferViewData &data)
|
||||
{
|
||||
dracoBufferView = data.ix;
|
||||
}
|
||||
|
||||
void to_json(json &j, const PrimitiveData &d) {
|
||||
j = {
|
||||
{ "material", d.material },
|
||||
{ "mode", d.mode },
|
||||
{ "attributes", d.attributes }
|
||||
};
|
||||
if (d.indices >= 0) {
|
||||
j["indices"] = d.indices;
|
||||
}
|
||||
|
||||
if (!d.dracoAttributes.empty()) {
|
||||
j["extensions"] = {
|
||||
{ KHR_DRACO_MESH_COMPRESSION, {
|
||||
{ "bufferView", d.dracoBufferView },
|
||||
{ "attributes", d.dracoAttributes }
|
||||
}}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#ifndef FBX2GLTF_PRIMITIVEDATA_H
|
||||
#define FBX2GLTF_PRIMITIVEDATA_H
|
||||
|
||||
#include "Raw2Gltf.h"
|
||||
|
||||
struct PrimitiveData
|
||||
{
|
||||
enum MeshMode
|
||||
{
|
||||
POINTS = 0,
|
||||
LINES,
|
||||
LINE_LOOP,
|
||||
LINE_STRIP,
|
||||
TRIANGLES,
|
||||
TRIANGLE_STRIP,
|
||||
TRIANGLE_FAN
|
||||
};
|
||||
|
||||
PrimitiveData(const AccessorData &indices, const MaterialData &material, std::shared_ptr<draco::Mesh> dracoMesh);
|
||||
|
||||
PrimitiveData(const AccessorData &indices, const MaterialData &material);
|
||||
|
||||
void AddAttrib(std::string name, const AccessorData &accessor);
|
||||
|
||||
template<class T>
|
||||
void AddDracoAttrib(const AttributeDefinition<T> attribute, const std::vector<T> &attribArr)
|
||||
{
|
||||
draco::PointAttribute att;
|
||||
int8_t componentCount = attribute.glType.count;
|
||||
att.Init(
|
||||
attribute.dracoAttribute, nullptr, componentCount, attribute.dracoComponentType,
|
||||
false, componentCount * draco::DataTypeLength(attribute.dracoComponentType), 0);
|
||||
|
||||
const int dracoAttId = dracoMesh->AddAttribute(att, true, attribArr.size());
|
||||
draco::PointAttribute *attPtr = dracoMesh->attribute(dracoAttId);
|
||||
|
||||
std::vector<uint8_t> buf(sizeof(T));
|
||||
for (uint32_t ii = 0; ii < attribArr.size(); ii++) {
|
||||
uint8_t *ptr = &buf[0];
|
||||
attribute.glType.write(ptr, attribArr[ii]);
|
||||
attPtr->SetAttributeValue(attPtr->mapped_index(draco::PointIndex(ii)), ptr);
|
||||
}
|
||||
|
||||
dracoAttributes[attribute.gltfName] = dracoAttId;
|
||||
}
|
||||
|
||||
void NoteDracoBuffer(const BufferViewData &data);
|
||||
|
||||
const int indices;
|
||||
const unsigned int material;
|
||||
const MeshMode mode;
|
||||
|
||||
std::map<std::string, int> attributes;
|
||||
std::map<std::string, int> dracoAttributes;
|
||||
|
||||
std::shared_ptr<draco::Mesh> dracoMesh;
|
||||
int dracoBufferView;
|
||||
};
|
||||
|
||||
void to_json(json &j, const PrimitiveData &d);
|
||||
|
||||
#endif //FBX2GLTF_PRIMITIVEDATA_H
|
|
@ -1,28 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#ifndef FBX2GLTF_SAMPLERDATA_H
|
||||
#define FBX2GLTF_SAMPLERDATA_H
|
||||
|
||||
#include "Raw2Gltf.h"
|
||||
|
||||
struct SamplerData : Holdable
|
||||
{
|
||||
// this is where magFilter, minFilter, wrapS and wrapT would go, should we want it
|
||||
SamplerData()
|
||||
: Holdable()
|
||||
{
|
||||
}
|
||||
|
||||
json serialize() const {
|
||||
return json::object();
|
||||
}
|
||||
};
|
||||
|
||||
#endif //FBX2GLTF_SAMPLERDATA_H
|
|
@ -1,28 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#include "SceneData.h"
|
||||
|
||||
#include "NodeData.h"
|
||||
|
||||
SceneData::SceneData(std::string name, const NodeData &rootNode)
|
||||
: Holdable(),
|
||||
name(std::move(name)),
|
||||
nodes({rootNode.ix})
|
||||
{
|
||||
}
|
||||
|
||||
json SceneData::serialize() const
|
||||
{
|
||||
assert(nodes.size() <= 1);
|
||||
return {
|
||||
{ "name", name },
|
||||
{ "nodes", nodes }
|
||||
};
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#ifndef FBX2GLTF_SCENEDATA_H
|
||||
#define FBX2GLTF_SCENEDATA_H
|
||||
|
||||
#include "Raw2Gltf.h"
|
||||
|
||||
struct SceneData : Holdable
|
||||
{
|
||||
SceneData(std::string name, const NodeData &rootNode);
|
||||
|
||||
json serialize() const override;
|
||||
|
||||
const std::string name;
|
||||
std::vector<uint32_t> nodes;
|
||||
};
|
||||
|
||||
#endif //FBX2GLTF_SCENEDATA_H
|
|
@ -1,32 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#include "SkinData.h"
|
||||
|
||||
#include "AccessorData.h"
|
||||
#include "NodeData.h"
|
||||
|
||||
SkinData::SkinData(
|
||||
const std::vector<uint32_t> joints, const AccessorData &inverseBindMatricesAccessor,
|
||||
const NodeData &skeletonRootNode)
|
||||
: Holdable(),
|
||||
joints(joints),
|
||||
inverseBindMatrices(inverseBindMatricesAccessor.ix),
|
||||
skeletonRootNode(skeletonRootNode.ix)
|
||||
{
|
||||
}
|
||||
|
||||
json SkinData::serialize() const
|
||||
{
|
||||
return {
|
||||
{ "joints", joints },
|
||||
{ "inverseBindMatrices", inverseBindMatrices },
|
||||
{ "skeleton", skeletonRootNode }
|
||||
};
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#ifndef FBX2GLTF_SKINDATA_H
|
||||
#define FBX2GLTF_SKINDATA_H
|
||||
|
||||
#include "Raw2Gltf.h"
|
||||
|
||||
struct SkinData : Holdable
|
||||
{
|
||||
SkinData(
|
||||
const std::vector<uint32_t> joints, const AccessorData &inverseBindMatricesAccessor,
|
||||
const NodeData &skeletonRootNode);
|
||||
|
||||
json serialize() const override;
|
||||
|
||||
const std::vector<uint32_t> joints;
|
||||
const uint32_t skeletonRootNode;
|
||||
const uint32_t inverseBindMatrices;
|
||||
};
|
||||
|
||||
#endif //FBX2GLTF_SKINDATA_H
|
|
@ -1,30 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#include "TextureData.h"
|
||||
|
||||
#include "ImageData.h"
|
||||
#include "SamplerData.h"
|
||||
|
||||
TextureData::TextureData(std::string name, const SamplerData &sampler, const ImageData &source)
|
||||
: Holdable(),
|
||||
name(std::move(name)),
|
||||
sampler(sampler.ix),
|
||||
source(source.ix)
|
||||
{
|
||||
}
|
||||
|
||||
json TextureData::serialize() const
|
||||
{
|
||||
return {
|
||||
{ "name", name },
|
||||
{ "sampler", sampler },
|
||||
{ "source", source }
|
||||
};
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#ifndef FBX2GLTF_TEXTUREDATA_H
|
||||
#define FBX2GLTF_TEXTUREDATA_H
|
||||
|
||||
#include "Raw2Gltf.h"
|
||||
|
||||
struct TextureData : Holdable
|
||||
{
|
||||
TextureData(std::string name, const SamplerData &sampler, const ImageData &source);
|
||||
|
||||
json serialize() const override;
|
||||
|
||||
const std::string name;
|
||||
const uint32_t sampler;
|
||||
const uint32_t source;
|
||||
};
|
||||
|
||||
#endif //FBX2GLTF_TEXTUREDATA_H
|
|
@ -0,0 +1,85 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include "GltfModel.hpp"
|
||||
|
||||
std::shared_ptr<BufferViewData> GltfModel::GetAlignedBufferView(
|
||||
BufferData& buffer,
|
||||
const BufferViewData::GL_ArrayType target) {
|
||||
uint32_t bufferSize = to_uint32(this->binary->size());
|
||||
if ((bufferSize % 4) > 0) {
|
||||
bufferSize += (4 - (bufferSize % 4));
|
||||
this->binary->resize(bufferSize);
|
||||
}
|
||||
return this->bufferViews.hold(new BufferViewData(buffer, bufferSize, target));
|
||||
}
|
||||
|
||||
// add a bufferview on the fly and copy data into it
|
||||
std::shared_ptr<BufferViewData>
|
||||
GltfModel::AddRawBufferView(BufferData& buffer, const char* source, uint32_t bytes) {
|
||||
auto bufferView = GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_NONE);
|
||||
bufferView->byteLength = bytes;
|
||||
|
||||
// make space for the new bytes (possibly moving the underlying data)
|
||||
uint32_t bufferSize = to_uint32(this->binary->size());
|
||||
this->binary->resize(bufferSize + bytes);
|
||||
|
||||
// and copy them into place
|
||||
memcpy(&(*this->binary)[bufferSize], source, bytes);
|
||||
return bufferView;
|
||||
}
|
||||
|
||||
std::shared_ptr<BufferViewData> GltfModel::AddBufferViewForFile(
|
||||
BufferData& buffer,
|
||||
const std::string& filename) {
|
||||
// see if we've already created a BufferViewData for this precise file
|
||||
auto iter = filenameToBufferView.find(filename);
|
||||
if (iter != filenameToBufferView.end()) {
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
std::shared_ptr<BufferViewData> result;
|
||||
std::ifstream file(filename, std::ios::binary | std::ios::ate);
|
||||
if (file) {
|
||||
std::streamsize size = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
std::vector<char> fileBuffer(size);
|
||||
if (file.read(fileBuffer.data(), size)) {
|
||||
result = AddRawBufferView(buffer, fileBuffer.data(), to_uint32(size));
|
||||
} else {
|
||||
fmt::printf("Warning: Couldn't read %lu bytes from %s, skipping file.\n", size, filename);
|
||||
}
|
||||
} else {
|
||||
fmt::printf("Warning: Couldn't open file %s, skipping file.\n", filename);
|
||||
}
|
||||
// note that we persist here not only success, but also failure, as nullptr
|
||||
filenameToBufferView[filename] = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
void GltfModel::serializeHolders(json& glTFJson) {
|
||||
serializeHolder(glTFJson, "buffers", buffers);
|
||||
serializeHolder(glTFJson, "bufferViews", bufferViews);
|
||||
serializeHolder(glTFJson, "scenes", scenes);
|
||||
serializeHolder(glTFJson, "accessors", accessors);
|
||||
serializeHolder(glTFJson, "images", images);
|
||||
serializeHolder(glTFJson, "samplers", samplers);
|
||||
serializeHolder(glTFJson, "textures", textures);
|
||||
serializeHolder(glTFJson, "materials", materials);
|
||||
serializeHolder(glTFJson, "meshes", meshes);
|
||||
serializeHolder(glTFJson, "skins", skins);
|
||||
serializeHolder(glTFJson, "animations", animations);
|
||||
serializeHolder(glTFJson, "cameras", cameras);
|
||||
serializeHolder(glTFJson, "nodes", nodes);
|
||||
if (!lights.ptrs.empty()) {
|
||||
json lightsJson = json::object();
|
||||
serializeHolder(lightsJson, "lights", lights);
|
||||
glTFJson["extensions"][KHR_LIGHTS_PUNCTUAL] = lightsJson;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,171 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <fstream>
|
||||
|
||||
#include "FBX2glTF.h"
|
||||
|
||||
#include "gltf/properties/AccessorData.hpp"
|
||||
#include "gltf/properties/AnimationData.hpp"
|
||||
#include "gltf/properties/BufferData.hpp"
|
||||
#include "gltf/properties/BufferViewData.hpp"
|
||||
#include "gltf/properties/CameraData.hpp"
|
||||
#include "gltf/properties/ImageData.hpp"
|
||||
#include "gltf/properties/LightData.hpp"
|
||||
#include "gltf/properties/MaterialData.hpp"
|
||||
#include "gltf/properties/MeshData.hpp"
|
||||
#include "gltf/properties/NodeData.hpp"
|
||||
#include "gltf/properties/PrimitiveData.hpp"
|
||||
#include "gltf/properties/SamplerData.hpp"
|
||||
#include "gltf/properties/SceneData.hpp"
|
||||
#include "gltf/properties/SkinData.hpp"
|
||||
#include "gltf/properties/TextureData.hpp"
|
||||
|
||||
/**
|
||||
* glTF 2.0 is based on the idea that data structs within a file are referenced by index; an
|
||||
* accessor will point to the n:th buffer view, and so on. The Holder class takes a freshly
|
||||
* instantiated class, and then creates, stored, and returns a shared_ptr<T> for it.
|
||||
*
|
||||
* The idea is that every glTF resource in the file will live as long as the Holder does, and the
|
||||
* Holders are all kept in the GLTFData struct. Clients may certainly cnhoose to perpetuate the full
|
||||
* shared_ptr<T> reference counting type, but generally speaking we pass around simple T& and T*
|
||||
* types because the GLTFData struct will, by design, outlive all other activity that takes place
|
||||
* during in a single conversion run.
|
||||
*/
|
||||
template <typename T>
|
||||
class Holder {
|
||||
public:
|
||||
std::shared_ptr<T> hold(T* ptr) {
|
||||
ptr->ix = to_uint32(ptrs.size());
|
||||
ptrs.emplace_back(ptr);
|
||||
return ptrs.back();
|
||||
}
|
||||
std::vector<std::shared_ptr<T>> ptrs;
|
||||
};
|
||||
|
||||
class GltfModel {
|
||||
public:
|
||||
explicit GltfModel(const GltfOptions& options)
|
||||
: binary(new std::vector<uint8_t>),
|
||||
isGlb(options.outputBinary),
|
||||
defaultSampler(nullptr),
|
||||
defaultBuffer(buffers.hold(buildDefaultBuffer(options))) {
|
||||
defaultSampler = samplers.hold(buildDefaultSampler());
|
||||
}
|
||||
|
||||
std::shared_ptr<BufferViewData> GetAlignedBufferView(
|
||||
BufferData& buffer,
|
||||
const BufferViewData::GL_ArrayType target);
|
||||
std::shared_ptr<BufferViewData>
|
||||
AddRawBufferView(BufferData& buffer, const char* source, uint32_t bytes);
|
||||
std::shared_ptr<BufferViewData> AddBufferViewForFile(
|
||||
BufferData& buffer,
|
||||
const std::string& filename);
|
||||
|
||||
template <class T>
|
||||
std::shared_ptr<AccessorData> AddAccessorWithView(
|
||||
BufferViewData& bufferView,
|
||||
const GLType& type,
|
||||
const std::vector<T>& source,
|
||||
std::string name) {
|
||||
auto accessor = accessors.hold(new AccessorData(bufferView, type, name));
|
||||
accessor->appendAsBinaryArray(source, *binary);
|
||||
bufferView.byteLength = accessor->byteLength();
|
||||
return accessor;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
std::shared_ptr<AccessorData>
|
||||
AddAccessorAndView(BufferData& buffer, const GLType& type, const std::vector<T>& source) {
|
||||
auto bufferView = GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_NONE);
|
||||
return AddAccessorWithView(*bufferView, type, source, std::string(""));
|
||||
}
|
||||
|
||||
template <class T>
|
||||
std::shared_ptr<AccessorData> AddAccessorAndView(
|
||||
BufferData& buffer,
|
||||
const GLType& type,
|
||||
const std::vector<T>& source,
|
||||
std::string name) {
|
||||
auto bufferView = GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_NONE);
|
||||
return AddAccessorWithView(*bufferView, type, source, name);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
std::shared_ptr<AccessorData> AddAttributeToPrimitive(
|
||||
BufferData& buffer,
|
||||
const RawModel& surfaceModel,
|
||||
PrimitiveData& primitive,
|
||||
const AttributeDefinition<T>& attrDef) {
|
||||
// copy attribute data into vector
|
||||
std::vector<T> attribArr;
|
||||
surfaceModel.GetAttributeArray<T>(attribArr, attrDef.rawAttributeIx);
|
||||
|
||||
std::shared_ptr<AccessorData> accessor;
|
||||
if (attrDef.dracoComponentType != draco::DT_INVALID && primitive.dracoMesh != nullptr) {
|
||||
primitive.AddDracoAttrib(attrDef, attribArr);
|
||||
|
||||
accessor = accessors.hold(new AccessorData(attrDef.glType));
|
||||
accessor->count = to_uint32(attribArr.size());
|
||||
} else {
|
||||
auto bufferView = GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_BUFFER);
|
||||
accessor = AddAccessorWithView(*bufferView, attrDef.glType, attribArr, std::string(""));
|
||||
}
|
||||
primitive.AddAttrib(attrDef.gltfName, *accessor);
|
||||
return accessor;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
void serializeHolder(json& glTFJson, std::string key, const Holder<T> holder) {
|
||||
if (!holder.ptrs.empty()) {
|
||||
std::vector<json> bits;
|
||||
for (const auto& ptr : holder.ptrs) {
|
||||
bits.push_back(ptr->serialize());
|
||||
}
|
||||
glTFJson[key] = bits;
|
||||
}
|
||||
}
|
||||
|
||||
void serializeHolders(json& glTFJson);
|
||||
|
||||
const bool isGlb;
|
||||
|
||||
// cache BufferViewData instances that've already been created from a given filename
|
||||
std::map<std::string, std::shared_ptr<BufferViewData>> filenameToBufferView;
|
||||
|
||||
std::shared_ptr<std::vector<uint8_t>> binary;
|
||||
|
||||
Holder<BufferData> buffers;
|
||||
Holder<BufferViewData> bufferViews;
|
||||
Holder<AccessorData> accessors;
|
||||
Holder<ImageData> images;
|
||||
Holder<SamplerData> samplers;
|
||||
Holder<TextureData> textures;
|
||||
Holder<MaterialData> materials;
|
||||
Holder<MeshData> meshes;
|
||||
Holder<SkinData> skins;
|
||||
Holder<AnimationData> animations;
|
||||
Holder<CameraData> cameras;
|
||||
Holder<NodeData> nodes;
|
||||
Holder<SceneData> scenes;
|
||||
Holder<LightData> lights;
|
||||
|
||||
std::shared_ptr<SamplerData> defaultSampler;
|
||||
std::shared_ptr<BufferData> defaultBuffer;
|
||||
|
||||
private:
|
||||
SamplerData* buildDefaultSampler() {
|
||||
return new SamplerData();
|
||||
}
|
||||
BufferData* buildDefaultBuffer(const GltfOptions& options) {
|
||||
return options.outputBinary ? new BufferData(binary)
|
||||
: new BufferData(extBufferFilename, binary, options.embedResources);
|
||||
}
|
||||
};
|
|
@ -0,0 +1,881 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include "Raw2Gltf.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#include <stb_image.h>
|
||||
#include <stb_image_write.h>
|
||||
|
||||
#include <utils/File_Utils.hpp>
|
||||
#include "utils/Image_Utils.hpp"
|
||||
#include "utils/String_Utils.hpp"
|
||||
|
||||
#include "raw/RawModel.hpp"
|
||||
|
||||
#include "gltf/properties/AccessorData.hpp"
|
||||
#include "gltf/properties/AnimationData.hpp"
|
||||
#include "gltf/properties/BufferData.hpp"
|
||||
#include "gltf/properties/BufferViewData.hpp"
|
||||
#include "gltf/properties/CameraData.hpp"
|
||||
#include "gltf/properties/ImageData.hpp"
|
||||
#include "gltf/properties/MaterialData.hpp"
|
||||
#include "gltf/properties/MeshData.hpp"
|
||||
#include "gltf/properties/NodeData.hpp"
|
||||
#include "gltf/properties/PrimitiveData.hpp"
|
||||
#include "gltf/properties/SamplerData.hpp"
|
||||
#include "gltf/properties/SceneData.hpp"
|
||||
#include "gltf/properties/SkinData.hpp"
|
||||
#include "gltf/properties/TextureData.hpp"
|
||||
|
||||
#include "GltfModel.hpp"
|
||||
#include "TextureBuilder.hpp"
|
||||
|
||||
typedef uint32_t TriangleIndex;
|
||||
|
||||
#define DEFAULT_SCENE_NAME "Root Scene"
|
||||
|
||||
/**
|
||||
* This method sanity-checks existance and then returns a *reference* to the *Data instance
|
||||
* registered under that name. This is safe in the context of this tool, where all such data
|
||||
* classes are guaranteed to stick around for the duration of the process.
|
||||
*/
|
||||
template <typename T>
|
||||
T& require(std::map<std::string, std::shared_ptr<T>> map, const std::string& key) {
|
||||
auto iter = map.find(key);
|
||||
assert(iter != map.end());
|
||||
T& result = *iter->second;
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T& require(std::map<long, std::shared_ptr<T>> map, long key) {
|
||||
auto iter = map.find(key);
|
||||
assert(iter != map.end());
|
||||
T& result = *iter->second;
|
||||
return result;
|
||||
}
|
||||
|
||||
static const std::vector<TriangleIndex> getIndexArray(const RawModel& raw) {
|
||||
std::vector<TriangleIndex> result;
|
||||
|
||||
for (int i = 0; i < raw.GetTriangleCount(); i++) {
|
||||
result.push_back((TriangleIndex)raw.GetTriangle(i).verts[0]);
|
||||
result.push_back((TriangleIndex)raw.GetTriangle(i).verts[1]);
|
||||
result.push_back((TriangleIndex)raw.GetTriangle(i).verts[2]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
ModelData* Raw2Gltf(
|
||||
std::ofstream& gltfOutStream,
|
||||
const std::string& outputFolder,
|
||||
const RawModel& raw,
|
||||
const GltfOptions& options) {
|
||||
if (verboseOutput) {
|
||||
fmt::printf("Building render model...\n");
|
||||
for (int i = 0; i < raw.GetMaterialCount(); i++) {
|
||||
fmt::printf(
|
||||
"Material %d: %s [shading: %s]\n",
|
||||
i,
|
||||
raw.GetMaterial(i).name.c_str(),
|
||||
Describe(raw.GetMaterial(i).info->shadingModel));
|
||||
}
|
||||
if (raw.GetVertexCount() > 2 * raw.GetTriangleCount()) {
|
||||
fmt::printf(
|
||||
"Warning: High vertex count. Make sure there are no unnecessary vertex attributes. (see -keepAttribute cmd-line option)");
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<RawModel> materialModels;
|
||||
raw.CreateMaterialModels(
|
||||
materialModels,
|
||||
options.useLongIndices == UseLongIndicesOptions::NEVER,
|
||||
options.keepAttribs,
|
||||
true);
|
||||
|
||||
if (verboseOutput) {
|
||||
fmt::printf("%7d vertices\n", raw.GetVertexCount());
|
||||
fmt::printf("%7d triangles\n", raw.GetTriangleCount());
|
||||
fmt::printf("%7d textures\n", raw.GetTextureCount());
|
||||
fmt::printf("%7d nodes\n", raw.GetNodeCount());
|
||||
fmt::printf("%7d surfaces\n", (int)materialModels.size());
|
||||
fmt::printf("%7d animations\n", raw.GetAnimationCount());
|
||||
fmt::printf("%7d cameras\n", raw.GetCameraCount());
|
||||
fmt::printf("%7d lights\n", raw.GetLightCount());
|
||||
}
|
||||
|
||||
std::unique_ptr<GltfModel> gltf(new GltfModel(options));
|
||||
|
||||
std::map<long, std::shared_ptr<NodeData>> nodesById;
|
||||
std::map<long, std::shared_ptr<MaterialData>> materialsById;
|
||||
std::map<std::string, std::shared_ptr<TextureData>> textureByIndicesKey;
|
||||
std::map<long, std::shared_ptr<MeshData>> meshBySurfaceId;
|
||||
|
||||
// for now, we only have one buffer; data->binary points to the same vector as that BufferData
|
||||
// does.
|
||||
BufferData& buffer = *gltf->defaultBuffer;
|
||||
{
|
||||
//
|
||||
// nodes
|
||||
//
|
||||
|
||||
for (int i = 0; i < raw.GetNodeCount(); i++) {
|
||||
// assumption: RawNode index == NodeData index
|
||||
const RawNode& node = raw.GetNode(i);
|
||||
|
||||
auto nodeData = gltf->nodes.hold(
|
||||
new NodeData(node.name, node.translation, node.rotation, node.scale, node.isJoint));
|
||||
|
||||
if (options.enableUserProperties) {
|
||||
nodeData->userProperties = node.userProperties;
|
||||
}
|
||||
|
||||
for (const auto& childId : node.childIds) {
|
||||
int childIx = raw.GetNodeById(childId);
|
||||
assert(childIx >= 0);
|
||||
nodeData->AddChildNode(childIx);
|
||||
}
|
||||
|
||||
nodesById.insert(std::make_pair(node.id, nodeData));
|
||||
}
|
||||
|
||||
//
|
||||
// animations
|
||||
//
|
||||
|
||||
for (int i = 0; i < raw.GetAnimationCount(); i++) {
|
||||
const RawAnimation& animation = raw.GetAnimation(i);
|
||||
|
||||
if (animation.channels.size() == 0) {
|
||||
fmt::printf(
|
||||
"Warning: animation '%s' has zero channels. Skipping.\n", animation.name.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
auto accessor = gltf->AddAccessorAndView(buffer, GLT_FLOAT, animation.times);
|
||||
accessor->min = {*std::min_element(std::begin(animation.times), std::end(animation.times))};
|
||||
accessor->max = {*std::max_element(std::begin(animation.times), std::end(animation.times))};
|
||||
|
||||
AnimationData& aDat = *gltf->animations.hold(new AnimationData(animation.name, *accessor));
|
||||
if (verboseOutput) {
|
||||
fmt::printf(
|
||||
"Animation '%s' has %lu channels:\n",
|
||||
animation.name.c_str(),
|
||||
animation.channels.size());
|
||||
}
|
||||
|
||||
for (size_t channelIx = 0; channelIx < animation.channels.size(); channelIx++) {
|
||||
const RawChannel& channel = animation.channels[channelIx];
|
||||
const RawNode& node = raw.GetNode(channel.nodeIndex);
|
||||
|
||||
if (verboseOutput) {
|
||||
fmt::printf(
|
||||
" Channel %lu (%s) has translations/rotations/scales/weights: [%lu, %lu, %lu, %lu]\n",
|
||||
channelIx,
|
||||
node.name.c_str(),
|
||||
channel.translations.size(),
|
||||
channel.rotations.size(),
|
||||
channel.scales.size(),
|
||||
channel.weights.size());
|
||||
}
|
||||
|
||||
NodeData& nDat = require(nodesById, node.id);
|
||||
if (!channel.translations.empty()) {
|
||||
aDat.AddNodeChannel(
|
||||
nDat,
|
||||
*gltf->AddAccessorAndView(buffer, GLT_VEC3F, channel.translations),
|
||||
"translation");
|
||||
}
|
||||
if (!channel.rotations.empty()) {
|
||||
aDat.AddNodeChannel(
|
||||
nDat, *gltf->AddAccessorAndView(buffer, GLT_QUATF, channel.rotations), "rotation");
|
||||
}
|
||||
if (!channel.scales.empty()) {
|
||||
aDat.AddNodeChannel(
|
||||
nDat, *gltf->AddAccessorAndView(buffer, GLT_VEC3F, channel.scales), "scale");
|
||||
}
|
||||
if (!channel.weights.empty()) {
|
||||
aDat.AddNodeChannel(
|
||||
nDat,
|
||||
*gltf->AddAccessorAndView(buffer, {CT_FLOAT, 1, "SCALAR"}, channel.weights),
|
||||
"weights");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// samplers
|
||||
//
|
||||
|
||||
// textures
|
||||
//
|
||||
|
||||
TextureBuilder textureBuilder(raw, options, outputFolder, *gltf);
|
||||
|
||||
//
|
||||
// materials
|
||||
//
|
||||
|
||||
for (int materialIndex = 0; materialIndex < raw.GetMaterialCount(); materialIndex++) {
|
||||
const RawMaterial& material = raw.GetMaterial(materialIndex);
|
||||
const bool isTransparent = material.type == RAW_MATERIAL_TYPE_TRANSPARENT ||
|
||||
material.type == RAW_MATERIAL_TYPE_SKINNED_TRANSPARENT;
|
||||
|
||||
Vec3f emissiveFactor;
|
||||
float emissiveIntensity;
|
||||
|
||||
// acquire the texture of a specific RawTextureUsage as *TextData, or nullptr if none exists
|
||||
auto simpleTex = [&](RawTextureUsage usage) -> std::shared_ptr<TextureData> {
|
||||
return (material.textures[usage] >= 0)
|
||||
? textureBuilder.simple(material.textures[usage], "simple")
|
||||
: nullptr;
|
||||
};
|
||||
|
||||
TextureData* normalTexture = simpleTex(RAW_TEXTURE_USAGE_NORMAL).get();
|
||||
TextureData* emissiveTexture = simpleTex(RAW_TEXTURE_USAGE_EMISSIVE).get();
|
||||
TextureData* occlusionTexture = nullptr;
|
||||
|
||||
std::shared_ptr<PBRMetallicRoughness> pbrMetRough;
|
||||
if (options.usePBRMetRough) {
|
||||
// albedo is a basic texture, no merging needed
|
||||
std::shared_ptr<TextureData> baseColorTex, aoMetRoughTex;
|
||||
|
||||
Vec4f diffuseFactor;
|
||||
float metallic, roughness;
|
||||
if (material.info->shadingModel == RAW_SHADING_MODEL_PBR_MET_ROUGH) {
|
||||
/**
|
||||
* PBR FBX Material -> PBR Met/Rough glTF.
|
||||
*
|
||||
* METALLIC and ROUGHNESS textures are packed in G and B channels of a rough/met texture.
|
||||
* Other values translate directly.
|
||||
*/
|
||||
RawMetRoughMatProps* props = (RawMetRoughMatProps*)material.info.get();
|
||||
|
||||
// determine if we need to generate a combined map
|
||||
bool hasMetallicMap = material.textures[RAW_TEXTURE_USAGE_METALLIC] >= 0;
|
||||
bool hasRoughnessMap = material.textures[RAW_TEXTURE_USAGE_ROUGHNESS] >= 0;
|
||||
bool hasOcclusionMap = material.textures[RAW_TEXTURE_USAGE_OCCLUSION] >= 0;
|
||||
bool atLeastTwoMaps = hasMetallicMap ? (hasRoughnessMap || hasOcclusionMap)
|
||||
: (hasRoughnessMap && hasMetallicMap);
|
||||
if (!atLeastTwoMaps) {
|
||||
// this handles the case of 0 or 1 maps supplied
|
||||
aoMetRoughTex = hasMetallicMap
|
||||
? simpleTex(RAW_TEXTURE_USAGE_METALLIC)
|
||||
: (hasRoughnessMap
|
||||
? simpleTex(RAW_TEXTURE_USAGE_ROUGHNESS)
|
||||
: (hasOcclusionMap ? simpleTex(RAW_TEXTURE_USAGE_OCCLUSION) : nullptr));
|
||||
} else {
|
||||
// otherwise merge occlusion into the red channel, metallic into blue channel, and
|
||||
// roughness into the green, of a new combinatory texture
|
||||
aoMetRoughTex = textureBuilder.combine(
|
||||
{
|
||||
material.textures[RAW_TEXTURE_USAGE_OCCLUSION],
|
||||
material.textures[RAW_TEXTURE_USAGE_METALLIC],
|
||||
material.textures[RAW_TEXTURE_USAGE_ROUGHNESS],
|
||||
},
|
||||
"ao_met_rough",
|
||||
[&](const std::vector<const TextureBuilder::pixel*> pixels)
|
||||
-> TextureBuilder::pixel {
|
||||
const float occlusion = (*pixels[0])[0];
|
||||
const float metallic = (*pixels[1])[0] * (hasMetallicMap ? 1 : props->metallic);
|
||||
const float roughness =
|
||||
(*pixels[2])[0] * (hasRoughnessMap ? 1 : props->roughness);
|
||||
return {{occlusion,
|
||||
props->invertRoughnessMap ? 1.0f - roughness : roughness,
|
||||
metallic,
|
||||
1}};
|
||||
},
|
||||
false);
|
||||
}
|
||||
baseColorTex = simpleTex(RAW_TEXTURE_USAGE_ALBEDO);
|
||||
diffuseFactor = props->diffuseFactor;
|
||||
metallic = props->metallic;
|
||||
roughness = props->roughness;
|
||||
emissiveFactor = props->emissiveFactor;
|
||||
emissiveIntensity = props->emissiveIntensity;
|
||||
// this will set occlusionTexture to null, if no actual occlusion map exists
|
||||
occlusionTexture = aoMetRoughTex.get();
|
||||
} else {
|
||||
/**
|
||||
* Traditional FBX Material -> PBR Met/Rough glTF.
|
||||
*
|
||||
* Diffuse channel is used as base colour. Simple constants for metallic and roughness.
|
||||
*/
|
||||
const RawTraditionalMatProps* props = ((RawTraditionalMatProps*)material.info.get());
|
||||
diffuseFactor = props->diffuseFactor;
|
||||
|
||||
if (material.info->shadingModel == RAW_SHADING_MODEL_BLINN ||
|
||||
material.info->shadingModel == RAW_SHADING_MODEL_PHONG) {
|
||||
// blinn/phong hardcoded to 0.4 metallic
|
||||
metallic = 0.4f;
|
||||
|
||||
// fairly arbitrary conversion equation, with properties:
|
||||
// shininess 0 -> roughness 1
|
||||
// shininess 2 -> roughness ~0.7
|
||||
// shininess 6 -> roughness 0.5
|
||||
// shininess 16 -> roughness ~0.33
|
||||
// as shininess ==> oo, roughness ==> 0
|
||||
auto getRoughness = [&](float shininess) { return sqrtf(2.0f / (2.0f + shininess)); };
|
||||
|
||||
aoMetRoughTex = textureBuilder.combine(
|
||||
{
|
||||
material.textures[RAW_TEXTURE_USAGE_SHININESS],
|
||||
},
|
||||
"ao_met_rough",
|
||||
[&](const std::vector<const TextureBuilder::pixel*> pixels)
|
||||
-> TextureBuilder::pixel {
|
||||
// do not multiply with props->shininess; that doesn't work like the other
|
||||
// factors.
|
||||
float shininess = props->shininess * (*pixels[0])[0];
|
||||
return {{0, getRoughness(shininess), metallic, 1}};
|
||||
},
|
||||
false);
|
||||
|
||||
if (aoMetRoughTex != nullptr) {
|
||||
// if we successfully built a texture, factors are just multiplicative identity
|
||||
metallic = roughness = 1.0f;
|
||||
} else {
|
||||
// no shininess texture,
|
||||
roughness = getRoughness(props->shininess);
|
||||
}
|
||||
|
||||
} else {
|
||||
metallic = 0.2f;
|
||||
roughness = 0.8f;
|
||||
}
|
||||
|
||||
baseColorTex = simpleTex(RAW_TEXTURE_USAGE_DIFFUSE);
|
||||
|
||||
emissiveFactor = props->emissiveFactor;
|
||||
emissiveIntensity = 1.0f;
|
||||
}
|
||||
pbrMetRough.reset(new PBRMetallicRoughness(
|
||||
baseColorTex.get(), aoMetRoughTex.get(), diffuseFactor, metallic, roughness));
|
||||
}
|
||||
|
||||
std::shared_ptr<KHRCmnUnlitMaterial> khrCmnUnlitMat;
|
||||
if (options.useKHRMatUnlit) {
|
||||
normalTexture = nullptr;
|
||||
|
||||
emissiveTexture = nullptr;
|
||||
emissiveFactor = Vec3f(0.00f, 0.00f, 0.00f);
|
||||
|
||||
Vec4f diffuseFactor;
|
||||
std::shared_ptr<TextureData> baseColorTex;
|
||||
|
||||
if (material.info->shadingModel == RAW_SHADING_MODEL_PBR_MET_ROUGH) {
|
||||
RawMetRoughMatProps* props = (RawMetRoughMatProps*)material.info.get();
|
||||
diffuseFactor = props->diffuseFactor;
|
||||
baseColorTex = simpleTex(RAW_TEXTURE_USAGE_ALBEDO);
|
||||
} else {
|
||||
RawTraditionalMatProps* props = ((RawTraditionalMatProps*)material.info.get());
|
||||
diffuseFactor = props->diffuseFactor;
|
||||
baseColorTex = simpleTex(RAW_TEXTURE_USAGE_DIFFUSE);
|
||||
}
|
||||
|
||||
pbrMetRough.reset(
|
||||
new PBRMetallicRoughness(baseColorTex.get(), nullptr, diffuseFactor, 0.0f, 1.0f));
|
||||
|
||||
khrCmnUnlitMat.reset(new KHRCmnUnlitMaterial());
|
||||
}
|
||||
if (!occlusionTexture) {
|
||||
occlusionTexture = simpleTex(RAW_TEXTURE_USAGE_OCCLUSION).get();
|
||||
}
|
||||
|
||||
std::shared_ptr<MaterialData> mData = gltf->materials.hold(new MaterialData(
|
||||
material.name,
|
||||
isTransparent,
|
||||
material.info->shadingModel,
|
||||
normalTexture,
|
||||
occlusionTexture,
|
||||
emissiveTexture,
|
||||
emissiveFactor * emissiveIntensity,
|
||||
khrCmnUnlitMat,
|
||||
pbrMetRough));
|
||||
materialsById[material.id] = mData;
|
||||
|
||||
if (options.enableUserProperties) {
|
||||
mData->userProperties = material.userProperties;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& surfaceModel : materialModels) {
|
||||
assert(surfaceModel.GetSurfaceCount() == 1);
|
||||
const RawSurface& rawSurface = surfaceModel.GetSurface(0);
|
||||
const long surfaceId = rawSurface.id;
|
||||
|
||||
const RawMaterial& rawMaterial =
|
||||
surfaceModel.GetMaterial(surfaceModel.GetTriangle(0).materialIndex);
|
||||
const MaterialData& mData = require(materialsById, rawMaterial.id);
|
||||
|
||||
MeshData* mesh = nullptr;
|
||||
auto meshIter = meshBySurfaceId.find(surfaceId);
|
||||
if (meshIter != meshBySurfaceId.end()) {
|
||||
mesh = meshIter->second.get();
|
||||
|
||||
} else {
|
||||
std::vector<float> defaultDeforms;
|
||||
for (const auto& channel : rawSurface.blendChannels) {
|
||||
defaultDeforms.push_back(channel.defaultDeform);
|
||||
}
|
||||
auto meshPtr = gltf->meshes.hold(new MeshData(rawSurface.name, defaultDeforms));
|
||||
meshBySurfaceId[surfaceId] = meshPtr;
|
||||
mesh = meshPtr.get();
|
||||
}
|
||||
|
||||
bool useLongIndices = (options.useLongIndices == UseLongIndicesOptions::ALWAYS) ||
|
||||
(options.useLongIndices == UseLongIndicesOptions::AUTO &&
|
||||
surfaceModel.GetVertexCount() > 65535);
|
||||
|
||||
std::shared_ptr<PrimitiveData> primitive;
|
||||
if (options.draco.enabled) {
|
||||
size_t triangleCount = surfaceModel.GetTriangleCount();
|
||||
|
||||
// initialize Draco mesh with vertex index information
|
||||
auto dracoMesh(std::make_shared<draco::Mesh>());
|
||||
dracoMesh->SetNumFaces(triangleCount);
|
||||
dracoMesh->set_num_points(surfaceModel.GetVertexCount());
|
||||
|
||||
for (uint32_t ii = 0; ii < triangleCount; ii++) {
|
||||
draco::Mesh::Face face;
|
||||
face[0] = surfaceModel.GetTriangle(ii).verts[0];
|
||||
face[1] = surfaceModel.GetTriangle(ii).verts[1];
|
||||
face[2] = surfaceModel.GetTriangle(ii).verts[2];
|
||||
dracoMesh->SetFace(draco::FaceIndex(ii), face);
|
||||
}
|
||||
|
||||
AccessorData& indexes =
|
||||
*gltf->accessors.hold(new AccessorData(useLongIndices ? GLT_UINT : GLT_USHORT));
|
||||
indexes.count = to_uint32(3 * triangleCount);
|
||||
primitive.reset(new PrimitiveData(indexes, mData, dracoMesh));
|
||||
} else {
|
||||
const AccessorData& indexes = *gltf->AddAccessorWithView(
|
||||
*gltf->GetAlignedBufferView(buffer, BufferViewData::GL_ELEMENT_ARRAY_BUFFER),
|
||||
useLongIndices ? GLT_UINT : GLT_USHORT,
|
||||
getIndexArray(surfaceModel),
|
||||
std::string(""));
|
||||
primitive.reset(new PrimitiveData(indexes, mData));
|
||||
};
|
||||
|
||||
//
|
||||
// surface vertices
|
||||
//
|
||||
{
|
||||
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_POSITION) != 0) {
|
||||
const AttributeDefinition<Vec3f> ATTR_POSITION(
|
||||
"POSITION",
|
||||
&RawVertex::position,
|
||||
GLT_VEC3F,
|
||||
draco::GeometryAttribute::POSITION,
|
||||
draco::DT_FLOAT32);
|
||||
auto accessor =
|
||||
gltf->AddAttributeToPrimitive<Vec3f>(buffer, surfaceModel, *primitive, ATTR_POSITION);
|
||||
|
||||
accessor->min = toStdVec(rawSurface.bounds.min);
|
||||
accessor->max = toStdVec(rawSurface.bounds.max);
|
||||
}
|
||||
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_NORMAL) != 0) {
|
||||
const AttributeDefinition<Vec3f> ATTR_NORMAL(
|
||||
"NORMAL",
|
||||
&RawVertex::normal,
|
||||
GLT_VEC3F,
|
||||
draco::GeometryAttribute::NORMAL,
|
||||
draco::DT_FLOAT32);
|
||||
const auto _ =
|
||||
gltf->AddAttributeToPrimitive<Vec3f>(buffer, surfaceModel, *primitive, ATTR_NORMAL);
|
||||
}
|
||||
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_TANGENT) != 0) {
|
||||
const AttributeDefinition<Vec4f> ATTR_TANGENT("TANGENT", &RawVertex::tangent, GLT_VEC4F);
|
||||
const auto _ = gltf->AddAttributeToPrimitive<Vec4f>(
|
||||
buffer, surfaceModel, *primitive, ATTR_TANGENT);
|
||||
}
|
||||
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_COLOR) != 0) {
|
||||
const AttributeDefinition<Vec4f> ATTR_COLOR(
|
||||
"COLOR_0",
|
||||
&RawVertex::color,
|
||||
GLT_VEC4F,
|
||||
draco::GeometryAttribute::COLOR,
|
||||
draco::DT_FLOAT32);
|
||||
const auto _ =
|
||||
gltf->AddAttributeToPrimitive<Vec4f>(buffer, surfaceModel, *primitive, ATTR_COLOR);
|
||||
}
|
||||
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_UV0) != 0) {
|
||||
const AttributeDefinition<Vec2f> ATTR_TEXCOORD_0(
|
||||
"TEXCOORD_0",
|
||||
&RawVertex::uv0,
|
||||
GLT_VEC2F,
|
||||
draco::GeometryAttribute::TEX_COORD,
|
||||
draco::DT_FLOAT32);
|
||||
const auto _ = gltf->AddAttributeToPrimitive<Vec2f>(
|
||||
buffer, surfaceModel, *primitive, ATTR_TEXCOORD_0);
|
||||
}
|
||||
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_UV1) != 0) {
|
||||
const AttributeDefinition<Vec2f> ATTR_TEXCOORD_1(
|
||||
"TEXCOORD_1",
|
||||
&RawVertex::uv1,
|
||||
GLT_VEC2F,
|
||||
draco::GeometryAttribute::TEX_COORD,
|
||||
draco::DT_FLOAT32);
|
||||
const auto _ = gltf->AddAttributeToPrimitive<Vec2f>(
|
||||
buffer, surfaceModel, *primitive, ATTR_TEXCOORD_1);
|
||||
}
|
||||
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_JOINT_INDICES) != 0) {
|
||||
const AttributeDefinition<Vec4i> ATTR_JOINTS(
|
||||
"JOINTS_0",
|
||||
&RawVertex::jointIndices,
|
||||
GLT_VEC4I,
|
||||
draco::GeometryAttribute::GENERIC,
|
||||
draco::DT_UINT16);
|
||||
const auto _ =
|
||||
gltf->AddAttributeToPrimitive<Vec4i>(buffer, surfaceModel, *primitive, ATTR_JOINTS);
|
||||
}
|
||||
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS) != 0) {
|
||||
const AttributeDefinition<Vec4f> ATTR_WEIGHTS(
|
||||
"WEIGHTS_0",
|
||||
&RawVertex::jointWeights,
|
||||
GLT_VEC4F,
|
||||
draco::GeometryAttribute::GENERIC,
|
||||
draco::DT_FLOAT32);
|
||||
const auto _ =
|
||||
gltf->AddAttributeToPrimitive<Vec4f>(buffer, surfaceModel, *primitive, ATTR_WEIGHTS);
|
||||
}
|
||||
|
||||
// each channel present in the mesh always ends up a target in the primitive
|
||||
for (int channelIx = 0; channelIx < rawSurface.blendChannels.size(); channelIx++) {
|
||||
const auto& channel = rawSurface.blendChannels[channelIx];
|
||||
|
||||
// track the bounds of each shape channel
|
||||
Bounds<float, 3> shapeBounds;
|
||||
|
||||
std::vector<Vec3f> positions, normals;
|
||||
std::vector<Vec4f> tangents;
|
||||
for (int jj = 0; jj < surfaceModel.GetVertexCount(); jj++) {
|
||||
auto blendVertex = surfaceModel.GetVertex(jj).blends[channelIx];
|
||||
shapeBounds.AddPoint(blendVertex.position);
|
||||
positions.push_back(blendVertex.position);
|
||||
if (options.useBlendShapeTangents && channel.hasNormals) {
|
||||
normals.push_back(blendVertex.normal);
|
||||
}
|
||||
if (options.useBlendShapeTangents && channel.hasTangents) {
|
||||
tangents.push_back(blendVertex.tangent);
|
||||
}
|
||||
}
|
||||
std::shared_ptr<AccessorData> pAcc = gltf->AddAccessorWithView(
|
||||
*gltf->GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_BUFFER),
|
||||
GLT_VEC3F,
|
||||
positions,
|
||||
channel.name);
|
||||
pAcc->min = toStdVec(shapeBounds.min);
|
||||
pAcc->max = toStdVec(shapeBounds.max);
|
||||
|
||||
std::shared_ptr<AccessorData> nAcc;
|
||||
if (!normals.empty()) {
|
||||
nAcc = gltf->AddAccessorWithView(
|
||||
*gltf->GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_BUFFER),
|
||||
GLT_VEC3F,
|
||||
normals,
|
||||
channel.name);
|
||||
}
|
||||
|
||||
std::shared_ptr<AccessorData> tAcc;
|
||||
if (!tangents.empty()) {
|
||||
nAcc = gltf->AddAccessorWithView(
|
||||
*gltf->GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_BUFFER),
|
||||
GLT_VEC4F,
|
||||
tangents,
|
||||
channel.name);
|
||||
}
|
||||
|
||||
primitive->AddTarget(pAcc.get(), nAcc.get(), tAcc.get());
|
||||
}
|
||||
}
|
||||
if (options.draco.enabled) {
|
||||
// Set up the encoder.
|
||||
draco::Encoder encoder;
|
||||
|
||||
if (options.draco.compressionLevel != -1) {
|
||||
int dracoSpeed = 10 - options.draco.compressionLevel;
|
||||
encoder.SetSpeedOptions(dracoSpeed, dracoSpeed);
|
||||
}
|
||||
if (options.draco.quantBitsPosition != -1) {
|
||||
encoder.SetAttributeQuantization(
|
||||
draco::GeometryAttribute::POSITION, options.draco.quantBitsPosition);
|
||||
}
|
||||
if (options.draco.quantBitsTexCoord != -1) {
|
||||
encoder.SetAttributeQuantization(
|
||||
draco::GeometryAttribute::TEX_COORD, options.draco.quantBitsTexCoord);
|
||||
}
|
||||
if (options.draco.quantBitsNormal != -1) {
|
||||
encoder.SetAttributeQuantization(
|
||||
draco::GeometryAttribute::NORMAL, options.draco.quantBitsNormal);
|
||||
}
|
||||
if (options.draco.quantBitsColor != -1) {
|
||||
encoder.SetAttributeQuantization(
|
||||
draco::GeometryAttribute::COLOR, options.draco.quantBitsColor);
|
||||
}
|
||||
if (options.draco.quantBitsGeneric != -1) {
|
||||
encoder.SetAttributeQuantization(
|
||||
draco::GeometryAttribute::GENERIC, options.draco.quantBitsGeneric);
|
||||
}
|
||||
|
||||
draco::EncoderBuffer dracoBuffer;
|
||||
draco::Status status = encoder.EncodeMeshToBuffer(*primitive->dracoMesh, &dracoBuffer);
|
||||
assert(status.code() == draco::Status::OK);
|
||||
|
||||
auto view = gltf->AddRawBufferView(buffer, dracoBuffer.data(), to_uint32(dracoBuffer.size()));
|
||||
primitive->NoteDracoBuffer(*view);
|
||||
}
|
||||
mesh->AddPrimitive(primitive);
|
||||
}
|
||||
|
||||
//
|
||||
// Assign meshes to node
|
||||
//
|
||||
|
||||
for (int i = 0; i < raw.GetNodeCount(); i++) {
|
||||
const RawNode& node = raw.GetNode(i);
|
||||
auto nodeData = gltf->nodes.ptrs[i];
|
||||
|
||||
//
|
||||
// Assign mesh to node
|
||||
//
|
||||
if (node.surfaceId > 0) {
|
||||
int surfaceIndex = raw.GetSurfaceById(node.surfaceId);
|
||||
const RawSurface& rawSurface = raw.GetSurface(surfaceIndex);
|
||||
|
||||
MeshData& meshData = require(meshBySurfaceId, rawSurface.id);
|
||||
nodeData->SetMesh(meshData.ix);
|
||||
|
||||
//
|
||||
// surface skin
|
||||
//
|
||||
if (!rawSurface.jointIds.empty()) {
|
||||
if (nodeData->skin == -1) {
|
||||
// glTF uses column-major matrices
|
||||
std::vector<Mat4f> inverseBindMatrices;
|
||||
for (const auto& inverseBindMatrice : rawSurface.inverseBindMatrices) {
|
||||
inverseBindMatrices.push_back(inverseBindMatrice.Transpose());
|
||||
}
|
||||
|
||||
std::vector<uint32_t> jointIndexes;
|
||||
for (const auto& jointId : rawSurface.jointIds) {
|
||||
jointIndexes.push_back(require(nodesById, jointId).ix);
|
||||
}
|
||||
|
||||
// Write out inverseBindMatrices
|
||||
auto accIBM = gltf->AddAccessorAndView(buffer, GLT_MAT4F, inverseBindMatrices);
|
||||
|
||||
auto skeletonRoot = require(nodesById, rawSurface.skeletonRootId);
|
||||
auto skin = *gltf->skins.hold(new SkinData(jointIndexes, *accIBM, skeletonRoot));
|
||||
nodeData->SetSkin(skin.ix);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// cameras
|
||||
//
|
||||
|
||||
for (int i = 0; i < raw.GetCameraCount(); i++) {
|
||||
const RawCamera& cam = raw.GetCamera(i);
|
||||
CameraData& camera = *gltf->cameras.hold(new CameraData());
|
||||
camera.name = cam.name;
|
||||
|
||||
if (cam.mode == RawCamera::CAMERA_MODE_PERSPECTIVE) {
|
||||
camera.type = "perspective";
|
||||
camera.aspectRatio = cam.perspective.aspectRatio;
|
||||
camera.yfov = cam.perspective.fovDegreesY * ((float)M_PI / 180.0f);
|
||||
camera.znear = cam.perspective.nearZ;
|
||||
camera.zfar = cam.perspective.farZ;
|
||||
} else {
|
||||
camera.type = "orthographic";
|
||||
camera.xmag = cam.orthographic.magX;
|
||||
camera.ymag = cam.orthographic.magY;
|
||||
camera.znear = cam.orthographic.nearZ;
|
||||
camera.zfar = cam.orthographic.farZ;
|
||||
}
|
||||
// Add the camera to the node hierarchy.
|
||||
|
||||
auto iter = nodesById.find(cam.nodeId);
|
||||
if (iter == nodesById.end()) {
|
||||
fmt::printf("Warning: Camera node id %lu does not exist.\n", cam.nodeId);
|
||||
continue;
|
||||
}
|
||||
iter->second->SetCamera(camera.ix);
|
||||
}
|
||||
|
||||
//
|
||||
// lights
|
||||
//
|
||||
std::vector<json> khrPunctualLights;
|
||||
if (options.useKHRLightsPunctual) {
|
||||
for (int i = 0; i < raw.GetLightCount(); i++) {
|
||||
const RawLight& light = raw.GetLight(i);
|
||||
LightData::Type type;
|
||||
switch (light.type) {
|
||||
case RAW_LIGHT_TYPE_DIRECTIONAL:
|
||||
type = LightData::Type::Directional;
|
||||
break;
|
||||
case RAW_LIGHT_TYPE_POINT:
|
||||
type = LightData::Type::Point;
|
||||
break;
|
||||
case RAW_LIGHT_TYPE_SPOT:
|
||||
type = LightData::Type::Spot;
|
||||
break;
|
||||
}
|
||||
const auto _ = gltf->lights.hold(new LightData(
|
||||
light.name,
|
||||
type,
|
||||
light.color,
|
||||
// FBX intensity defaults to 100, so let's call that 1.0;
|
||||
// but caveat: I find nothing in the documentation to suggest
|
||||
// what unit the FBX value is meant to be measured in...
|
||||
light.intensity / 100,
|
||||
light.innerConeAngle,
|
||||
light.outerConeAngle));
|
||||
}
|
||||
for (int i = 0; i < raw.GetNodeCount(); i++) {
|
||||
const RawNode& node = raw.GetNode(i);
|
||||
const auto nodeData = gltf->nodes.ptrs[i];
|
||||
|
||||
if (node.lightIx >= 0) {
|
||||
// we lean on the fact that in this simple case, raw and gltf indexing are aligned
|
||||
nodeData->SetLight(node.lightIx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NodeData& rootNode = require(nodesById, raw.GetRootNode());
|
||||
const SceneData& rootScene = *gltf->scenes.hold(new SceneData(DEFAULT_SCENE_NAME, rootNode));
|
||||
|
||||
if (options.outputBinary) {
|
||||
// note: glTF binary is little-endian
|
||||
const char glbHeader[] = {
|
||||
'g',
|
||||
'l',
|
||||
'T',
|
||||
'F', // magic
|
||||
0x02,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00, // version
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00, // total length: written in later
|
||||
};
|
||||
gltfOutStream.write(glbHeader, 12);
|
||||
|
||||
// binary glTF 2.0 has a sub-header for each of the JSON and BIN chunks
|
||||
const char glb2JsonHeader[] = {
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00, // chunk length: written in later
|
||||
'J',
|
||||
'S',
|
||||
'O',
|
||||
'N', // chunk type: 0x4E4F534A aka JSON
|
||||
};
|
||||
gltfOutStream.write(glb2JsonHeader, 8);
|
||||
}
|
||||
|
||||
{
|
||||
std::vector<std::string> extensionsUsed, extensionsRequired;
|
||||
if (options.useKHRMatUnlit) {
|
||||
extensionsUsed.push_back(KHR_MATERIALS_CMN_UNLIT);
|
||||
}
|
||||
if (!gltf->lights.ptrs.empty()) {
|
||||
extensionsUsed.push_back(KHR_LIGHTS_PUNCTUAL);
|
||||
}
|
||||
if (options.draco.enabled) {
|
||||
extensionsUsed.push_back(KHR_DRACO_MESH_COMPRESSION);
|
||||
extensionsRequired.push_back(KHR_DRACO_MESH_COMPRESSION);
|
||||
}
|
||||
|
||||
json glTFJson{{"asset", {{"generator", "FBX2glTF v" + FBX2GLTF_VERSION}, {"version", "2.0"}}},
|
||||
{"scene", rootScene.ix}};
|
||||
if (!extensionsUsed.empty()) {
|
||||
glTFJson["extensionsUsed"] = extensionsUsed;
|
||||
}
|
||||
if (!extensionsRequired.empty()) {
|
||||
glTFJson["extensionsRequired"] = extensionsRequired;
|
||||
}
|
||||
|
||||
gltf->serializeHolders(glTFJson);
|
||||
|
||||
gltfOutStream << glTFJson.dump(options.outputBinary ? 0 : 4);
|
||||
}
|
||||
if (options.outputBinary) {
|
||||
uint32_t jsonLength = (uint32_t)gltfOutStream.tellp() - 20;
|
||||
// the binary body must begin on a 4-aligned address, so pad json with spaces if necessary
|
||||
while ((jsonLength % 4) != 0) {
|
||||
gltfOutStream.put(' ');
|
||||
jsonLength++;
|
||||
}
|
||||
|
||||
uint32_t binHeader = (uint32_t)gltfOutStream.tellp();
|
||||
// binary glTF 2.0 has a sub-header for each of the JSON and BIN chunks
|
||||
const char glb2BinaryHeader[] = {
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x00, // chunk length: written in later
|
||||
'B',
|
||||
'I',
|
||||
'N',
|
||||
0x00, // chunk type: 0x004E4942 aka BIN
|
||||
};
|
||||
gltfOutStream.write(glb2BinaryHeader, 8);
|
||||
|
||||
// append binary buffer directly to .glb file
|
||||
size_t binaryLength = gltf->binary->size();
|
||||
gltfOutStream.write((const char*)&(*gltf->binary)[0], binaryLength);
|
||||
while ((binaryLength % 4) != 0) {
|
||||
gltfOutStream.put('\0');
|
||||
binaryLength++;
|
||||
}
|
||||
uint32_t totalLength = to_uint32(gltfOutStream.tellp());
|
||||
|
||||
// seek back to sub-header for json chunk
|
||||
gltfOutStream.seekp(8);
|
||||
|
||||
// write total length, little-endian
|
||||
gltfOutStream.put((totalLength >> 0) & 0xFF);
|
||||
gltfOutStream.put((totalLength >> 8) & 0xFF);
|
||||
gltfOutStream.put((totalLength >> 16) & 0xFF);
|
||||
gltfOutStream.put((totalLength >> 24) & 0xFF);
|
||||
|
||||
// write JSON length, little-endian
|
||||
gltfOutStream.put((jsonLength >> 0) & 0xFF);
|
||||
gltfOutStream.put((jsonLength >> 8) & 0xFF);
|
||||
gltfOutStream.put((jsonLength >> 16) & 0xFF);
|
||||
gltfOutStream.put((jsonLength >> 24) & 0xFF);
|
||||
|
||||
// seek back to the gltf 2.0 binary chunk header
|
||||
gltfOutStream.seekp(binHeader);
|
||||
|
||||
// write total length, little-endian
|
||||
gltfOutStream.put((binaryLength >> 0) & 0xFF);
|
||||
gltfOutStream.put((binaryLength >> 8) & 0xFF);
|
||||
gltfOutStream.put((binaryLength >> 16) & 0xFF);
|
||||
gltfOutStream.put((binaryLength >> 24) & 0xFF);
|
||||
|
||||
// be tidy and return write pointer to end-of-file
|
||||
gltfOutStream.seekp(0, std::ios::end);
|
||||
}
|
||||
|
||||
return new ModelData(gltf->binary);
|
||||
}
|
|
@ -0,0 +1,185 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
// This can be a macro under Windows, confusing Draco
|
||||
#undef ERROR
|
||||
#include <draco/compression/encode.h>
|
||||
|
||||
#include "FBX2glTF.h"
|
||||
#include "raw/RawModel.hpp"
|
||||
|
||||
const std::string KHR_DRACO_MESH_COMPRESSION = "KHR_draco_mesh_compression";
|
||||
const std::string KHR_MATERIALS_CMN_UNLIT = "KHR_materials_unlit";
|
||||
const std::string KHR_LIGHTS_PUNCTUAL = "KHR_lights_punctual";
|
||||
|
||||
const std::string extBufferFilename = "buffer.bin";
|
||||
|
||||
struct ComponentType {
|
||||
// OpenGL Datatype enums
|
||||
enum GL_DataType {
|
||||
GL_BYTE = 5120,
|
||||
GL_UNSIGNED_BYTE,
|
||||
GL_SHORT,
|
||||
GL_UNSIGNED_SHORT,
|
||||
GL_INT,
|
||||
GL_UNSIGNED_INT,
|
||||
GL_FLOAT
|
||||
};
|
||||
|
||||
const GL_DataType glType;
|
||||
const unsigned int size;
|
||||
};
|
||||
|
||||
const ComponentType CT_USHORT = {ComponentType::GL_UNSIGNED_SHORT, 2};
|
||||
const ComponentType CT_UINT = {ComponentType::GL_UNSIGNED_INT, 4};
|
||||
const ComponentType CT_FLOAT = {ComponentType::GL_FLOAT, 4};
|
||||
|
||||
// Map our low-level data types for glTF output
|
||||
struct GLType {
|
||||
GLType(const ComponentType& componentType, unsigned int count, const std::string dataType)
|
||||
: componentType(componentType), count(count), dataType(dataType) {}
|
||||
|
||||
unsigned int byteStride() const {
|
||||
return componentType.size * count;
|
||||
}
|
||||
|
||||
void write(uint8_t* buf, const float scalar) const {
|
||||
*((float*)buf) = scalar;
|
||||
}
|
||||
void write(uint8_t* buf, const uint32_t scalar) const {
|
||||
switch (componentType.size) {
|
||||
case 1:
|
||||
*buf = (uint8_t)scalar;
|
||||
break;
|
||||
case 2:
|
||||
*((uint16_t*)buf) = (uint16_t)scalar;
|
||||
break;
|
||||
case 4:
|
||||
*((uint32_t*)buf) = scalar;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
template <class T, int d>
|
||||
void write(uint8_t* buf, const mathfu::Vector<T, d>& vector) const {
|
||||
for (int ii = 0; ii < d; ii++) {
|
||||
((T*)buf)[ii] = vector(ii);
|
||||
}
|
||||
}
|
||||
template <class T, int d>
|
||||
void write(uint8_t* buf, const mathfu::Matrix<T, d>& matrix) const {
|
||||
// three matrix types require special alignment considerations that we don't handle
|
||||
// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#data-alignment
|
||||
assert(!(sizeof(T) == 1 && d == 2));
|
||||
assert(!(sizeof(T) == 1 && d == 3));
|
||||
assert(!(sizeof(T) == 2 && d == 2));
|
||||
for (int col = 0; col < d; col++) {
|
||||
for (int row = 0; row < d; row++) {
|
||||
// glTF matrices are column-major
|
||||
((T*)buf)[col * d + row] = matrix(row, col);
|
||||
}
|
||||
}
|
||||
}
|
||||
template <class T>
|
||||
void write(uint8_t* buf, const mathfu::Quaternion<T>& quaternion) const {
|
||||
for (int ii = 0; ii < 3; ii++) {
|
||||
((T*)buf)[ii] = quaternion.vector()(ii);
|
||||
}
|
||||
((T*)buf)[3] = quaternion.scalar();
|
||||
}
|
||||
|
||||
const ComponentType componentType;
|
||||
const uint8_t count;
|
||||
const std::string dataType;
|
||||
};
|
||||
|
||||
const GLType GLT_FLOAT = {CT_FLOAT, 1, "SCALAR"};
|
||||
const GLType GLT_USHORT = {CT_USHORT, 1, "SCALAR"};
|
||||
const GLType GLT_UINT = {CT_UINT, 1, "SCALAR"};
|
||||
const GLType GLT_VEC2F = {CT_FLOAT, 2, "VEC2"};
|
||||
const GLType GLT_VEC3F = {CT_FLOAT, 3, "VEC3"};
|
||||
const GLType GLT_VEC4F = {CT_FLOAT, 4, "VEC4"};
|
||||
const GLType GLT_VEC4I = {CT_USHORT, 4, "VEC4"};
|
||||
const GLType GLT_MAT2F = {CT_USHORT, 4, "MAT2"};
|
||||
const GLType GLT_MAT3F = {CT_USHORT, 9, "MAT3"};
|
||||
const GLType GLT_MAT4F = {CT_FLOAT, 16, "MAT4"};
|
||||
const GLType GLT_QUATF = {CT_FLOAT, 4, "VEC4"};
|
||||
|
||||
/**
|
||||
* The base of any indexed glTF entity.
|
||||
*/
|
||||
struct Holdable {
|
||||
uint32_t ix = UINT_MAX;
|
||||
|
||||
virtual json serialize() const = 0;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct AttributeDefinition {
|
||||
const std::string gltfName;
|
||||
const T RawVertex::*rawAttributeIx;
|
||||
const GLType glType;
|
||||
const draco::GeometryAttribute::Type dracoAttribute;
|
||||
const draco::DataType dracoComponentType;
|
||||
|
||||
AttributeDefinition(
|
||||
const std::string gltfName,
|
||||
const T RawVertex::*rawAttributeIx,
|
||||
const GLType& _glType,
|
||||
const draco::GeometryAttribute::Type dracoAttribute,
|
||||
const draco::DataType dracoComponentType)
|
||||
: gltfName(gltfName),
|
||||
rawAttributeIx(rawAttributeIx),
|
||||
glType(_glType),
|
||||
dracoAttribute(dracoAttribute),
|
||||
dracoComponentType(dracoComponentType) {}
|
||||
|
||||
AttributeDefinition(
|
||||
const std::string gltfName,
|
||||
const T RawVertex::*rawAttributeIx,
|
||||
const GLType& _glType)
|
||||
: gltfName(gltfName),
|
||||
rawAttributeIx(rawAttributeIx),
|
||||
glType(_glType),
|
||||
dracoAttribute(draco::GeometryAttribute::INVALID),
|
||||
dracoComponentType(draco::DataType::DT_INVALID) {}
|
||||
};
|
||||
|
||||
struct AccessorData;
|
||||
struct AnimationData;
|
||||
struct BufferData;
|
||||
struct BufferViewData;
|
||||
struct CameraData;
|
||||
struct GLTFData;
|
||||
struct ImageData;
|
||||
struct MaterialData;
|
||||
struct MeshData;
|
||||
struct NodeData;
|
||||
struct PrimitiveData;
|
||||
struct SamplerData;
|
||||
struct SceneData;
|
||||
struct SkinData;
|
||||
struct TextureData;
|
||||
|
||||
struct ModelData {
|
||||
explicit ModelData(std::shared_ptr<const std::vector<uint8_t>> const& _binary)
|
||||
: binary(_binary) {}
|
||||
|
||||
std::shared_ptr<const std::vector<uint8_t>> const binary;
|
||||
};
|
||||
|
||||
ModelData* Raw2Gltf(
|
||||
std::ofstream& gltfOutStream,
|
||||
const std::string& outputFolder,
|
||||
const RawModel& raw,
|
||||
const GltfOptions& options);
|
|
@ -0,0 +1,226 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include "TextureBuilder.hpp"
|
||||
|
||||
#include <stb_image.h>
|
||||
#include <stb_image_write.h>
|
||||
|
||||
#include <utils/File_Utils.hpp>
|
||||
#include <utils/Image_Utils.hpp>
|
||||
#include <utils/String_Utils.hpp>
|
||||
|
||||
#include <gltf/properties/ImageData.hpp>
|
||||
#include <gltf/properties/TextureData.hpp>
|
||||
|
||||
// keep track of some texture data as we load them
|
||||
struct TexInfo {
|
||||
explicit TexInfo(int rawTexIx) : rawTexIx(rawTexIx) {}
|
||||
|
||||
const int rawTexIx;
|
||||
int width{};
|
||||
int height{};
|
||||
int channels{};
|
||||
uint8_t* pixels{};
|
||||
};
|
||||
|
||||
std::shared_ptr<TextureData> TextureBuilder::combine(
|
||||
const std::vector<int>& ixVec,
|
||||
const std::string& tag,
|
||||
const pixel_merger& computePixel,
|
||||
bool includeAlphaChannel) {
|
||||
const std::string key = texIndicesKey(ixVec, tag);
|
||||
auto iter = textureByIndicesKey.find(key);
|
||||
if (iter != textureByIndicesKey.end()) {
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
int width = -1, height = -1;
|
||||
std::string mergedFilename = tag;
|
||||
std::vector<TexInfo> texes{};
|
||||
for (const int rawTexIx : ixVec) {
|
||||
TexInfo info(rawTexIx);
|
||||
if (rawTexIx >= 0) {
|
||||
const RawTexture& rawTex = raw.GetTexture(rawTexIx);
|
||||
const std::string& fileLoc = rawTex.fileLocation;
|
||||
const std::string& name = FileUtils::GetFileBase(FileUtils::GetFileName(fileLoc));
|
||||
if (!fileLoc.empty()) {
|
||||
info.pixels = stbi_load(fileLoc.c_str(), &info.width, &info.height, &info.channels, 0);
|
||||
if (!info.pixels) {
|
||||
fmt::printf("Warning: merge texture [%d](%s) could not be loaded.\n", rawTexIx, name);
|
||||
} else {
|
||||
if (width < 0) {
|
||||
width = info.width;
|
||||
height = info.height;
|
||||
} else if (width != info.width || height != info.height) {
|
||||
fmt::printf(
|
||||
"Warning: texture %s (%d, %d) can't be merged with previous texture(s) of dimension (%d, %d)\n",
|
||||
name,
|
||||
info.width,
|
||||
info.height,
|
||||
width,
|
||||
height);
|
||||
// this is bad enough that we abort the whole merge
|
||||
return nullptr;
|
||||
}
|
||||
mergedFilename += "_" + name;
|
||||
}
|
||||
}
|
||||
}
|
||||
texes.push_back(info);
|
||||
}
|
||||
// at the moment, the best choice of filename is also the best choice of name
|
||||
const std::string mergedName = mergedFilename;
|
||||
|
||||
if (width < 0) {
|
||||
// no textures to merge; bail
|
||||
return nullptr;
|
||||
}
|
||||
// TODO: which channel combinations make sense in input files?
|
||||
|
||||
// write 3 or 4 channels depending on whether or not we need transparency
|
||||
int channels = includeAlphaChannel ? 4 : 3;
|
||||
|
||||
std::vector<uint8_t> mergedPixels(static_cast<size_t>(channels * width * height));
|
||||
for (int xx = 0; xx < width; xx++) {
|
||||
for (int yy = 0; yy < height; yy++) {
|
||||
std::vector<pixel> pixels(texes.size());
|
||||
std::vector<const pixel*> pixelPointers(texes.size(), nullptr);
|
||||
for (int jj = 0; jj < texes.size(); jj++) {
|
||||
const TexInfo& tex = texes[jj];
|
||||
// each texture's structure will depend on its channel count
|
||||
int ii = tex.channels * (xx + yy * width);
|
||||
int kk = 0;
|
||||
if (tex.pixels != nullptr) {
|
||||
for (; kk < tex.channels; kk++) {
|
||||
pixels[jj][kk] = tex.pixels[ii++] / 255.0f;
|
||||
}
|
||||
}
|
||||
for (; kk < pixels[jj].size(); kk++) {
|
||||
pixels[jj][kk] = 1.0f;
|
||||
}
|
||||
pixelPointers[jj] = &pixels[jj];
|
||||
}
|
||||
const pixel merged = computePixel(pixelPointers);
|
||||
int ii = channels * (xx + yy * width);
|
||||
for (int jj = 0; jj < channels; jj++) {
|
||||
mergedPixels[ii + jj] = static_cast<uint8_t>(fmax(0, fmin(255.0f, merged[jj] * 255.0f)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// write a .png iff we need transparency in the destination texture
|
||||
bool png = includeAlphaChannel;
|
||||
|
||||
std::vector<char> imgBuffer;
|
||||
int res;
|
||||
if (png) {
|
||||
res = stbi_write_png_to_func(
|
||||
WriteToVectorContext,
|
||||
&imgBuffer,
|
||||
width,
|
||||
height,
|
||||
channels,
|
||||
mergedPixels.data(),
|
||||
width * channels);
|
||||
} else {
|
||||
res = stbi_write_jpg_to_func(
|
||||
WriteToVectorContext, &imgBuffer, width, height, channels, mergedPixels.data(), 80);
|
||||
}
|
||||
if (!res) {
|
||||
fmt::printf("Warning: failed to generate merge texture '%s'.\n", mergedFilename);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ImageData* image;
|
||||
if (options.outputBinary) {
|
||||
const auto bufferView =
|
||||
gltf.AddRawBufferView(*gltf.defaultBuffer, imgBuffer.data(), to_uint32(imgBuffer.size()));
|
||||
image = new ImageData(mergedName, *bufferView, png ? "image/png" : "image/jpeg");
|
||||
} else {
|
||||
const std::string imageFilename = mergedFilename + (png ? ".png" : ".jpg");
|
||||
const std::string imagePath = outputFolder + imageFilename;
|
||||
FILE* fp = fopen(imagePath.c_str(), "wb");
|
||||
if (fp == nullptr) {
|
||||
fmt::printf("Warning:: Couldn't write file '%s' for writing.\n", imagePath);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (fwrite(imgBuffer.data(), imgBuffer.size(), 1, fp) != 1) {
|
||||
fmt::printf(
|
||||
"Warning: Failed to write %lu bytes to file '%s'.\n", imgBuffer.size(), imagePath);
|
||||
fclose(fp);
|
||||
return nullptr;
|
||||
}
|
||||
fclose(fp);
|
||||
if (verboseOutput) {
|
||||
fmt::printf("Wrote %lu bytes to texture '%s'.\n", imgBuffer.size(), imagePath);
|
||||
}
|
||||
image = new ImageData(mergedName, imageFilename);
|
||||
}
|
||||
std::shared_ptr<TextureData> texDat = gltf.textures.hold(
|
||||
new TextureData(mergedName, *gltf.defaultSampler, *gltf.images.hold(image)));
|
||||
textureByIndicesKey.insert(std::make_pair(key, texDat));
|
||||
return texDat;
|
||||
}
|
||||
|
||||
/** Create a new TextureData for the given RawTexture index, or return a previously created one. */
|
||||
std::shared_ptr<TextureData> TextureBuilder::simple(int rawTexIndex, const std::string& tag) {
|
||||
const std::string key = texIndicesKey({rawTexIndex}, tag);
|
||||
auto iter = textureByIndicesKey.find(key);
|
||||
if (iter != textureByIndicesKey.end()) {
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
const RawTexture& rawTexture = raw.GetTexture(rawTexIndex);
|
||||
const std::string textureName = FileUtils::GetFileBase(rawTexture.name);
|
||||
const std::string relativeFilename = FileUtils::GetFileName(rawTexture.fileLocation);
|
||||
|
||||
ImageData* image = nullptr;
|
||||
if (options.outputBinary) {
|
||||
auto bufferView = gltf.AddBufferViewForFile(*gltf.defaultBuffer, rawTexture.fileLocation);
|
||||
if (bufferView) {
|
||||
const auto& suffix = FileUtils::GetFileSuffix(rawTexture.fileLocation);
|
||||
std::string mimeType;
|
||||
if (suffix) {
|
||||
mimeType = ImageUtils::suffixToMimeType(suffix.value());
|
||||
} else {
|
||||
mimeType = "image/jpeg";
|
||||
fmt::printf(
|
||||
"Warning: Can't deduce mime type of texture '%s'; using %s.\n",
|
||||
rawTexture.fileLocation,
|
||||
mimeType);
|
||||
}
|
||||
image = new ImageData(relativeFilename, *bufferView, mimeType);
|
||||
}
|
||||
|
||||
} else if (!relativeFilename.empty()) {
|
||||
image = new ImageData(relativeFilename, relativeFilename);
|
||||
std::string outputPath = outputFolder + "/" + relativeFilename;
|
||||
if (FileUtils::CopyFile(rawTexture.fileLocation, outputPath, true)) {
|
||||
if (verboseOutput) {
|
||||
fmt::printf("Copied texture '%s' to output folder: %s\n", textureName, outputPath);
|
||||
}
|
||||
} else {
|
||||
// no point commenting further on read/write error; CopyFile() does enough of that, and we
|
||||
// certainly want to to add an image struct to the glTF JSON, with the correct relative path
|
||||
// reference, even if the copy failed.
|
||||
}
|
||||
}
|
||||
if (!image) {
|
||||
// fallback is tiny transparent PNG
|
||||
image = new ImageData(
|
||||
textureName,
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==");
|
||||
}
|
||||
|
||||
std::shared_ptr<TextureData> texDat = gltf.textures.hold(
|
||||
new TextureData(textureName, *gltf.defaultSampler, *gltf.images.hold(image)));
|
||||
textureByIndicesKey.insert(std::make_pair(key, texDat));
|
||||
return texDat;
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "FBX2glTF.h"
|
||||
|
||||
#include <gltf/properties/ImageData.hpp>
|
||||
|
||||
#include "GltfModel.hpp"
|
||||
|
||||
class TextureBuilder {
|
||||
public:
|
||||
using pixel = std::array<float, 4>; // pixel components are floats in [0, 1]
|
||||
using pixel_merger = std::function<pixel(const std::vector<const pixel*>)>;
|
||||
|
||||
TextureBuilder(
|
||||
const RawModel& raw,
|
||||
const GltfOptions& options,
|
||||
const std::string& outputFolder,
|
||||
GltfModel& gltf)
|
||||
: raw(raw), options(options), outputFolder(outputFolder), gltf(gltf) {}
|
||||
~TextureBuilder() {}
|
||||
|
||||
std::shared_ptr<TextureData> combine(
|
||||
const std::vector<int>& ixVec,
|
||||
const std::string& tag,
|
||||
const pixel_merger& mergeFunction,
|
||||
bool transparency);
|
||||
|
||||
std::shared_ptr<TextureData> simple(int rawTexIndex, const std::string& tag);
|
||||
|
||||
static std::string texIndicesKey(const std::vector<int>& ixVec, const std::string& tag) {
|
||||
std::string result = tag;
|
||||
for (int ix : ixVec) {
|
||||
result += "_" + std::to_string(ix);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
static std::string describeChannel(int channels) {
|
||||
switch (channels) {
|
||||
case 1:
|
||||
return "G";
|
||||
case 2:
|
||||
return "GA";
|
||||
case 3:
|
||||
return "RGB";
|
||||
case 4:
|
||||
return "RGBA";
|
||||
default:
|
||||
return fmt::format("?%d?", channels);
|
||||
}
|
||||
};
|
||||
|
||||
static void WriteToVectorContext(void* context, void* data, int size) {
|
||||
auto* vec = static_cast<std::vector<uint8_t>*>(context);
|
||||
for (int ii = 0; ii < size; ii++) {
|
||||
vec->push_back(((uint8_t*)data)[ii]);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
const RawModel& raw;
|
||||
const GltfOptions& options;
|
||||
const std::string outputFolder;
|
||||
GltfModel& gltf;
|
||||
|
||||
std::map<std::string, std::shared_ptr<TextureData>> textureByIndicesKey;
|
||||
};
|
|
@ -0,0 +1,40 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include "AccessorData.hpp"
|
||||
#include "BufferViewData.hpp"
|
||||
|
||||
AccessorData::AccessorData(const BufferViewData& bufferView, GLType type, std::string name)
|
||||
: Holdable(),
|
||||
bufferView(bufferView.ix),
|
||||
type(std::move(type)),
|
||||
byteOffset(0),
|
||||
count(0),
|
||||
name(name) {}
|
||||
|
||||
AccessorData::AccessorData(GLType type)
|
||||
: Holdable(), bufferView(-1), type(std::move(type)), byteOffset(0), count(0) {}
|
||||
|
||||
json AccessorData::serialize() const {
|
||||
json result{
|
||||
{"componentType", type.componentType.glType}, {"type", type.dataType}, {"count", count}};
|
||||
if (bufferView >= 0) {
|
||||
result["bufferView"] = bufferView;
|
||||
result["byteOffset"] = byteOffset;
|
||||
}
|
||||
if (!min.empty()) {
|
||||
result["min"] = min;
|
||||
}
|
||||
if (!max.empty()) {
|
||||
result["max"] = max;
|
||||
}
|
||||
if (name.length() > 0) {
|
||||
result["name"] = name;
|
||||
}
|
||||
return result;
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "gltf/Raw2Gltf.hpp"
|
||||
|
||||
struct AccessorData : Holdable {
|
||||
AccessorData(const BufferViewData& bufferView, GLType type, std::string name);
|
||||
explicit AccessorData(GLType type);
|
||||
|
||||
json serialize() const override;
|
||||
|
||||
template <class T>
|
||||
void appendAsBinaryArray(const std::vector<T>& in, std::vector<uint8_t>& out) {
|
||||
const unsigned int stride = type.byteStride();
|
||||
const size_t offset = out.size();
|
||||
const size_t count = in.size();
|
||||
|
||||
this->count = (unsigned int)count;
|
||||
|
||||
out.resize(offset + count * stride);
|
||||
for (int ii = 0; ii < count; ii++) {
|
||||
type.write(&out[offset + ii * stride], in[ii]);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int byteLength() const {
|
||||
return type.byteStride() * count;
|
||||
}
|
||||
|
||||
const int bufferView;
|
||||
const GLType type;
|
||||
|
||||
unsigned int byteOffset;
|
||||
unsigned int count;
|
||||
std::vector<float> min;
|
||||
std::vector<float> max;
|
||||
std::string name;
|
||||
};
|
|
@ -0,0 +1,54 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include "AnimationData.hpp"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "AccessorData.hpp"
|
||||
#include "NodeData.hpp"
|
||||
|
||||
AnimationData::AnimationData(std::string name, const AccessorData& timeAccessor)
|
||||
: Holdable(), name(std::move(name)), timeAccessor(timeAccessor.ix) {}
|
||||
|
||||
// assumption: 1-to-1 relationship between channels and samplers; this is a simplification on what
|
||||
// glTF can express, but it means we can rely on samplerIx == channelIx throughout an animation
|
||||
void AnimationData::AddNodeChannel(
|
||||
const NodeData& node,
|
||||
const AccessorData& accessor,
|
||||
std::string path) {
|
||||
assert(channels.size() == samplers.size());
|
||||
uint32_t ix = to_uint32(channels.size());
|
||||
channels.emplace_back(channel_t(ix, node, std::move(path)));
|
||||
samplers.emplace_back(sampler_t(timeAccessor, accessor.ix));
|
||||
}
|
||||
|
||||
json AnimationData::serialize() const {
|
||||
return {{"name", name}, {"channels", channels}, {"samplers", samplers}};
|
||||
}
|
||||
|
||||
AnimationData::channel_t::channel_t(uint32_t ix, const NodeData& node, std::string path)
|
||||
: ix(ix), node(node.ix), path(std::move(path)) {}
|
||||
|
||||
AnimationData::sampler_t::sampler_t(uint32_t time, uint32_t output) : time(time), output(output) {}
|
||||
|
||||
void to_json(json& j, const AnimationData::channel_t& data) {
|
||||
j = json{{"sampler", data.ix},
|
||||
{
|
||||
"target",
|
||||
{{"node", data.node}, {"path", data.path}},
|
||||
}};
|
||||
}
|
||||
|
||||
void to_json(json& j, const AnimationData::sampler_t& data) {
|
||||
j = json{
|
||||
{"input", data.time},
|
||||
{"interpolation", "LINEAR"},
|
||||
{"output", data.output},
|
||||
};
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "gltf/Raw2Gltf.hpp"
|
||||
|
||||
struct AnimationData : Holdable {
|
||||
AnimationData(std::string name, const AccessorData& timeAccessor);
|
||||
|
||||
// assumption: 1-to-1 relationship between channels and samplers; this is a simplification on what
|
||||
// glTF can express, but it means we can rely on samplerIx == channelIx throughout an animation
|
||||
void AddNodeChannel(const NodeData& node, const AccessorData& accessor, std::string path);
|
||||
|
||||
json serialize() const override;
|
||||
|
||||
struct channel_t {
|
||||
channel_t(uint32_t _ix, const NodeData& node, std::string path);
|
||||
|
||||
const uint32_t ix;
|
||||
const uint32_t node;
|
||||
const std::string path;
|
||||
};
|
||||
|
||||
struct sampler_t {
|
||||
sampler_t(uint32_t time, uint32_t output);
|
||||
|
||||
const uint32_t time;
|
||||
const uint32_t output;
|
||||
};
|
||||
|
||||
const std::string name;
|
||||
const uint32_t timeAccessor;
|
||||
std::vector<channel_t> channels;
|
||||
std::vector<sampler_t> samplers;
|
||||
};
|
||||
|
||||
void to_json(json& j, const AnimationData::channel_t& data);
|
||||
void to_json(json& j, const AnimationData::sampler_t& data);
|
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include <cppcodec/base64_default_rfc4648.hpp>
|
||||
|
||||
#include "BufferData.hpp"
|
||||
|
||||
BufferData::BufferData(const std::shared_ptr<const std::vector<uint8_t>>& binData)
|
||||
: Holdable(), isGlb(true), binData(binData) {}
|
||||
|
||||
BufferData::BufferData(
|
||||
std::string uri,
|
||||
const std::shared_ptr<const std::vector<uint8_t>>& binData,
|
||||
bool isEmbedded)
|
||||
: Holdable(), isGlb(false), uri(isEmbedded ? "" : std::move(uri)), binData(binData) {}
|
||||
|
||||
json BufferData::serialize() const {
|
||||
json result{{"byteLength", binData->size()}};
|
||||
if (!isGlb) {
|
||||
if (!uri.empty()) {
|
||||
result["uri"] = uri;
|
||||
} else {
|
||||
std::string encoded = base64::encode(*binData);
|
||||
result["uri"] = "data:application/octet-stream;base64," + encoded;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "gltf/Raw2Gltf.hpp"
|
||||
|
||||
struct BufferData : Holdable {
|
||||
explicit BufferData(const std::shared_ptr<const std::vector<uint8_t>>& binData);
|
||||
|
||||
BufferData(
|
||||
std::string uri,
|
||||
const std::shared_ptr<const std::vector<uint8_t>>& binData,
|
||||
bool isEmbedded = false);
|
||||
|
||||
json serialize() const override;
|
||||
|
||||
const bool isGlb;
|
||||
const std::string uri;
|
||||
const std::shared_ptr<const std::vector<uint8_t>> binData; // TODO this is just weird
|
||||
};
|
|
@ -0,0 +1,24 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include "BufferViewData.hpp"
|
||||
#include "BufferData.hpp"
|
||||
|
||||
BufferViewData::BufferViewData(
|
||||
const BufferData& _buffer,
|
||||
const size_t _byteOffset,
|
||||
const GL_ArrayType _target)
|
||||
: Holdable(), buffer(_buffer.ix), byteOffset((unsigned int)_byteOffset), target(_target) {}
|
||||
|
||||
json BufferViewData::serialize() const {
|
||||
json result{{"buffer", buffer}, {"byteLength", byteLength}, {"byteOffset", byteOffset}};
|
||||
if (target != GL_ARRAY_NONE) {
|
||||
result["target"] = target;
|
||||
}
|
||||
return result;
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "gltf/Raw2Gltf.hpp"
|
||||
|
||||
struct BufferViewData : Holdable {
|
||||
enum GL_ArrayType {
|
||||
GL_ARRAY_NONE = 0, // no GL buffer is being set
|
||||
GL_ARRAY_BUFFER = 34962,
|
||||
GL_ELEMENT_ARRAY_BUFFER = 34963
|
||||
};
|
||||
|
||||
BufferViewData(const BufferData& _buffer, const size_t _byteOffset, const GL_ArrayType _target);
|
||||
|
||||
json serialize() const override;
|
||||
|
||||
const unsigned int buffer;
|
||||
const unsigned int byteOffset;
|
||||
const GL_ArrayType target;
|
||||
|
||||
unsigned int byteLength = 0;
|
||||
};
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include "CameraData.hpp"
|
||||
|
||||
CameraData::CameraData()
|
||||
: Holdable(), aspectRatio(0.0f), yfov(0.0f), xmag(0.0f), ymag(0.0f), znear(0.0f), zfar(0.0f) {}
|
||||
|
||||
json CameraData::serialize() const {
|
||||
json result{
|
||||
{"name", name},
|
||||
{"type", type},
|
||||
};
|
||||
json subResult{{"znear", znear}, {"zfar", zfar}};
|
||||
if (type == "perspective") {
|
||||
subResult["aspectRatio"] = aspectRatio;
|
||||
subResult["yfov"] = yfov;
|
||||
} else {
|
||||
subResult["xmag"] = xmag;
|
||||
subResult["ymag"] = ymag;
|
||||
}
|
||||
result[type] = subResult;
|
||||
return result;
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "gltf/Raw2Gltf.hpp"
|
||||
|
||||
// TODO: this class needs some work
|
||||
struct CameraData : Holdable {
|
||||
CameraData();
|
||||
json serialize() const override;
|
||||
|
||||
std::string name;
|
||||
std::string type;
|
||||
float aspectRatio;
|
||||
float yfov;
|
||||
float xmag;
|
||||
float ymag;
|
||||
float znear;
|
||||
float zfar;
|
||||
};
|
|
@ -0,0 +1,26 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include "ImageData.hpp"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "BufferViewData.hpp"
|
||||
|
||||
ImageData::ImageData(std::string name, std::string uri)
|
||||
: Holdable(), name(std::move(name)), uri(std::move(uri)), bufferView(-1) {}
|
||||
|
||||
ImageData::ImageData(std::string name, const BufferViewData& bufferView, std::string mimeType)
|
||||
: Holdable(), name(std::move(name)), bufferView(bufferView.ix), mimeType(std::move(mimeType)) {}
|
||||
|
||||
json ImageData::serialize() const {
|
||||
if (bufferView < 0) {
|
||||
return {{"name", name}, {"uri", uri}};
|
||||
}
|
||||
return {{"name", name}, {"bufferView", bufferView}, {"mimeType", mimeType}};
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "gltf/Raw2Gltf.hpp"
|
||||
|
||||
struct ImageData : Holdable {
|
||||
ImageData(std::string name, std::string uri);
|
||||
ImageData(std::string name, const BufferViewData& bufferView, std::string mimeType);
|
||||
|
||||
json serialize() const override;
|
||||
|
||||
const std::string name;
|
||||
const std::string uri; // non-empty in gltf mode
|
||||
const int32_t bufferView; // non-negative in glb mode
|
||||
const std::string mimeType;
|
||||
};
|
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include "LightData.hpp"
|
||||
|
||||
LightData::LightData(
|
||||
std::string name,
|
||||
Type type,
|
||||
Vec3f color,
|
||||
float intensity,
|
||||
float innerConeAngle,
|
||||
float outerConeAngle)
|
||||
: Holdable(),
|
||||
type(type),
|
||||
color(color),
|
||||
intensity(intensity),
|
||||
innerConeAngle(innerConeAngle),
|
||||
outerConeAngle(outerConeAngle) {}
|
||||
|
||||
json LightData::serialize() const {
|
||||
json result{{"name", name}, {"color", toStdVec(color)}, {"intensity", intensity}};
|
||||
switch (type) {
|
||||
case Directional:
|
||||
result["type"] = "directional";
|
||||
break;
|
||||
case Point:
|
||||
result["type"] = "point";
|
||||
break;
|
||||
case Spot:
|
||||
result["type"] = "spot";
|
||||
json spotJson;
|
||||
if (innerConeAngle != 0) {
|
||||
spotJson["innerConeAngle"] = innerConeAngle;
|
||||
}
|
||||
if (outerConeAngle != M_PI_4) {
|
||||
spotJson["outerConeAngle"] = outerConeAngle;
|
||||
}
|
||||
result["spot"] = spotJson;
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "gltf/Raw2Gltf.hpp"
|
||||
|
||||
struct LightData : Holdable {
|
||||
enum Type {
|
||||
Directional,
|
||||
Point,
|
||||
Spot,
|
||||
};
|
||||
|
||||
LightData(
|
||||
std::string name,
|
||||
Type type,
|
||||
Vec3f color,
|
||||
float intensity,
|
||||
float innerConeAngle,
|
||||
float outerConeAngle);
|
||||
|
||||
json serialize() const override;
|
||||
|
||||
const std::string name;
|
||||
const Type type;
|
||||
const Vec3f color;
|
||||
const float intensity;
|
||||
const float innerConeAngle;
|
||||
const float outerConeAngle;
|
||||
};
|
|
@ -0,0 +1,133 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include "MaterialData.hpp"
|
||||
#include "TextureData.hpp"
|
||||
|
||||
// TODO: retrieve & pass in correct UV set from FBX
|
||||
std::unique_ptr<Tex> Tex::ref(const TextureData* tex, uint32_t texCoord) {
|
||||
return std::unique_ptr<Tex>{(tex != nullptr) ? new Tex(tex->ix, texCoord) : nullptr};
|
||||
}
|
||||
|
||||
Tex::Tex(uint32_t texRef, uint32_t texCoord) : texRef(texRef), texCoord(texCoord) {}
|
||||
|
||||
void to_json(json& j, const Tex& data) {
|
||||
j = json{{"index", data.texRef}, {"texCoord", data.texCoord}};
|
||||
}
|
||||
|
||||
KHRCmnUnlitMaterial::KHRCmnUnlitMaterial() {}
|
||||
|
||||
void to_json(json& j, const KHRCmnUnlitMaterial& d) {
|
||||
j = json({});
|
||||
}
|
||||
|
||||
inline float clamp(float d, float bottom = 0, float top = 1) {
|
||||
return std::max(bottom, std::min(top, d));
|
||||
}
|
||||
inline Vec3f
|
||||
clamp(const Vec3f& vec, const Vec3f& bottom = VEC3F_ZERO, const Vec3f& top = VEC3F_ONE) {
|
||||
return Vec3f::Max(bottom, Vec3f::Min(top, vec));
|
||||
}
|
||||
inline Vec4f
|
||||
clamp(const Vec4f& vec, const Vec4f& bottom = VEC4F_ZERO, const Vec4f& top = VEC4F_ONE) {
|
||||
return Vec4f::Max(bottom, Vec4f::Min(top, vec));
|
||||
}
|
||||
|
||||
PBRMetallicRoughness::PBRMetallicRoughness(
|
||||
const TextureData* baseColorTexture,
|
||||
const TextureData* metRoughTexture,
|
||||
const Vec4f& baseColorFactor,
|
||||
float metallic,
|
||||
float roughness)
|
||||
: baseColorTexture(Tex::ref(baseColorTexture)),
|
||||
metRoughTexture(Tex::ref(metRoughTexture)),
|
||||
baseColorFactor(clamp(baseColorFactor)),
|
||||
metallic(clamp(metallic)),
|
||||
roughness(clamp(roughness)) {}
|
||||
|
||||
void to_json(json& j, const PBRMetallicRoughness& d) {
|
||||
j = {};
|
||||
if (d.baseColorTexture != nullptr) {
|
||||
j["baseColorTexture"] = *d.baseColorTexture;
|
||||
}
|
||||
if (d.baseColorFactor.LengthSquared() > 0) {
|
||||
j["baseColorFactor"] = toStdVec(d.baseColorFactor);
|
||||
}
|
||||
if (d.metRoughTexture != nullptr) {
|
||||
j["metallicRoughnessTexture"] = *d.metRoughTexture;
|
||||
// if a texture is provided, throw away metallic/roughness values
|
||||
j["roughnessFactor"] = 1.0f;
|
||||
j["metallicFactor"] = 1.0f;
|
||||
} else {
|
||||
// without a texture, however, use metallic/roughness as constants
|
||||
j["metallicFactor"] = d.metallic;
|
||||
j["roughnessFactor"] = d.roughness;
|
||||
}
|
||||
}
|
||||
|
||||
MaterialData::MaterialData(
|
||||
std::string name,
|
||||
bool isTransparent,
|
||||
const RawShadingModel shadingModel,
|
||||
const TextureData* normalTexture,
|
||||
const TextureData* occlusionTexture,
|
||||
const TextureData* emissiveTexture,
|
||||
const Vec3f& emissiveFactor,
|
||||
std::shared_ptr<KHRCmnUnlitMaterial> const khrCmnConstantMaterial,
|
||||
std::shared_ptr<PBRMetallicRoughness> const pbrMetallicRoughness)
|
||||
: Holdable(),
|
||||
name(std::move(name)),
|
||||
shadingModel(shadingModel),
|
||||
isTransparent(isTransparent),
|
||||
normalTexture(Tex::ref(normalTexture)),
|
||||
occlusionTexture(Tex::ref(occlusionTexture)),
|
||||
emissiveTexture(Tex::ref(emissiveTexture)),
|
||||
emissiveFactor(clamp(emissiveFactor)),
|
||||
khrCmnConstantMaterial(khrCmnConstantMaterial),
|
||||
pbrMetallicRoughness(pbrMetallicRoughness) {}
|
||||
|
||||
json MaterialData::serialize() const {
|
||||
json result = {{"name", name},
|
||||
{"alphaMode", isTransparent ? "BLEND" : "OPAQUE"},
|
||||
{"extras",
|
||||
{{"fromFBX",
|
||||
{{"shadingModel", Describe(shadingModel)},
|
||||
{"isTruePBR", shadingModel == RAW_SHADING_MODEL_PBR_MET_ROUGH}}}}}};
|
||||
|
||||
if (normalTexture != nullptr) {
|
||||
result["normalTexture"] = *normalTexture;
|
||||
}
|
||||
if (occlusionTexture != nullptr) {
|
||||
result["occlusionTexture"] = *occlusionTexture;
|
||||
}
|
||||
if (emissiveTexture != nullptr) {
|
||||
result["emissiveTexture"] = *emissiveTexture;
|
||||
}
|
||||
if (emissiveFactor.LengthSquared() > 0) {
|
||||
result["emissiveFactor"] = toStdVec(emissiveFactor);
|
||||
}
|
||||
if (pbrMetallicRoughness != nullptr) {
|
||||
result["pbrMetallicRoughness"] = *pbrMetallicRoughness;
|
||||
}
|
||||
if (khrCmnConstantMaterial != nullptr) {
|
||||
json extensions = {};
|
||||
extensions[KHR_MATERIALS_CMN_UNLIT] = *khrCmnConstantMaterial;
|
||||
result["extensions"] = extensions;
|
||||
}
|
||||
|
||||
for (const auto& i : userProperties) {
|
||||
auto& prop_map = result["extras"]["fromFBX"]["userProperties"];
|
||||
|
||||
json j = json::parse(i);
|
||||
for (const auto& k : json::iterator_wrapper(j)) {
|
||||
prop_map[k.key()] = k.value();
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "gltf/Raw2Gltf.hpp"
|
||||
|
||||
struct Tex {
|
||||
static std::unique_ptr<Tex> ref(const TextureData* tex, uint32_t texCoord = 0);
|
||||
explicit Tex(uint32_t texRef, uint32_t texCoord);
|
||||
|
||||
const uint32_t texRef;
|
||||
const uint32_t texCoord;
|
||||
};
|
||||
|
||||
struct KHRCmnUnlitMaterial {
|
||||
KHRCmnUnlitMaterial();
|
||||
};
|
||||
|
||||
struct PBRMetallicRoughness {
|
||||
PBRMetallicRoughness(
|
||||
const TextureData* baseColorTexture,
|
||||
const TextureData* metRoughTexture,
|
||||
const Vec4f& baseColorFactor,
|
||||
float metallic = 0.1f,
|
||||
float roughness = 0.6f);
|
||||
|
||||
std::unique_ptr<Tex> baseColorTexture;
|
||||
std::unique_ptr<Tex> metRoughTexture;
|
||||
const Vec4f baseColorFactor;
|
||||
const float metallic;
|
||||
const float roughness;
|
||||
};
|
||||
|
||||
struct MaterialData : Holdable {
|
||||
MaterialData(
|
||||
std::string name,
|
||||
bool isTransparent,
|
||||
RawShadingModel shadingModel,
|
||||
const TextureData* normalTexture,
|
||||
const TextureData* occlusionTexture,
|
||||
const TextureData* emissiveTexture,
|
||||
const Vec3f& emissiveFactor,
|
||||
std::shared_ptr<KHRCmnUnlitMaterial> const khrCmnConstantMaterial,
|
||||
std::shared_ptr<PBRMetallicRoughness> const pbrMetallicRoughness);
|
||||
|
||||
json serialize() const override;
|
||||
|
||||
const std::string name;
|
||||
const RawShadingModel shadingModel;
|
||||
const bool isTransparent;
|
||||
const std::unique_ptr<const Tex> normalTexture;
|
||||
const std::unique_ptr<const Tex> occlusionTexture;
|
||||
const std::unique_ptr<const Tex> emissiveTexture;
|
||||
const Vec3f emissiveFactor;
|
||||
|
||||
const std::shared_ptr<const KHRCmnUnlitMaterial> khrCmnConstantMaterial;
|
||||
const std::shared_ptr<const PBRMetallicRoughness> pbrMetallicRoughness;
|
||||
|
||||
std::vector<std::string> userProperties;
|
||||
};
|
||||
|
||||
void to_json(json& j, const Tex& data);
|
||||
void to_json(json& j, const KHRCmnUnlitMaterial& d);
|
||||
void to_json(json& j, const PBRMetallicRoughness& d);
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue