Initial commit.
This commit is contained in:
commit
276a0dfb86
|
@ -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)
|
|
@ -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.
|
|
@ -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()
|
|
@ -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.
|
|
@ -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
|
||||
Facebook’s rights in any patent claims that are infringed by (i) modifications
|
||||
to the Software made by you or any third party or (ii) the Software in
|
||||
combination with any software or other technology.
|
||||
|
||||
The license granted hereunder will terminate, automatically and without notice,
|
||||
if you (or any of your subsidiaries, corporate affiliates or agents) initiate
|
||||
directly or indirectly, or take a direct financial interest in, any Patent
|
||||
Assertion: (i) against Facebook or any of its subsidiaries or corporate
|
||||
affiliates, (ii) against any party if such Patent Assertion arises in whole or
|
||||
in part from any software, technology, product or service of Facebook or any of
|
||||
its subsidiaries or corporate affiliates, or (iii) against any party relating
|
||||
to the Software. Notwithstanding the foregoing, if Facebook or any of its
|
||||
subsidiaries or corporate affiliates files a lawsuit alleging patent
|
||||
infringement against you in the first instance, and you respond by filing a
|
||||
patent infringement counterclaim in that lawsuit against that party that is
|
||||
unrelated to the Software, the license granted hereunder will not terminate
|
||||
under section (i) of this paragraph due to such counterclaim.
|
||||
|
||||
A "Necessary Claim" is a claim of a patent owned by Facebook that is
|
||||
necessarily infringed by the Software standing alone.
|
||||
|
||||
A "Patent Assertion" is any lawsuit or other action alleging direct, indirect,
|
||||
or contributory infringement or inducement to infringe any patent, including a
|
||||
cross-claim or counterclaim.
|
|
@ -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.
|
|
@ -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.
|
|
@ -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
|
||||
Facebook’s rights in any patent claims that are infringed by (i) modifications
|
||||
to the Software made by you or any third party or (ii) the Software in
|
||||
combination with any software or other technology.
|
||||
|
||||
The license granted hereunder will terminate, automatically and without notice,
|
||||
if you (or any of your subsidiaries, corporate affiliates or agents) initiate
|
||||
directly or indirectly, or take a direct financial interest in, any Patent
|
||||
Assertion: (i) against Facebook or any of its subsidiaries or corporate
|
||||
affiliates, (ii) against any party if such Patent Assertion arises in whole or
|
||||
in part from any software, technology, product or service of Facebook or any of
|
||||
its subsidiaries or corporate affiliates, or (iii) against any party relating
|
||||
to the Software. Notwithstanding the foregoing, if Facebook or any of its
|
||||
subsidiaries or corporate affiliates files a lawsuit alleging patent
|
||||
infringement against you in the first instance, and you respond by filing a
|
||||
patent infringement counterclaim in that lawsuit against that party that is
|
||||
unrelated to the Software, the license granted hereunder will not terminate
|
||||
under section (i) of this paragraph due to such counterclaim.
|
||||
|
||||
A "Necessary Claim" is a claim of a patent owned by Facebook that is
|
||||
necessarily infringed by the Software standing alone.
|
||||
|
||||
A "Patent Assertion" is any lawsuit or other action alleging direct, indirect,
|
||||
or contributory infringement or inducement to infringe any patent, including a
|
||||
cross-claim or counterclaim.
|
|
@ -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.
|
||||
|
|
@ -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
|
|
@ -0,0 +1 @@
|
|||
%~dp0\Windows\FBX2glTF --binary %1 %2
|
|
@ -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}"
|
|
@ -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;
|
|
@ -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"
|
||||
]
|
||||
}
|
|
@ -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__
|
File diff suppressed because it is too large
Load Diff
|
@ -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__
|
|
@ -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);
|
||||
}
|
|
@ -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__
|
|
@ -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;
|
||||
}
|
|
@ -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__
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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 },
|
||||
};
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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 }
|
||||
};
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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 }
|
||||
};
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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 }
|
||||
}}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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 }
|
||||
};
|
||||
}
|
|
@ -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
|
|
@ -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 }
|
||||
};
|
||||
}
|
|
@ -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
|
|
@ -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 }
|
||||
};
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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__
|
|
@ -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;
|
||||
}
|
|
@ -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__
|
|
@ -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
|
|
@ -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__
|
||||
|
Loading…
Reference in New Issue