Initial commit.

This commit is contained in:
Par Winzell 2017-10-13 01:46:12 -07:00
commit 276a0dfb86
56 changed files with 5781 additions and 0 deletions

178
CMakeLists.txt Normal file
View File

@ -0,0 +1,178 @@
cmake_minimum_required(VERSION 3.5)
project(FBX2glTF)
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")
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)
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()
# DRACO
ExternalProject_Add(Draco
UPDATE_DISCONNECTED TRUE
GIT_REPOSITORY https://github.com/google/draco
PREFIX draco
INSTALL_DIR
CMAKE_ARGS
-DCMAKE_INSTALL_PREFIX=<INSTALL_DIR>
)
set(DRACO_INCLUDE_DIR "${CMAKE_BINARY_DIR}/draco/include")
if (WIN32)
set(DRACO_LIB "${CMAKE_BINARY_DIR}/draco/lib/dracoenc.lib")
else()
set(DRACO_LIB "${CMAKE_BINARY_DIR}/draco/lib/libdracoenc.a")
endif()
# MATHFU
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
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."
)
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."
)
set(JSON_INCLUDE_DIR "${CMAKE_BINARY_DIR}/json/src/Json/src")
# cppcodec
ExternalProject_Add(CPPCodec
UPDATE_DISCONNECTED TRUE
GIT_REPOSITORY https://github.com/tplgy/cppcodec
PREFIX 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
src/utils/File_Utils.cpp
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
)
add_executable(FBX2glTF ${SOURCE_FILES})
add_dependencies(FBX2glTF
Draco
MathFu
Json
CxxOpts
CPPCodec
Fmt
)
if (NOT MSVC)
# Disable annoying & spammy warning from FBX SDK header file
target_compile_options(FBX2glTF PRIVATE
"-Wno-null-dereference"
"-Wunused"
)
endif()
target_link_libraries(FBX2glTF
${FRAMEWORKS}
${CMAKE_DL_LIBS}
${CMAKE_THREAD_LIBS_INIT}
${DRACO_LIB}
${FMT_LIB}
optimized ${FBXSDK_LIBRARY}
debug ${FBXSDK_LIBRARY_DEBUG}
)
target_include_directories(FBX2glTF PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/src
${FBXSDK_INCLUDE_DIR}
${DRACO_INCLUDE_DIR}
${MATHFU_INCLUDE_DIRS}
${JSON_INCLUDE_DIR}
${CXXOPTS_INCLUDE_DIR}
${CPPCODEC_INCLUDE_DIR}
${FMT_INCLUDE_DIR}
)
install (TARGETS FBX2glTF DESTINATION bin)

29
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,29 @@
# Contributing to FBX2glTF
We want to make contributing to this project as easy and transparent as
possible.
## Pull Requests
We actively welcome your pull requests.
1. Fork the repo and create your branch from `master`.
2. Ensure your code matches the style of existing source.
3. In case of behavioural changes, update this documentation.
4. If you haven't already, complete the Contributor License Agreement ("CLA").
## Contributor License Agreement ("CLA")
In order to accept your pull request, we need you to submit a CLA. You only need
to do this once to work on any of Facebook's open source projects.
Complete your CLA here: <https://code.facebook.com/cla>
## Issues
We use GitHub issues to track public bugs. Please ensure your description is
clear and has sufficient instructions to be able to reproduce the issue.
Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe
disclosure of security bugs. In those cases, please go through the process
outlined on that page and do not file a public issue.
## License
By contributing to FBX2glTF, you agree that your contributions will be licensed
under the LICENSE file in the root directory of this source tree.

102
FindFBX.cmake Normal file
View File

@ -0,0 +1,102 @@
#
# Helper function for finding the FBX SDK.
# Cribbed & tweaked from https://github.com/floooh/fbxc/
#
# params: FBXSDK_VERSION
# FBXSDK_SDKS
#
# sets: FBXSDK_FOUND
# FBXSDK_DIR
# FBXSDK_LIBRARY
# FBXSDK_LIBRARY_DEBUG
# FBXSDK_INCLUDE_DIR
#
# semi-hack to detect architecture
if( CMAKE_SIZEOF_VOID_P MATCHES 8 )
# void ptr = 8 byte --> x86_64
set(ARCH_32 OFF)
else()
# void ptr != 8 byte --> x86
set(ARCH_32 OFF)
endif()
set(FBXSDK_VERSION "2018.1.1" CACHE STRING "Precise version string of FBX SDK to use.")
set(_fbxsdk_vstudio_version "vs2015")
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}")
endif()
if (APPLE)
set(_fbxsdk_root "${FBXSDK_APPLE_ROOT}")
set(_fbxsdk_libdir_debug "lib/clang/debug")
set(_fbxsdk_libdir_release "lib/clang/release")
set(_fbxsdk_libname_debug "libfbxsdk.a")
set(_fbxsdk_libname_release "libfbxsdk.a")
elseif (WIN32)
set(_fbxsdk_root "${FBXSDK_WINDOWS_ROOT}")
if (ARCH_32)
set(_fbxsdk_libdir_debug "lib/${_fbxsdk_vstudio_version}/x86/debug")
set(_fbxsdk_libdir_release "lib/${_fbxsdk_vstudio_version}/x86/release")
else()
set(_fbxsdk_libdir_debug "lib/${_fbxsdk_vstudio_version}/x64/debug")
set(_fbxsdk_libdir_release "lib/${_fbxsdk_vstudio_version}/x64/release")
endif()
set(_fbxsdk_libname_debug "libfbxsdk-md.lib")
set(_fbxsdk_libname_release "libfbxsdk-md.lib")
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")
else()
set(_fbxsdk_libdir_debug "lib/gcc4/x64/debug")
set(_fbxsdk_libdir_release "lib/gcc4/x64/release")
endif()
set(_fbxsdk_libname_debug "libfbxsdk.a")
set(_fbxsdk_libname_release "libfbxsdk.a")
else()
message(FATAL_ERROR, "Unknown platform. Can't find FBX SDK.")
endif()
# should point the the FBX SDK installation dir
set(FBXSDK_ROOT "${_fbxsdk_root}")
message("FBXSDK_ROOT: ${FBXSDK_ROOT}")
# find header dir and libs
find_path(FBXSDK_INCLUDE_DIR "fbxsdk.h"
NO_CMAKE_FIND_ROOT_PATH
PATHS ${FBXSDK_ROOT}
PATH_SUFFIXES "include")
message("FBXSDK_INCLUDE_DIR: ${FBXSDK_INCLUDE_DIR}")
find_library(FBXSDK_LIBRARY ${_fbxsdk_libname_release}
NO_CMAKE_FIND_ROOT_PATH
PATHS "${FBXSDK_ROOT}/${_fbxsdk_libdir_release}")
message("FBXSDK_LIBRARY: ${FBXSDK_LIBRARY}")
find_library(FBXSDK_LIBRARY_DEBUG ${_fbxsdk_libname_debug}
NO_CMAKE_FIND_ROOT_PATH
PATHS "${FBXSDK_ROOT}/${_fbxsdk_libdir_debug}")
message("FBXSDK_LIBRARY_DEBUG: ${FBXSDK_LIBRARY_DEBUG}")
if (FBXSDK_INCLUDE_DIR AND FBXSDK_LIBRARY AND FBXSDK_LIBRARY_DEBUG)
set(FBXSDK_FOUND YES)
else()
set(FBXSDK_FOUND NO)
endif()

30
LICENSE Normal file
View File

@ -0,0 +1,30 @@
BSD License
For FBX2glTF software
Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name Facebook nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

33
PATENTS Normal file
View File

@ -0,0 +1,33 @@
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
Facebooks 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.

168
README.md Normal file
View File

@ -0,0 +1,168 @@
# FBX2glTF
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
This tool has been tested on Linux, Mac OS X and Windows. It requires CMake 3.5+
and a reasonably C++11 compliant toolchain.
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),
and [fmt](https://github.com/fmtlib/fmt);
all of which are automatically downloaded, configured and 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...
### Linux and MacOS X
Compilation on Unix machines should be as simple as:
```
> cd <FBX2glTF directory>
> cmake -H. -Bbuild
> make -Cbuild
```
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
```
### Windows
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.
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.
## 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
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.
### 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.
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.
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
the same way we use Draco to compress meshes (see below). Like geometry,
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".
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).
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,
(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.**
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.
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`.
## 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
in reducing the size of the output file, especially for static models.
Enabling this feature adds an expressed required dependency in the glTF on the
`KHR_draco_geometry_compression` extension, and can thus only be loaded by a
viewer that is willing and able to decompress the data.
**Note that at the time of writing, this glTF extension is still undergoing the
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.
## Authors
- Pär Winzell
- J.M.P. van Waveren
- Amanda Watson
## License
`FBX2glTF` is BSD-licensed. We also provide an additional patent grant.

44
npm/LICENSE Normal file
View File

@ -0,0 +1,44 @@
BSD License
For FBX2glTF software
Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name Facebook nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
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,
including without limitation the implied warranties of merchantability, fitness
for a particular purpose or non-infringement of third party rights. In no event
shall Autodesk, Inc. be liable for any direct, indirect, incidental, special,
exemplary, or consequential damages (including, but not limited to, procurement
of substitute goods or services; loss of use, data, or profits; or business
interruption) however caused and on any theory of liability, whether in
contract, strict liability, or tort (including negligence or otherwise) arising
in any way out of such code.

33
npm/PATENTS Normal file
View File

@ -0,0 +1,33 @@
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
Facebooks 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.

38
npm/README.md Normal file
View File

@ -0,0 +1,38 @@
# FBX2glTF
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),
a modern runtime asset delivery format.
# Platform Binaries
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
# Further Reading
The home of this tool is [here](https://github.com/facebookincubator/FBX2glTF).
# Authors
- Pär Winzell
- J.M.P. van Waveren
- Amanda Watson
# Legal
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,
including without limitation the implied warranties of merchantability, fitness
for a particular purpose or non-infringement of third party rights. In no event
shall Autodesk, Inc. be liable for any direct, indirect, incidental, special,
exemplary, or consequential damages (including, but not limited to, procurement
of substitute goods or services; loss of use, data, or profits; or business
interruption) however caused and on any theory of liability, whether in
contract, strict liability, or tort (including negligence or otherwise) arising
in any way out of such code.

6
npm/bin/README Normal file
View File

@ -0,0 +1,6 @@
This directory must be populated with the following files prior to building the
NPM package:
Darwin/FBX2glTF
Linux/FBX2glTF
Windows/FBX2glTF.exe

1
npm/bin/fbx2glb.bat Normal file
View File

@ -0,0 +1 @@
%~dp0\Windows\FBX2glTF --binary %1 %2

43
npm/bin/fbx2glb.sh Executable file
View File

@ -0,0 +1,43 @@
#!/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}"

43
npm/index.js Normal file
View File

@ -0,0 +1,43 @@
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;

27
npm/package.json Normal file
View File

@ -0,0 +1,27 @@
{
"name": "fbx2gltf",
"version": "0.9.0",
"description": "Node wrapper around FBX2glTF tools.",
"main": "index.js",
"repository": {
"type": "git",
"url": "git+https://github.com/facebookincubator/FBX2glTF.git"
},
"contributors": [
"Pär Winzell <zell@fb.com>",
"J.M.P. van Waveren",
"Amanda Watson"
],
"license": "BSD-3-Clause",
"bugs": {
"url": "https://github.com/facebookincubator/FBX2glTF/issues"
},
"homepage": "https://github.com/facebookincubator/FBX2glTF"
"files": [
"LICENSE",
"PATENTS",
"README.md",
"bin",
"index.js"
]
}

26
src/FBX2glTF.h Normal file
View File

@ -0,0 +1,26 @@
/**
* 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_H__
#define __FBX2GLTF_H__
#if defined ( _WIN32 )
// This can be a macro under Windows, confusing FMT
#undef isnan
// Tell Windows not to define min() and max() macros
#define NOMINMAX
#include <Windows.h>
#endif
#include <fmt/printf.h>
#include <fbxsdk.h>
#include "mathfu.h"
#endif // !__FBX2GLTF_H__

1033
src/Fbx2Raw.cpp Normal file

File diff suppressed because it is too large Load Diff

17
src/Fbx2Raw.h Normal file
View File

@ -0,0 +1,17 @@
/**
* 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__

718
src/Raw2Gltf.cpp Normal file
View File

@ -0,0 +1,718 @@
/**
* 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;
}
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;
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);
std::string textureName = Gltf::StringUtils::GetFileBaseString(texture.name);
// texture.name is the inferred filename on *our* system
const std::string texFilename = texture.name;
ImageData *source = nullptr;
if (options.outputBinary) {
std::ifstream file(texFilename, 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)) {
auto bufferView = gltf->AddRawBufferView(buffer, fileBuffer.data(), size);
source = new ImageData(textureName, *bufferView, "image/png");
} else {
fmt::printf("Warning: Couldn't read %lu bytes from %s, skipping\n", size, texFilename.c_str());
}
} else {
fmt::printf("Warning: Couldn't open texture file %s, skipping\n", texFilename.c_str());
}
} else {
// TODO: write to buffer.bin?
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);
// 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, 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" }}},
{ "extensionsUsed", extensionsUsed },
{ "extensionsRequired", extensionsRequired },
{ "scene", rootScene.ix }
};
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 Normal file
View File

@ -0,0 +1,204 @@
/**
* 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__

538
src/RawModel.cpp Normal file
View File

@ -0,0 +1,538 @@
/**
* 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(name);
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 Mat2f &transform)
{
for (size_t i = 0; i < vertices.size(); i++) {
if ((vertexAttributes & RAW_VERTEX_ATTRIBUTE_UV0) != 0) {
vertices[i].uv0 = transform * vertices[i].uv0;
}
if ((vertexAttributes & RAW_VERTEX_ATTRIBUTE_UV1) != 0) {
vertices[i].uv1 = transform * vertices[i].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);
for (const auto &transparentTriangle : transparentTriangles) {
sortedTriangles.push_back(transparentTriangle);
}
// Add the triangles to the sorted list.
for (const auto &opaqueTriangle : opaqueTriangles) {
sortedTriangles.push_back(opaqueTriangle);
}
} 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;
}

332
src/RawModel.h Normal file
View File

@ -0,0 +1,332 @@
/**
* 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>
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 Mat2f &transform);
// 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__

49
src/glTF/AccessorData.cpp Normal file
View File

@ -0,0 +1,49 @@
/**
* 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;
}

48
src/glTF/AccessorData.h Normal file
View File

@ -0,0 +1,48 @@
/**
* 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

View File

@ -0,0 +1,70 @@
/**
* 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 },
};
}

51
src/glTF/AnimationData.h Normal file
View File

@ -0,0 +1,51 @@
/**
* 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

43
src/glTF/BufferData.cpp Normal file
View File

@ -0,0 +1,43 @@
/**
* 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;
}

29
src/glTF/BufferData.h Normal file
View File

@ -0,0 +1,29 @@
/**
* 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

View File

@ -0,0 +1,32 @@
/**
* 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;
}

35
src/glTF/BufferViewData.h Normal file
View File

@ -0,0 +1,35 @@
/**
* 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

42
src/glTF/CameraData.cpp Normal file
View File

@ -0,0 +1,42 @@
/**
* 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;
}

31
src/glTF/CameraData.h Normal file
View File

@ -0,0 +1,31 @@
/**
* 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

44
src/glTF/ImageData.cpp Normal file
View File

@ -0,0 +1,44 @@
/**
* 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 }
};
}

28
src/glTF/ImageData.h Normal file
View File

@ -0,0 +1,28 @@
/**
* 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

201
src/glTF/MaterialData.cpp Normal file
View File

@ -0,0 +1,201 @@
/**
* 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, 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)),
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", "BLEND" }
};
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;
}

108
src/glTF/MaterialData.h Normal file
View File

@ -0,0 +1,108 @@
/**
* 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, 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 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

29
src/glTF/MeshData.cpp Normal file
View File

@ -0,0 +1,29 @@
/**
* 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 }
};
}

36
src/glTF/MeshData.h Normal file
View File

@ -0,0 +1,36 @@
/**
* 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

83
src/glTF/NodeData.cpp Normal file
View File

@ -0,0 +1,83 @@
/**
* 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;
}

38
src/glTF/NodeData.h Normal file
View File

@ -0,0 +1,38 @@
/**
* 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

View File

@ -0,0 +1,60 @@
/**
* 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 }
}}
};
}
}

71
src/glTF/PrimitiveData.h Normal file
View File

@ -0,0 +1,71 @@
/**
* 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

28
src/glTF/SamplerData.h Normal file
View File

@ -0,0 +1,28 @@
/**
* 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

28
src/glTF/SceneData.cpp Normal file
View File

@ -0,0 +1,28 @@
/**
* 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 }
};
}

25
src/glTF/SceneData.h Normal file
View File

@ -0,0 +1,25 @@
/**
* 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

32
src/glTF/SkinData.cpp Normal file
View File

@ -0,0 +1,32 @@
/**
* 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 }
};
}

28
src/glTF/SkinData.h Normal file
View File

@ -0,0 +1,28 @@
/**
* 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

30
src/glTF/TextureData.cpp Normal file
View File

@ -0,0 +1,30 @@
/**
* 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 }
};
}

26
src/glTF/TextureData.h Normal file
View File

@ -0,0 +1,26 @@
/**
* 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

249
src/main.cpp Normal file
View File

@ -0,0 +1,249 @@
/**
* 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 <unordered_map>
#include <map>
#include <iostream>
#include <fstream>
#if defined( __unix__ ) || defined( __APPLE__ )
#include <sys/stat.h>
#define _stricmp strcasecmp
#endif
#include <cxxopts.hpp>
#include "FBX2glTF.h"
#include "utils/String_Utils.h"
#include "utils/File_Utils.h"
#include "Fbx2Raw.h"
#include "RawModel.h"
#include "Raw2Gltf.h"
bool verboseOutput = false;
int main(int argc, char *argv[])
{
cxxopts::Options options(
"FBX2glTF",
"FBX2glTF 2.0: Generate a glTF 2.0 representation of an FBX model.");
std::string inputPath;
std::string outputPath;
Mat2f texturesTransform(0.0f);
bool doTransformTextures = false;
GltfOptions gltfOptions{
-1, // keepAttribs
false, // outputBinary
false, // embedResources
false, // useDraco
false, // useKHRMatCom
false, // usePBRMetRough
false // usePBRSpecGloss
};
options.positional_help("[<FBX File>]");
options.add_options()
(
"i,input", "The FBX model to convert.",
cxxopts::value<std::string>(inputPath))
(
"o,output", "Where to generate the output, without suffix.",
cxxopts::value<std::string>(outputPath))
(
"e,embed", "Inline buffers as data:// URIs within generated non-binary glTF.",
cxxopts::value<bool>(gltfOptions.embedResources))
(
"b,binary", "Output a single binary format .glb file.",
cxxopts::value<bool>(gltfOptions.outputBinary))
(
"d,draco", "Apply Draco mesh compression to geometries.",
cxxopts::value<bool>(gltfOptions.useDraco))
("flip-u", "Flip all U texture coordinates.")
("flip-v", "Flip all V texture coordinates.")
(
"khr-materials-common", "(WIP) Use KHR_materials_common extensions to specify Unlit/Lambert/Blinn/Phong shaders.",
cxxopts::value<bool>(gltfOptions.useKHRMatCom))
(
"pbr-metallic-roughness", "(WIP) Try to glean glTF 2.0 native PBR attributes from the FBX.",
cxxopts::value<bool>(gltfOptions.usePBRMetRough))
(
"pbr-specular-glossiness", "(WIP) Experimentally fill in the KHR_materials_pbrSpecularGlossiness extension.",
cxxopts::value<bool>(gltfOptions.usePBRSpecGloss))
(
"k,keep-attribute", "Used repeatedly to build a limiting set of vertex attributes to keep.",
cxxopts::value<std::string>())
("v,verbose", "Enable verbose output.")
("h,help", "Show this help.")
("V,version", "Display the current program version.");
try {
options.parse_positional("input");
options.parse(argc, argv);
} catch (const cxxopts::OptionException &e) {
fmt::printf(options.help());
return 1;
}
if (options.count("version")) {
fmt::printf(
R"(
FBX2glTF version 2.0
Copyright (c) 2016-2017 Oculus VR, LLC.
)");
return 0;
}
if (options.count("help")) {
fmt::printf(options.help());
return 0;
}
if (!options.count("input")) {
fmt::printf("You must supply a FBX file to convert.\n");
fmt::printf(options.help());
return 1;
}
if (options.count("verbose")) {
verboseOutput = true;
}
if (!gltfOptions.useKHRMatCom && !gltfOptions.usePBRSpecGloss && !gltfOptions.usePBRMetRough) {
if (verboseOutput) {
fmt::printf("Defaulting to KHR_materials_common material support.\n");
}
gltfOptions.useKHRMatCom = true;
}
if (options.count("flip-u") > 0) {
texturesTransform(0, 0) = -1.0f;
texturesTransform(1, 1) = 1.0f;
doTransformTextures = true;
}
if (options.count("flip-v") > 0) {
texturesTransform(0, 0) = 1.0f;
texturesTransform(1, 1) = -1.0f;
doTransformTextures = true;
}
if (options.count("keepAttribute")) {
gltfOptions.keepAttribs = RAW_VERTEX_ATTRIBUTE_JOINT_INDICES | RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS;
for (const auto &attribute : options["keepAttribute"].as<std::vector<std::string>>()) {
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 --keepAttribute: %s\n", attribute);
fmt::printf("Valid choices are: position, normal, tangent, binormial, color, uv0, uv1, auto,\n");
return 1;
}
}
}
if (gltfOptions.embedResources && gltfOptions.outputBinary) {
fmt::printf("Note: Ignoring --embed; it's meaningless with --binary.\n");
}
if (options.count("output") == 0) {
// if -o is not given, default to the basename of the .fbx
outputPath = "./" + Gltf::StringUtils::GetFileBaseString(inputPath);
}
std::string outputFolder; // the output folder in .gltf mode, not used for .glb
std::string modelPath; // the path of the actual .glb or .gltf file
if (gltfOptions.outputBinary) {
// in binary mode, we write precisely where we're asked
modelPath = outputPath + ".glb";
} else {
// in gltf mode, we create a folder and write into that
outputFolder = outputPath + "_out/";
if (!FileUtils::CreatePath(outputFolder.c_str())) {
fmt::fprintf(stderr, "ERROR: Failed to create folder: %s'\n", outputFolder.c_str());
return 1;
}
modelPath = outputFolder + Gltf::StringUtils::GetFileNameString(outputPath) + ".gltf";
}
ModelData *data_render_model = nullptr;
RawModel raw;
if (verboseOutput) {
fmt::printf("Loading FBX File: %s\n", inputPath);
}
if (!LoadFBXFile(raw, inputPath.c_str(), "tga;bmp;png;jpg")) {
fmt::fprintf(stderr, "ERROR:: Failed to parse FBX: %s\n", inputPath);
return 1;
}
if (doTransformTextures) {
raw.TransformTextures(texturesTransform);
}
raw.Condense();
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, 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;
}
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;
}

92
src/mathfu.h Normal file
View File

@ -0,0 +1,92 @@
/**
* 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_MATHFU_H
#define FBX2GLTF_MATHFU_H
#include <fbxsdk.h>
#include <mathfu/vector.h>
#include <mathfu/matrix.h>
#include <mathfu/quaternion.h>
#include <mathfu/rect.h>
/**
* All the mathfu:: implementations of our core data types.
*/
template<class T, int d>
struct Bounds
{
mathfu::Vector<T, d> min;
mathfu::Vector<T, d> max;
bool initialized = false;
void Clear() {
min = mathfu::Vector<T, d>();
max = mathfu::Vector<T, d>();
initialized = false;
}
void AddPoint(mathfu::Vector<T, d> p) {
if (initialized) {
for (int ii = 0; ii < d; ii ++) {
min(ii) = std::min(min(ii), p(ii));
max(ii) = std::max(max(ii), p(ii));
}
} else {
min = p;
max = p;
initialized = true;
}
}
};
typedef mathfu::Vector<uint16_t, 4> Vec4i;
typedef mathfu::Matrix<uint16_t, 4> Mat4i;
typedef mathfu::Vector<float, 2> Vec2f;
typedef mathfu::Vector<float, 3> Vec3f;
typedef mathfu::Vector<float, 4> Vec4f;
typedef mathfu::Matrix<float, 2> Mat2f;
typedef mathfu::Matrix<float, 3> Mat3f;
typedef mathfu::Matrix<float, 4> Mat4f;
typedef mathfu::Quaternion<float> Quatf;
typedef Bounds<float, 3> Boundsf;
template<class T, int d> static inline std::vector<T> toStdVec(mathfu::Vector <T, d> vec)
{
std::vector<T> result(d);
for (int ii = 0; ii < d; ii ++) {
result[ii] = vec[ii];
}
return result;
}
template<class T> std::vector<T> toStdVec(mathfu::Quaternion<T> quat) {
return std::vector<T> { quat.vector()[0], quat.vector()[1], quat.vector()[2], quat.scalar() };
}
static inline Vec3f toVec3f(const FbxVector4 &v) {
return Vec3f((float) v[0], (float) v[1], (float) v[2]);
}
static inline Mat4f toMat4f(const FbxAMatrix &m) {
auto result = Mat4f();
for (int row = 0; row < 4; row ++) {
for (int col = 0; col < 4; col ++) {
result(row, col) = (float) m[row][col];
}
}
return result;
}
static inline Quatf toQuatf(const FbxQuaternion &q) {
return Quatf((float) q[3], (float) q[0], (float) q[1], (float) q[2]);
}
#endif //FBX2GLTF_MATHFU_H

176
src/utils/File_Utils.cpp Normal file
View File

@ -0,0 +1,176 @@
/**
* 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 <string>
#include <vector>
#include <stdint.h>
#include <stdio.h>
#if defined( __unix__ ) || defined ( __APPLE__ )
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <dirent.h>
#include <errno.h>
#define _getcwd getcwd
#define _mkdir(a) mkdir(a, 0777)
#elif defined( _WIN32 )
#include <direct.h>
#include <process.h>
#else
#include <direct.h>
#include <process.h>
#endif
#include <sys/stat.h>
#include "FBX2glTF.h"
#include "String_Utils.h"
namespace FileUtils {
std::string GetCurrentFolder()
{
char cwd[Gltf::StringUtils::MAX_PATH_LENGTH];
if (!_getcwd(cwd, sizeof(cwd))) {
return std::string();
}
cwd[sizeof(cwd) - 1] = '\0';
Gltf::StringUtils::GetCleanPath(cwd, cwd, Gltf::StringUtils::PATH_UNIX);
const size_t length = strlen(cwd);
if (cwd[length - 1] != '/' && length < Gltf::StringUtils::MAX_PATH_LENGTH - 1) {
cwd[length + 0] = '/';
cwd[length + 1] = '\0';
}
return std::string(cwd);
}
bool FolderExists(const char *folderPath)
{
#if defined( __unix__ ) || defined( __APPLE__ )
DIR *dir = opendir(folderPath);
if (dir) {
closedir(dir);
return true;
}
return false;
#else
const DWORD ftyp = GetFileAttributesA( folderPath );
if ( ftyp == INVALID_FILE_ATTRIBUTES )
{
return false; // bad path
}
return ( ftyp & FILE_ATTRIBUTE_DIRECTORY ) != 0;
#endif
}
bool MatchExtension(const char *fileExtension, const char *matchExtensions)
{
if (matchExtensions[0] == '\0') {
return true;
}
if (fileExtension[0] == '.') {
fileExtension++;
}
for (const char *end = matchExtensions; end[0] != '\0';) {
for (; end[0] == ';'; end++) {}
const char *ext = end;
for (; end[0] != ';' && end[0] != '\0'; end++) {}
#if defined( __unix__ ) || defined( __APPLE__ )
if (strncasecmp(fileExtension, ext, end - ext) == 0)
#else
if ( _strnicmp( fileExtension, ext, end - ext ) == 0 )
#endif
{
return true;
}
}
return false;
}
void ListFolderFiles(std::vector<std::string> &fileList, const char *folder, const char *matchExtensions)
{
#if defined( __unix__ ) || defined( __APPLE__ )
DIR *dir = opendir(folder);
if (dir == nullptr) {
return;
}
for (;;) {
struct dirent *dp = readdir(dir);
if (dp == nullptr) {
break;
}
if (dp->d_type == DT_DIR) {
continue;
}
const char *fileName = dp->d_name;
const char *fileExt = strrchr(fileName, '.');
if (!fileExt || !MatchExtension(fileExt, matchExtensions)) {
continue;
}
fileList.emplace_back(fileName);
}
closedir(dir);
#else
std::string pathStr = folder;
pathStr += "*";
WIN32_FIND_DATA FindFileData;
HANDLE hFind = FindFirstFile( pathStr.c_str(), &FindFileData );
if ( hFind != INVALID_HANDLE_VALUE )
{
do
{
if ((FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) {
std::string fileName = FindFileData.cFileName;
std::string::size_type extPos = fileName.rfind('.');
if (extPos != std::string::npos &&
MatchExtension(fileName.substr(extPos + 1).c_str(), matchExtensions)) {
fileList.push_back(fileName);
}
}
} while ( FindNextFile( hFind, &FindFileData ) );
FindClose( hFind );
}
#endif
}
bool CreatePath(const char *path)
{
#if defined( __unix__ ) || defined( __APPLE__ )
Gltf::StringUtils::PathSeparator separator = Gltf::StringUtils::PATH_UNIX;
#else
Gltf::StringUtils::PathSeparator separator = Gltf::StringUtils::PATH_WIN;
#endif
std::string folder = Gltf::StringUtils::GetFolderString(path);
std::string clean = Gltf::StringUtils::GetCleanPathString(folder, separator);
std::string build = clean;
for (int i = 0; i < clean.length(); i ++) {
if (clean[i] == separator && i > 0) {
build[i] = '\0';
if (i > 1 || build[1] != ':') {
if (_mkdir(build.c_str()) != 0 && errno != EEXIST) {
return false;
}
}
}
build[i] = clean[i];
}
return true;
}
}

24
src/utils/File_Utils.h Normal file
View File

@ -0,0 +1,24 @@
/**
* 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 __FILE_UTILS_H__
#define __FILE_UTILS_H__
namespace FileUtils {
std::string GetCurrentFolder();
bool FolderExists(const char *folderPath);
bool MatchExtension(const char *fileExtension, const char *matchExtensions);
void ListFolderFiles(std::vector<std::string> &fileList, const char *folder, const char *matchExtensions);
bool CreatePath(const char *path);
}
#endif // !__FILE_UTILS_H__

137
src/utils/Image_Utils.cpp Normal file
View File

@ -0,0 +1,137 @@
/**
* 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 <stdint.h>
#include <sys/stat.h>
#include <string>
#include <vector>
#include <cstring>
#include <algorithm>
#include "File_Utils.h"
#include "Image_Utils.h"
// https://www.w3.org/TR/PNG/#11IHDR
const int PNG_IHDR_CHUNK_START = 8;
const int PNG_HEADER_SIZE = PNG_IHDR_CHUNK_START + 8 + 13;
enum PNG_ColorType
{
Grayscale = 0,
RGB = 2,
Palette = 3,
GrayscaleAlpha = 4,
RGBA = 6
};
static bool ImageIsPNG(char const *fileName, unsigned char const *buffer)
{
if ((buffer[0] == 0x89 && buffer[1] == 0x50 && buffer[2] == 0x4E && buffer[3] == 0x47)) {
// first chunk must be an IHDR
return (
buffer[PNG_IHDR_CHUNK_START + 4] == 'I' &&
buffer[PNG_IHDR_CHUNK_START + 5] == 'H' &&
buffer[PNG_IHDR_CHUNK_START + 6] == 'D' &&
buffer[PNG_IHDR_CHUNK_START + 7] == 'R');
}
return false;
}
static ImageProperties PNGProperties(unsigned char const *buffer)
{
// Extract (big-endian) properties from the PNG IHDR
ImageProperties properties;
properties.width =
(buffer[PNG_IHDR_CHUNK_START + 8] & 0xFF) << 24 | (buffer[PNG_IHDR_CHUNK_START + 9] & 0xFF) << 16 |
(buffer[PNG_IHDR_CHUNK_START + 10] & 0xFF) << 8 | (buffer[PNG_IHDR_CHUNK_START + 11] & 0xFF);
properties.height =
(buffer[PNG_IHDR_CHUNK_START + 12] & 0xFF) << 24 | (buffer[PNG_IHDR_CHUNK_START + 13] & 0xFF) << 16 |
(buffer[PNG_IHDR_CHUNK_START + 14] & 0xFF) << 8 | (buffer[PNG_IHDR_CHUNK_START + 15] & 0xFF);
properties.occlusion = (buffer[PNG_IHDR_CHUNK_START + 17] == RGBA) ? IMAGE_TRANSPARENT : IMAGE_OPAQUE;
return properties;
}
// header is broken into multiple structs because TGA headers are packed
struct TGA_HeaderStart_t
{
char IDLength;
char ColorMapType;
char DataTypeCode;
};
struct TGA_HeaderColor_t
{
short int ColorMapOrigin;
short int ColorMapLength;
char ColorMapDepth;
};
struct TGA_HeaderOrigin_t
{
short int XOrigin;
short int YOrigin;
short Width;
short Height;
char BitsPerPixel;
char ImageDescriptor;
};
const int TGA_HEADER_SIZE = 8 + sizeof(TGA_HeaderOrigin_t);
static bool ImageIsTGA(char const *fileName, unsigned char const *buffer)
{
// TGA's have pretty ambiguous header so we simply check their file extension
size_t len = strlen(fileName);
if (len < 4) {
return false;
}
#if defined( __unix__ ) || defined( __APPLE__ )
return strcasecmp(fileName + len - 4, ".tga") == 0;
#else
return _stricmp( fileName + len - 4, ".tga" ) == 0;
#endif
}
static ImageProperties TGAProperties(unsigned char const *buffer)
{
const TGA_HeaderOrigin_t *header = reinterpret_cast< const TGA_HeaderOrigin_t * >( &(buffer[8]));
ImageProperties properties;
properties.width = header->Width;
properties.height = header->Height;
properties.occlusion = (header->BitsPerPixel == 32) ? IMAGE_TRANSPARENT : IMAGE_OPAQUE;
return properties;
}
ImageProperties GetImageProperties(char const *filePath)
{
ImageProperties defaultProperties;
defaultProperties.width = 1;
defaultProperties.height = 1;
defaultProperties.occlusion = IMAGE_OPAQUE;
FILE *f = fopen(filePath, "rb");
if (f == nullptr) {
return defaultProperties;
}
// This assumes every image file is at least as large as the largest header.
const int maxHeaderSize = std::max(PNG_HEADER_SIZE, TGA_HEADER_SIZE);
unsigned char buffer[maxHeaderSize];
if (fread(buffer, (size_t) maxHeaderSize, 1, f) == 1) {
if (ImageIsPNG(filePath, buffer)) {
return PNGProperties(buffer);
} else if (ImageIsTGA(filePath, buffer)) {
return TGAProperties(buffer);
}
}
return defaultProperties;
}

29
src/utils/Image_Utils.h Normal file
View File

@ -0,0 +1,29 @@
/**
* 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 __IMAGE_UTILS_H__
#define __IMAGE_UTILS_H__
enum ImageOcclusion
{
IMAGE_OPAQUE,
IMAGE_PERFORATED,
IMAGE_TRANSPARENT
};
struct ImageProperties
{
int width = 0;
int height = 0;
ImageOcclusion occlusion = IMAGE_OPAQUE;
};
ImageProperties GetImageProperties(char const *filePath);
#endif // !__IMAGE_UTILS_H__

View File

@ -0,0 +1,21 @@
/**
* 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 "String_Utils.h"
namespace Gltf {
namespace StringUtils {
PathSeparator operator!(const PathSeparator &s)
{
return (s == PATH_WIN) ? PATH_UNIX : PATH_WIN;
}
}
}// namespace Gltf

85
src/utils/String_Utils.h Normal file
View File

@ -0,0 +1,85 @@
/**
* 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 _STRING_UTILS_H__
#define _STRING_UTILS_H__
#include <string>
#include <cstdio>
#include <cstring>
#include <cstdarg>
#if defined( _MSC_VER )
#define strncasecmp _strnicmp
#define strcasecmp _stricmp
#endif
namespace Gltf // TODO replace
{
namespace StringUtils {
static const unsigned int MAX_PATH_LENGTH = 1024;
enum PathSeparator
{
PATH_WIN = '\\',
PATH_UNIX = '/'
};
PathSeparator operator!(const PathSeparator &s);
inline const std::string GetCleanPathString(const std::string &path, const PathSeparator separator = PATH_WIN)
{
std::string cleanPath = path;
for (size_t s = cleanPath.find(!separator, 0); s != std::string::npos; s = cleanPath.find(!separator, s)) {
cleanPath[s] = separator;
}
return cleanPath;
}
template<size_t size>
inline void GetCleanPath(char (&dest)[size], const char *path, const PathSeparator separator = PATH_WIN)
{
size_t len = size - 1;
strncpy(dest, path, len);
char *destPtr = dest;
while ((destPtr = strchr(destPtr, !separator)) != nullptr) {
*destPtr = separator;
}
}
inline const std::string GetFolderString(const std::string &path)
{
size_t s = path.rfind(PATH_WIN);
s = (s != std::string::npos) ? s : path.rfind(PATH_UNIX);
return path.substr(0, s + 1);
}
inline const std::string GetFileNameString(const std::string &path)
{
size_t s = path.rfind(PATH_WIN);
s = (s != std::string::npos) ? s : path.rfind(PATH_UNIX);
return path.substr(s + 1, std::string::npos);
}
inline const std::string GetFileBaseString(const std::string &path)
{
const std::string fileName = GetFileNameString(path);
return fileName.substr(0, fileName.rfind('.')).c_str();
}
inline int CompareNoCase(const std::string &s1, const std::string &s2)
{
return strncasecmp(s1.c_str(), s2.c_str(), MAX_PATH_LENGTH);
}
} // StringUtils
}// namespace Gltf
#endif // _STRING_UTILS_H__