Compare commits

...

226 Commits
v0.9.0 ... main

Author SHA1 Message Date
Pär Winzell 739ee5db94
Kill AppVeyor builds.
We'll move to CircleCI when we find an active maintainer who wants to push new releases.
2020-04-20 12:10:09 -07:00
Benjamin MICHEL 37f992321e Fixed the npm library output path 2019-10-09 09:48:14 -07:00
Pär Winzell be627fa228 Minor README updates.
- Better 'tar' invocation that doesn't require GNU tar.
- No need for GIT_LFS_SKIP_SMUDGE anymore.
- Apply VSCode's MarkDown formatting suggestions.
2019-10-09 09:35:37 -07:00
vfxgordon 1d735698ba Morph target names output to mesh.extras.targetNames (#231) 2019-10-07 16:46:38 -07:00
Pär Winzell 5c3229d6cf Patch release, screwed up 0.9.7. 2019-09-11 11:16:50 -07:00
Pär Winzell 3c08169510 Prepare patch release 0.9.7. 2019-08-10 09:33:02 -07:00
Pär Winzell b9c7d0a400
Tweak shell script snippet in README.
Fixes #214.
2019-08-08 08:54:39 -07:00
Jesse Vander Does 44d2d5bf97 Write `.gltf` to parent directory & infer binary from `.glb` (#193)
If the output extension is gltf then set the output directory to the parent. The binary and any associated textures will be written to this directory.
2019-08-07 09:35:17 -07:00
Pär Winzell 24092a80bb Use the correct camera property.
Looks like FieldOfViewX/FieldOfViewY are only well-defined when
aperture mode is eHorizAndVert. Use FieldOfView instead.

Fixes #209.
2019-08-06 17:48:45 -07:00
Pär Winzell 31e3665862 Minor cleanup. 2019-08-06 17:22:30 -07:00
Pär Winzell 7b39358f46 Be explicit in our list-initialisation.
This caused https://github.com/facebookincubator/FBX2glTF/issues/218
in ways that I honestly don't fully understand, and I believe only under
Windows, with Release-level optimisation turned on.

It's possible we should drop all these initializer lists, and replace
them with simple and explicit initialising constructors.

Fixes #218.
2019-08-06 16:36:19 -07:00
Leslie Leigh 648fdfb944 Add option "--fbx-tmp-dir" (#219)
Merge code from @shrinktofit that allows control over where temporary files are created.

(Most commonly the .fbm directory where the SDK extracts embedded resources.)
2019-08-06 08:39:59 -07:00
Par Winzell 6437d02e5f Prepare for NPM release. 2019-07-31 11:10:33 -07:00
Mr.B b09bdef836 Add Facebook official document into README (#216)
Add Facebook official document into README
2019-07-29 15:00:15 -07:00
“Brandon 3daf2b712a Fix issue where code segfaults when surface material is missing
Ignores missing material and warns user of bad FBX

Only edge case is handled while previously working models should operate as before
2019-07-18 09:35:44 -07:00
Pär Winzell 70136c6f53
Fix iconv on mac (#208)
The FBX SDK is compiled against a regular libiconv on Windows on Linux (with symbols libconv_open() etc) but on Mac, they compiled it against Apple's modified libiconv, which renames those functions. Annoyingly this means we need to explicitly decline the Conan libiconv package, for that platform only.
2019-06-11 22:42:31 -07:00
Par Winzell ec98db9762 Strip nonsense include directories. 2019-06-11 14:31:30 -07:00
Pär Winzell 43e3d05440 Drop all GIT LFS assets from repo.
Test will continue to live on here: https://github.com/zellski/FBX2glTF-Tests
2019-06-09 17:44:01 -07:00
Par Winzell 9853625ba1 Fix logic error.
Thanks @C0lumbo. This fixes #202.
2019-06-04 22:48:25 -07:00
Pär Winzell 3afd9f3266 Get zlib, libxml2 and iconv all from Conan. 2019-06-04 22:44:50 -07:00
Pär Winzell 5b475476ba Don't clobber README.md on de-tar. 2019-06-01 12:54:56 -07:00
Pär Winzell 25ef9167ed Simplify build, document it somewhat. 2019-06-01 12:48:16 -07:00
Pär Winzell a89b7d526f
Update README.md 2019-06-01 11:37:39 -07:00
Pär Winzell 4e645ddfca
Tell AppVeyor to install the FBX SDK from elsewhere (#201)
We'll fetch release tarballs from another GitHub repo.
2019-06-01 11:35:12 -07:00
Pär Winzell e411f4165a
Tell Travis to install the FBX SDK from elsewhere (#200)
We'll fetch release tarballs from another GitHub repo.
2019-05-31 22:59:10 -07:00
Pär Winzell 83e1cdc512 The FBX SDK doesn't belong in this repo. 2019-05-31 18:04:01 -07:00
Pär Winzell 449c5ed59d
Upload release artifacts for Windows (#197) 2019-05-16 12:39:39 -07:00
Par Winzell 7683d208cb Prepare for merge with master. 2019-05-16 12:39:08 -07:00
Par Winzell 75f811491c auth token encrypted against FB acct 2019-05-16 11:59:16 -07:00
Pär Winzell 8f51ee4016 Conan doesn't like adding remotes twice. 2019-05-16 11:11:51 -07:00
Par Winzell b8b470395b Build all branches, cache Conan downloads. 2019-05-16 10:58:56 -07:00
Par Winzell 07c69ce0e7 Don't skip tags, whitelist test branch. 2019-05-16 10:32:41 -07:00
Pär Winzell a66049f2e8 Conan doesn't like adding remotes twice. 2019-05-15 00:33:41 -07:00
Pär Winzell 959d799482 Try AppVeyor auto-upload release builds. 2019-05-14 22:09:11 -07:00
Jesse Vander Does feb2768b61 Added sdk to dockerignore
I've added the sdk directory to the docker ignore.
With the SDK ignored, we can install the SDK before
adding the project source. This allows the docker image
to preserve most of the layer caches when source code is
changed.
2019-05-14 07:16:28 -07:00
Pär Winzell 3c57e62ae5
Have Travis upload build artifacts (#194) 2019-05-14 07:14:26 -07:00
Pär Winzell 0ba34ee7f5 settings.libcxx -> settings.compiler.libcxx 2019-05-11 16:38:29 -07:00
Pär Winzell 80e4a96d9b Constrain our GCC build tweak to GCC.
Only for GCC (5.*) is it necessary to instruct the compiler to avoid
falling back on a very old libstd ABI.
2019-05-11 15:08:35 -07:00
Pär Winzell 6b8490a6ae
Update README.md 2019-05-07 17:27:22 -07:00
Jesse Vander Does 1100e09111 Dockerize FBX2glTF (#184)
Added dockerfile and docker-compose.
2019-05-07 17:20:38 -07:00
Par Winzell 1cc57c90b2 Update internal databases. 2019-05-07 15:32:35 -07:00
Par Winzell f0eaa1efb2 Travis CI Support. 2019-05-07 15:08:12 -07:00
Par Winzell ed43cacb33 Ensure Conan downloads modern binaries.
On a system with GCC 5.* Conan will conservatively choose 'libstdc++'
over 'libstdc++11' for compiler.libcxx, and then proceed to download
libraries compiled with the older ABI.

Meanwhile, though, our own CMake setup dictates the use of the modern
ABI, and the result is an application binary with ABI mismatches that
yield SIGSEGVs almost immediately.

Here, we guard against erronous invocations, and gently push the user
towards sending in the right explicit override for their system.
2019-05-07 13:15:54 -07:00
Par Winzell 33ef6fef2e We now have a good FBXSDK_SDKS default. 2019-05-05 22:18:08 -07:00
Pär Winzell b1960feb64 Let's try AppVeyor... 2019-05-05 10:16:21 -07:00
Par Winzell 8e5ded122e Argh. This is the actual commit intended. 2019-05-05 01:21:25 -07:00
Par Winzell df00e0538d Ignore animation 'takes', calculate frame intervals ourselves.
Lifted from comment in source:

Individual animations are often concatenated on the timeline, and the
only certain way to identify precisely what interval they occupy is to
depth-traverse the entire animation stack, and examine the actual keys.

There is a deprecated concept of an "animation take" which is meant to
provide precisely this time interval information, but the data is not
actually derived by the SDK from source-of-truth data structures, but
rather provided directly by the FBX exporter, and not sanity checked.

Some exporters calculate it correctly. Others do not. In any case, we
now ignore it completely.
2019-05-05 01:00:53 -07:00
Par Winzell a07cabd1ec eTotalOne is strictly a subset of aNormalize. 2019-05-03 21:48:51 -07:00
Pär Winzell 4b501431fe
License -> BSD (#186)
Relicense FBX2glTF as BSD.
2019-05-03 16:13:18 -07:00
Pär Winzell 7fc7120487
Azure CI build support. (#182)
Azure build support.
2019-04-23 12:44:03 -07:00
Par Winzell 366e904b70 FB Copyright compliance. 2019-04-22 08:45:05 -07:00
Pär Winzell 7970914dd0 Delete spurious (and utterly broken) log line. 2019-04-20 22:08:24 -07:00
Par Winzell 5ba62a726c these were never meant to be here 2019-04-19 23:58:13 -07:00
Pär Winzell 7dd8438c78
Embrace Conan, use it to grab boost::filesystem. (#180)
With this, we are able to get rid of all the increasingly broken file
system utility code, and trust boost::filesystem to handle all the
cross-platform complexity.

The first version of this PR centred around C++17 & std::filesystem,
but support remains too elusive; it seems works out of the box in
Visual Studio (especially 2019), but is entirely missing from the Mac
dclang, and even with GCC 8.0 it requires an explicit '-l c++fs'.

Luckily the std:: version is almost exactly the boost:: version (not
surprising) so when the world's caught up, we can ditch Boost and go
all stdlib.

Setting up Conan requires a bit of work; we'll want to document the
details in the README.
2019-04-19 23:54:11 -07:00
Simon 4bb4bdbac1 animation framerate option 2019-04-18 09:24:38 -07:00
Par Winzell 4ed510ca8e Add required Facebook copyright headers. 2019-04-17 18:06:09 -07:00
hhalen 13f463d336 Fix skinning weight normalization. Previous code would call Normalized() on a Vec4f containing the weights. This normalizes the vector, i.e. makes the length of the vector equal to 1.0. For skinning weights what we want is the sum of the weights to be 1.0, which is a different. This commit fixes that. 2019-04-09 10:12:11 -07:00
Par Winzell 9c76e5ade1 Stop testing if conversion failed. 2019-04-07 18:33:27 -07:00
Pär Winzell 769454e964 This all needs rewriting, but at least let's make it not crash on Windows. 2019-04-07 15:18:13 -07:00
Par Winzell 11398f6acb This is an expected case and shouldn't be logged. 2019-04-03 11:57:20 -07:00
Par Winzell 910a8bfdd0 Left-over debug line. 2019-04-03 10:37:13 -07:00
Par Winzell 9e6ab31e37 Oops, forgot to actually commit json.hpp. 2019-04-03 09:58:48 -07:00
Par Winzell c6ed20a45a Commit JSON header-only dependency to our repo. 2019-03-29 20:05:01 -07:00
Par Winzell 1f21a50cc9 Fix the previous fixes.
Alright, less haphazardly now after the two previous botched commits,
this fixes mistakes and bugs made a year or more in the past:
 - We now always pass the metallic and roughness factors through all
   the way to the glTF layer. They should not be multiplied into the
   generated textures, and so they should be present as-is in glTF
   output.
 - We only generate the AO/Rough/Net combined texture if at least two
   of the constituent textures are present.
 - We only reference the generated texture as an occlusionTexture if
   there really was an occlusion map present (and it had non-trivial
   pixels).

It's also now ridiculously clear that:
 - The material conversion section is long and tortured and it's very
   easy to screw up. It should be broken into functions and classes.
 - We urgely need a real regression suite, and we need to model some
   artificial FBX files that test both realistic scenarios and edge-
   case permutations.
2019-03-29 19:56:33 -07:00
Par Winzell 46f6234af5 Fix previous fix. 2019-03-29 10:29:59 -07:00
Par Winzell d2f7d0e270 Generally repair old broken logic around aoMetRough texture. 2019-03-29 09:59:57 -07:00
Pär Winzell ca78a31b22
Merge pull request #171 from mtostenson/master
Only create aoMetRoughTex when metalnesss or roughness texture provided.

Following up with tweak commit.
2019-03-29 09:31:44 -07:00
Michael Tostenson 9c114ffa49 Only create aoMetRoughTex when metalnesss or roughness texture provided 2019-03-26 17:28:10 -07:00
abbaswasim 678a9bf844 Fix typo (#167)
Corrects spelling of `roughness`
2019-03-11 12:45:38 -07:00
Pär Winzell b8d587d2c7
Add link to bleeding-edge builds. 2019-02-25 09:31:15 -08:00
Par Winzell a596073f04 Identify materials by unique ID, not name.
At the end of the various material/mesh transformations we do, we were still using a ridiculously simplistic method of mapping RawMaterial to glTF MaterialData.

This switches to using FBX's GetUniqueID(), which should be the law of the land in general. Other model entities may need further investigation as well.
2019-02-24 19:32:13 -08:00
Par Winzell 4780efdc6f Extend tests a bit. 2019-02-24 17:42:13 -08:00
Par Winzell a3841fe7cd Formatting-only change. 2019-02-23 23:51:05 -08:00
Par Winzell e83d495fdc Fix Draco. How long was this broken? 2019-02-22 23:02:14 -08:00
Par Winzell 713ee2f879 Tiny type tweak. 2019-02-22 17:15:42 -08:00
Par Winzell 9d4a599d0c Pin STB header files at working versions. 2019-02-22 17:11:34 -08:00
Par Winzell f170772f8a Use gltf-validator that knows about KHR_lights_punctual. 2019-02-21 12:50:09 -08:00
Par Winzell 70c3823eb7 Fix 'spot' light export. 2019-02-21 12:45:02 -08:00
Pär Winzell 5ec3184a8a
Mocha integration test suite (#150)
This moves ./npm to ./npm/fbx2gltf, and introduces a Mocha / TypeScript based testing harness. 

We're also adding a GIT LFS dependency, but only for people who want to run tests. We're using LFS to store the many FBX models we want to convert to GLB as part of the integration test suite.
2019-01-25 16:01:08 -08:00
Par Winzell ffd6af3142 Fixes to lights. Oops. 2019-01-25 15:14:37 -08:00
Par Winzell ce2a9f8d85 Handle 'inverse roughness' flag. 2019-01-25 14:33:53 -08:00
Par Winzell d0883136d3 First cleaned-up, consistent physical material implementation.
There's still work to be done here, mainly:
 - Convert BUMP to NORMAL
 - Apply "roughness inversion" to roughness map

But it now informs the user of unsupported properties their model uses,
and generally works correctly!
2019-01-25 10:51:30 -08:00
Par Winzell a3bee2e42a Tiny order tweak. 2019-01-24 17:03:24 -08:00
Par Winzell 12025de179 Don't discriminate textures only by FBX logical name.
We don't want to use the logical texture name from the FBX to distinguish separate textures. It seems entirely valid for distinct textures to have the same name. It's not completely clear what's best to use instead, but adding fileLocation seems reasonable. It can be blank, for textures that don't exist on the filesystem –– but that's a pretty weird edge case, and even then we have both 'usage' and 'name' to distinguish.
2019-01-24 16:59:36 -08:00
Par Winzell d451aa73f3 Delete obsolete classes, poke at Physical Material support.
Looks like I never did the required cleanup after my accidental commit back in August.
This deletes the materials classes that were obsoleted back then, and comments out the
large swathes of PhysicalMaterial code that's not currently doing anything (i.e. all the
materials properties that glTF can't currently represent.)
2019-01-16 13:52:33 -08:00
Par Winzell 1328a4b96f Another, better argument parsing fix. 2019-01-11 20:47:08 -08:00
Par Winzell 5c07d274c3 Fix broken U/V flipping. 2019-01-11 19:38:33 -08:00
Par Winzell 1145defda3 Use case-insensitive matching on old shader types. 2018-12-19 14:22:58 -08:00
Pär Winzell 5730d1c301
Apply clang-format to all our source. (#149)
Apply clang-format to all our source.
2018-12-18 23:30:29 -08:00
Pär Winzell be1b75431d
Switch from CXXOPTS to CLI11. (#148)
We want to move to auto-formatting all our code, and it just seemed impossible
to make cxxopts usage tidy under clang-format's dominion. While trying to work
out its quirks, I realised that CLI11 did everything I wanted much better, and
so we've switched.

We're also going to chuck the usage of ExternalProject_Add(), at least for the
simplest use cases such as single-header include files. We'll just commit them
directly; that's kind of the whole point.

The one discipline we'll maintain is that commits that involve third_party/
should be as self-contained as possible (without breaking the app).
2018-12-17 16:13:53 -08:00
Par Winzell 5389d848e2 Implement KHR_lights_punctual. 2018-12-17 10:21:43 -08:00
Par Winzell 09089a7d79 Create a compilation database. 2018-12-16 12:41:40 -08:00
Par Winzell 49892c5e83 Abstract out user property serialisation. 2018-12-14 00:05:43 -08:00
Graham Wihlidal f2cb6a1010 Fix various tabs vs spaces formatting inconsistencies 2018-12-13 23:00:05 -08:00
Graham Wihlidal 8b081a5fc3 Implemented support for exporting user properties assigned to materials 2018-12-13 23:00:05 -08:00
Par Winzell 7374bcf34f No default argument in implementation. 2018-12-12 12:46:45 -08:00
Par Winzell f71be404f4 Remaining fixes for 2019.2. 2018-12-07 09:52:38 -08:00
Pär Winzell 62bb8710c0 Fix cross-platform path handling.
This is a recreation of the PR @robertlong submitted long ago here:
https://github.com/facebookincubator/FBX2glTF/pull/97

Refactors and whitespace conflicts made this easier.

There is still a substantial rewrite of the texture-loading and
file-path handling pending, for sometime soon.
2018-12-07 09:40:21 -08:00
Par Winzell c1faf6b822 I'm going to stab someone. 2018-12-04 10:01:07 -08:00
Par Winzell 90e5bef116 Better iconv handling. Jeez, CMake... 2018-12-04 09:54:08 -08:00
Par Winzell 3e0aa3565c Don't require iConv; it can be builtin. 2018-12-04 09:29:39 -08:00
Par Winzell 073f06aa13 Do the right thing with ICONV. Needed for Mac now. 2018-12-04 08:56:05 -08:00
Par Winzell e990432ecf Require 2019.2 henceforth. 2018-12-03 22:19:25 -08:00
Par Winzell fe2aa11516 Mark third-party headers as such.
This avoids spammy compiler warnings in code we don't care about.
2018-12-02 19:03:02 -08:00
Par Winzell 2029fa7277 Delete unused variable. 2018-12-02 19:02:24 -08:00
Par Winzell a65628f56f Specify that we want std::isnan().
Depending on platform, multiple versions of isnan() can easily be floating around, causing compilation headaches. Luckily we can always rely on the standard library implementation.
2018-12-02 19:00:42 -08:00
Pär Winzell c34f861f69 Don't force RTTI for one measly cast. 2018-10-28 16:03:52 -07:00
Michael Ranieri d38ccd935f fix fov computation from fbxsdk (#133)
* fix fov computation from fbxsdk
2018-10-22 18:32:20 -07:00
Michael Ranieri efd404764d Fix camera rotation from FBX, fix illegal memory access of binary data 2018-10-19 01:05:48 -07:00
Par Winzell 6e527563cd Add more Copyright boilerplate. 2018-10-15 15:24:07 -07:00
Par Winzell 0c94cb5706 Add Facebook's generic Code of Conduct file. 2018-10-15 15:19:36 -07:00
Pär Winzell 319c0fe460 Wrap user properties in a command line option. 2018-10-14 21:46:49 -07:00
Pär Winzell bbbba646de Massive reorganisation.
This finishes the first phase of the FBX2glTF refactor, breaking utility classes out where things were getting too monolithic.

There is an equally important cleanup phase coming where we wrench all the various parts of this code, including the historical ones that we've rarely touched as yet, into a single C++ style paradigm, and modernise everything to C++11 at least.

But for now, we're just picking the pieces back on the floor so we can push 0.9.6 out. It's been far too long since a release.
2018-10-13 20:15:12 -07:00
Par Winzell 2a4da70de0 Make the JSON library ambiently available. 2018-10-13 19:25:18 -07:00
Michael Ranieri 2e03a3cdea Transcribe FBX Custom Properties 2018-10-13 19:23:48 -07:00
Par Winzell f646be2e47 Both DIFFUSE and ALBEDO can dictate transparency. 2018-10-02 16:59:41 -07:00
Artem Titoulenko 5282f693f9 Blend shape keys to accessor names (#122)
Map blendshape keys to accessor names
2018-09-25 16:09:18 -07:00
Pär Winzell 52de0d20d8
Typo fix on Fbx2Raw.cpp
Thanks @fire for the report. Fixes #128.
2018-09-24 20:24:30 -07:00
Par Winzell f988cb7aa7 Missing bits, oops.
Did not mean to commit/push the current state of master. But rather than
mess up source control history with a force push, I'll just try to hurry
to a stable point.
2018-09-13 17:21:57 -07:00
Par Winzell 7d36e7f4d7 Rearrange, extract, clean up.
Hopefully without unintentional changes to functionality. This renames header
files to .hpp, imposes a gltf/raw/fbx directory structure, extracts standalone
chunks of Fbx2Raw into distinct files, and undoes some particularly egregious
mistakes from when I knew even less C++ than I do now.

This is in anticipation of implementing 3ds Max's "Physical Material".
2018-09-13 16:55:24 -07:00
Par Winzell b475fbead4 Accept multiple SDK versions as valid. 2018-09-13 12:30:06 -07:00
Par Winzell 891cf668b9 Fix order of linked libraries. 2018-09-13 10:39:21 -07:00
Pär Winzell 2fc254ed79
Update example command-line switch 2018-09-08 11:35:10 -07:00
Matt Brown 9c84e14ddc fixed typo in Describe function for specular 2018-08-19 18:24:52 -07:00
Pär Winzell bdcf16f042
Trivial typo fix.
Thanks to @ale64bit for pointing this out.
2018-08-19 18:24:16 -07:00
Pär Winzell 7995f7b341 Resolve _WIN32 isnan() fiasco completely. 2018-05-27 13:28:49 -07:00
Par Winzell d3f9a269ba Customisable Draco encoding options. 2018-05-08 09:01:58 -07:00
Par Winzell 5788eea8ad Don't tell me you did nothing. 2018-05-03 18:58:58 -07:00
Par Winzell e178a75be3 Start tracking and using FBX2glTF version. 2018-05-03 18:51:35 -07:00
Par Winzell 3c8cad4730 Fix crash from dangling surface reference.
The condense operation recreates the vectors of surfaces, materials,
textures and vertices so as to exclude anything that isn't referenced
explicitly by a triangle. In the process, we must take care that
references from other properties are cleared out.

This fixes the case when a node references a mesh by id, and then the
mesh is deleted because no triangle references. TODO: go through other
properties and make sure the same problem doesn't exist there.

It is also possible that these vectors should be replaced by maps, at
least for the elements that (now) have unique IDs.
2018-05-03 14:44:43 -07:00
Par Winzell f530af8bf4 Use std:: whenever possible. 2018-05-03 08:21:42 -07:00
Darío Hereñú 447333a16a Typo on string #239, #241, #244 2018-04-26 07:21:02 -07:00
Par Winzell 68983ad0d0 Fix skinning edge case.
A mesh with a single (skinning) deformer which had zero clusters would
erroneously register as skinned, leading GetRoodNode() to an assertion
failure. Fixed.
2018-04-08 17:54:42 -07:00
Par Winzell cb76a49b82 List all sources in CMakeLists.txt.
If nothing else, it helps IDEs do the right thing.
2018-04-08 15:47:27 -07:00
Par Winzell 3bfe56b71f Minor Windows fixes. 2018-03-29 06:46:19 -07:00
Par Winzell 542550192c Bump NPM version, 2018-03-29 06:24:46 -07:00
Par Winzell 9174fe2f50 Fixes and improvements to shininess -> roughness. 2018-03-29 05:49:40 -07:00
Par Winzell b3492194cf Slightly better Blinn/Phong -> PBR.
We're still gunshy from our previous attempts at coming up with metalilc
and roughness values from diffuse/specular/shininess, but this should be
safe: a high shininess means a low roughness, and vice versa.
2018-03-28 10:44:51 -07:00
Par Winzell 3f1590a26b Don't be tricksy if we're already done.
The FBX SDK looks for our textures and often finds them. It helpfully
tells us exactly where they are. Let's not throw that information away
and demand that the textures only exist in precisely the folders we are
aware of.
2018-03-27 20:00:48 -07:00
Par Winzell e992aac1d9 Mark glTF materials with FBX origins.
Because we make a best-effort attempt to convert materials on the old
form -- like :ambert and Phong -- to PBR materials, it can be beneficial
to the consumer of the asset to know if the asset was intentionally
authored as PBR, or if it was a conversion.

The precise details of this information is specific to the intersection
of FBX and glTF, so we're not going to bother proposing extensions; we
just drop something into the extras field, e.g.

    "materials": [
        {
            "name": "Troll_Water",
            "alphaMode": "OPAQUE",
            "extras": {
                "fromFBX": {
                    "shadingModel": "Metallic/Roughness",
                    "isTruePBR": true
                }
            },
        // ... and so on.

The possible values for shadingModel are:
    "<unknown>"
    "Constant"
    "Lambert"
    "Blinn"
    "Phong"
    "Metallic/Roughness"

Currently isTruePBR is true for the final entry, false for the other.
However, we may well add more PBR shading models in the future, so if
you intend to use this feature to look for true PBR, use the derived
property.
2018-03-27 19:31:01 -07:00
Par Winzell ca12a38afe Always eliminate the 0.01 factor. 2018-03-27 14:54:36 -07:00
PLAINCONCEPTS\davila 4ec8c8e34d Change the way that unit conversion affects the scene. 2018-03-27 14:17:33 -07:00
Par Winzell f6ce7e345d Keep our byte-writing within bounds.
Now that we're writing both 16-bit and 32-bit integers, it's starting to
matter a little more how we slam even scalars into memory. This is maybe
not the fastest way to accomplish this, and I'm not crazy about the way
GLType works in general, but it does have the virtues of clarity and
expediency.
2018-03-27 11:26:50 -07:00
Pär Winzell 93d1385b1e
Fix typo & markdown error. 2018-03-18 09:42:04 -07:00
Josh Faust e92261f491 Fix Image_Utils.h to include <algorithm> 2018-03-01 10:50:57 -08:00
Par Winzell c72cf85acf Small oops left over from glTF 1.0. 2018-02-25 19:40:22 -08:00
Pär Winzell dc8f199d54
Support occlusion texture all the way. (#81)
By oversight we had not included occlusionTexture in the core
MaterialData. While we're at it, bake occlusion into the red channel of
the merged metallic/roughness texture.
2018-02-25 18:35:04 -08:00
Pär Winzell 362b8bd761
Clamp FBX values to glTF specifications (#80)
There seem to be few constraints on what values FBX properties can take. By contrast, glTF constrains e.g. common material factors to lie in [0, 1]. We take a simple approach and just clamp.
2018-02-25 18:13:19 -08:00
Pär Winzell a8f194f793
better texture transparency test (#78)
Previous to this, a PNG that was on RGBA format would cause its
corresponding texture to be flagged as transparent. This is very
silly. We now iterate over the bytes, and if any is not 255, THEN
there's alpha.
2018-02-25 17:28:26 -08:00
Pär Winzell a984f7bf37
Drop the Gltf:: namespace prefix. (#77) 2018-02-25 17:19:19 -08:00
Pär Winzell 3f796dd90f
Use a tiny PNG for a fallback texture image, not a GIF (#74) 2018-02-23 19:39:08 -08:00
Pär Winzell f1982e6ca3 Bump NPM version. 2018-02-19 20:59:44 -08:00
Par Winzell da5d606c93 Documentation tweaks. 2018-02-19 20:15:01 -08:00
Par Winzell 608c6f1797 Add support for 32-bit indices.
This was way overdue. Breaking up large meshes into many 65535-vertex
primitives can save a few bytes, but it's really a lot of complication
for minor benefit.

With this change the user can force short or long indices, and the
default is to use shorts for smaller meshes and longs for longer.
2018-02-19 13:58:09 -08:00
Par Winzell 0deaf186a8 Some basic .gitignore stuff. 2018-02-19 09:32:25 -08:00
Par Winzell 7c0715c4ad Clean house in material extensions.
- KHR_materials_common never had a real life in the glTF 2.0 world. One
day we may see a new extension for Phong/Blinn/Lambert.
- PBR_specular_glossiness is a poor fit for PBS StingRay (the only real
source of PBR we have) and has no advantage over PBR_metallic_roughness.
- The conversion we were doing for traditional materials to PBR made no
sense. Revert to a very simple formula: diffuse -> baseColor, simple
reasonable constants for metallic & roughness.
2018-02-19 08:57:19 -08:00
Par Winzell 67bb9372d7 Optionally compute normals from geometry.
The user can now ask for normals to be computed NEVER (can easily cause
broken glTF if the source isn't perfect), MISSING (when the mesh simply
lacks normals), BROKEN (only emptuy normals are replaced), or
ALWAYS (perhaps if the normals in the source are junk).
2018-02-18 22:19:24 -08:00
Par Winzell b95c50a72f Code to repair normals. 2018-02-18 14:42:30 -08:00
Par Winzell 20b1bd7051 Semi-randomly tweak Phong->PBR conversion.
I stole expressions from Gary Hsu's PBR conversion routines here:

3606e79717/extensions/Khronos/KHR_materials_pbrSpecularGlossiness/examples/convert-between-workflows/js/three.pbrUtilities.js

which is experimental enough as it is, but I had gone further into the
domain of madness and uses this with *old* diffuse/specular values, not
PBR specular/glossness.

As a result a lot of old content was coming up with 100% metal values
quite often, which in turn means completely ignoring diffuse when
assembling a new base colour...

I should rip out this whole conversion. But not just now...
2018-02-04 14:42:57 -08:00
Par Winzell 54c3b04fce Cope with degenerate node T/R/S.
It's technically valid for e.g. scale to have a zero dimension, which in
turn wreaks havoc on the rotation quaternion we get from the FBX SDK.

The simplest solution is to just leave any T/R/S vector out of the glTF
if it has any NaN component.
2018-02-02 11:12:06 -08:00
Par Winzell a3989249f3 Consistent naming of unlit extension use. 2018-02-02 07:08:00 -08:00
Par Winzell 8ab9459d6a Don't override encoder's best guess. 2018-02-02 06:58:47 -08:00
Par Winzell cbd2e3f00d Use newer Draco. 2018-02-02 06:58:17 -08:00
Robert Long 74479426a7 KHR_materials_unlit (#61)
KHR_materials_unlit support.
2018-02-02 06:57:36 -08:00
Par Winzell 41f8a5ef03 Improvements in texture merging.
Be more flexible about reading various input formats (most especially
varying numbers of channels), and stop outputting RGBA PNGs for textures
that don't need it.

I'm not sure JPG generation ever worked right. But now it does.
2017-12-22 16:02:48 -08:00
David Ávila a2d5c7d87b Fix duplicate name issues (#51)
Fix the naming issues. Now the nodes are identified by pNode->GetUniqueID(), instead of its name. All dictionaries and references to nodes are replaced by its id, instead of its name.
2017-12-06 10:43:18 -08:00
Pär Winzell 13367dfc66
Merge pull request #50 from msfeldstein/master
Pass camera reference through propertly as index uints, not as (glTF 1.0 era) name strings.
2017-12-04 22:18:49 -08:00
Mike Feldstein 9d1096f3ff Properly pass camera data through 2017-12-04 14:35:57 -08:00
Pär Winzell cc0a6e5b89
Merge pull request #48 from davilovick/fix_normal_issues 2017-12-03 11:58:09 -08:00
David Ávila Membrives 1a46ccf7ff Include meshScaling in normal transform 2017-12-03 20:47:41 +01:00
David Ávila Membrives 9c2e485cfc Apply the correct transform on normals (remove transform & scaling components) 2017-12-03 15:32:53 +01:00
Pär Winzell a9c4fb1bd0 Fix for Windows. (And generally a good idea.) 2017-11-30 23:15:13 -08:00
Par Winzell 21305baa93 NPM-publish of patch release 0.9.3. 2017-11-30 23:02:22 -08:00
Pär Winzell fdf7bb3336 Support for Stingray PBS material definitions (#47)
This adds the first FBX PBR import path. Materials that have been
exported via the Stingray PBS preset should be picked up as native
metallic/roughness, and exported essentially 1:1 to the glTF output.

In more detail, this commit:
- (Re)introduces the STB header libraries as a dependency. We currently
use it for reading and writing images. In time we may need a more
dedicated PNG compression library.
- Generalizes FbxMaterialAccess to return different subclasses of
  FbxMaterialInfo; currently FbxRoughMetMaterialInfo and
  FbxTraditionalMaterialInfo.
  - FbxTraditionalMaterialInfo is populated from the canonical
    FbxSurfaceMaterial classes.
  - FbxRoughMetMaterialInfo is currently populated through the Stingray
    PBS set of properties, further documented in the code.
- RawMaterial was in turn generalized to feature a pluggable,
  type-specific RawMatProps struct; current implementations are,
  unsurprisingly, RawTraditionalMatProps and RawMetRoughMatProps. These
  are basically just lists of per-surface constants, e.g. diffuseFactor or
  roughness.
- In the third phase, glTF generation, the bulk of the changes are
  concerned with creating packed textures of the type needed by e.g. the
  metallic-roughness struct, where one colour channel holds roughness and
  the other metallic. This is done with a somewhat pluggable "map source
  pixels to destination pixel" mechanism. More work will likely be needed
  here in the future to accomodate more demanding mappings.

There's also a lot of code to convert from one representation to
another. The most useful, but also the least well-supported conversion,
is from old workflow (diffuse, specular, shininess) to
metallic/roughness. Going from PBR spec/gloss to PBR met/rough is hard
enough, but we go one step sillier and treat shininess as if it were
glossiness, which it certainly isn't. More work is needed here! But it's
still a fun proof of concept of sorts, and perhaps for some people it's
useful to just get *something* into the PBR world.
2017-11-30 22:23:50 -08:00
Par Winzell e2e0d741a2 Style tweaks. 2017-11-28 01:22:43 -08:00
David Ávila fada9e45ee Modify FBX2glTF to allow that several nodes share the same mesh. (#46) 2017-11-28 01:16:01 -08:00
Pär Winzell f61814f7c9
Pin Draco at 1.2.0, for now
As @robertlong discovered, there are compilation errors in newer Draco patch releases under Win/VS 2017.
2017-11-21 19:10:45 -08:00
Par Winzell 423458a841 Construct JSON with explicit key order.
We are at liberty to order our JSON any way we like (by spec) and we can
improve readability a lot by doing so. By default, this JSON library
uses an unordered map for objects, but it's relatively easy to switch in
a FiFo map that keeps track of the insertion order.
2017-11-17 11:37:19 -08:00
Par Winzell c70ded31d5 Explicit versions of dependencies.
As we approach 1.0.0 we can't just download whatever someone's slammed
into the master branch of our various open source dependencies.
2017-11-16 18:19:52 -08:00
Par Winzell bcbfdae6be Drop misleading warning.
It's perfectly fine for materials to have neither diffuse texture nor
vertex colours. This dates back to a time when the tool had more limited
use cases.

To compensate: https://github.com/facebookincubator/FBX2glTF/issues/43
2017-11-16 14:10:30 -08:00
Pär Winzell 5eb36702b5
Make blend shape normals/tangents opt-in. (#40)
The FBX SDK absolutely claims that there is a normal layer to each
FbxShape, with non-trivial data, even when the corresponding FBX file,
upon visual inspection, explicitly contains nothing but zeroes. The only
conclusion I can draw is that the SDK is computing normals from
geometry, without being asked to, which seems kind of sketchy.

These computed normals are often not at all what the artist wanted, they
take up a lot of space -- often pointlessly, since if they're computed,
we could just as well compute them on the client -- and at least in the
case of three.js their inclusion uses up many of the precious 8 morph
target slots in the shader.

So, they are now opt-in, at least until we can solve the mystery of just
what goes on under the hood in the SDK.
2017-11-14 14:36:44 -08:00
Par Winzell e73c2f46e2 Only use vertex alpha if color layer is present.
This was previously flagging far too many materials as transparent.
2017-11-14 13:47:34 -08:00
Pär Winzell 7f8746f56e
Minor README tweaks. 2017-11-09 20:15:00 -08:00
Par Winzell 412b98b9ab Cope with animation edge case, don't NPE. 2017-11-08 11:25:55 -08:00
Par Winzell 145f0d84a1 Minor normal/tangent cleanup. 2017-11-07 13:59:13 -08:00
Pär Winzell 3eeebf4599
Blendshape tangets & normals, cleanup. (#35)
Turns out Maya was always including normals in the FBX export, they were just a bit trickier to get to than originally surmised. We need to go through the proper element access formalities that takes mapping and reference modes into account.

Luckily we already have a helper class for this, so let's lean on that.
2017-11-07 13:32:54 -08:00
Pär Winzell 5786fc9f0b One more dynamic_cast to drop. 2017-11-06 23:14:35 -08:00
Par Winzell 4f8ddffdf7 Better calculation of glTF opacity.
At the glTF level, transparency is a scalar; we just throw away any
color information in FBX TransparentColor. We still need to calculate
our total opacity from it, however. This is the right formula, which
additionally matches the deprecated (but still populated, by the Maya
exporter) 'Opacity' property.
2017-11-06 14:48:16 -08:00
Par Winzell 3b7872f4f1 NPM-publish of patch release 0.9.2. 2017-11-06 13:26:12 -08:00
Par Winzell d2a20ee5cb Fixes for Linux/GCC.
Between Linux, Darwin and Windows keeping me honest, I am slowly learning C++.
2017-11-06 12:20:15 -08:00
Pär Winzell 5eb3779ad8 Don't use dynamic_cast, it needs RTTI. 2017-11-06 00:17:39 -08:00
Pär Winzell 60f18ae635 Diffuse texture transparency takes priority.
If the diffuse texture is opaque, it doesn't matter if a couple of
vertexes are semi-transparent.
2017-11-05 23:09:59 -08:00
Pär Winzell 7ecc6e5743 Fix build on Windows.
No vectors of const objects. Got it. Makes sense.
2017-11-05 23:08:32 -08:00
Pär Winzell 9ae36088b4
Blend Shape / Morph Target Support (#33)
This adds blend shape / morph target functionality.

At the FBX level, a mesh can have a number of deformers associated with it. One such deformer type is the blend shape. A blend shape is a collection of channels, which do all the work. A channel can consist of a single target shape (the simple case) or multiple (a progressive morph). In the latter case, the artist has created in-between shapes, the assumption being that linear interpolation between a beginning shape and an end shape would be too crude. Each such target shape contains a complete set of new positions for each vertex of the deformed base mesh.

(It's also supposed to be optionally a complete set of normals and tangents, but I've yet to see that work right; they always come through as zeroes. This is something to investigate in the future.)

So the number of glTF morph targets in a mesh is the total number of FBX target shapes associated with channels associated with blend shape deformers associated with that mesh! Yikes.

The per-vertex data of each such target shape is added to a vector in RawVertex. A side effect of this is that vertices that participate in blend shapes must be made unique to the mesh in question, as opposed to general vertices which are shared across multiple surfaces.

Blend Shape based animations become identical glTF morph target animations..

Fixes #17.
2017-11-05 00:06:24 -07:00
Pär Winzell 5e0f05261c
Sort triangles with vertex transparency separately. (#32)
Lean on the excellent pre-existing support for creating multiple glTF
meshes from a single FBX mesh based on material type. All the triangles
with (at least one) non-opaque vertex get flagged as transparent
material. They will all go separately in their own mesh after the
CreateMaterialModels() gauntlet.

Fixes #25.
2017-11-04 20:10:28 -07:00
Par Winzell 14150269a0 Switch FbxDouble4 uses to FbxVector4, clean up. 2017-11-03 21:48:41 -07:00
Par Winzell 0fcd00cb5b Get rid of some pointless copying. 2017-10-31 20:18:14 -07:00
Pär Winzell 21138b6eff
Merge pull request #30 from facebookincubator/check-for-exe
Use .exe suffix on Windows.
2017-10-31 11:49:10 -07:00
Michael Bayne 7f9aa3318a Use .exe suffix on Windows. 2017-10-31 11:33:32 -07:00
Par Winzell 58518f5e00 Tiny log string fix. 2017-10-29 00:34:20 -07:00
Pär Winzell 34800d056a
Merge pull request #28 from facebookincubator/bug-fix-einherit-warning
Fix and improve inheritance warnings.
2017-10-27 15:52:35 -07:00
Par Winzell 075dc4a8c3 Fix and improve inheritance warnings.
We were warnings against eInheritRSrs, which is actually the one type of
ineritance we're good with. It's eInheritRrSs we should freak out about.

That said, no need to do it for the root node -- at that point there is
no global transform to worry about.
2017-10-27 15:47:49 -07:00
Par Winzell 22354fd7ce Use standard library log2f. 2017-10-27 15:31:32 -07:00
Par Winzell 1829f6d6a6 RawSurface.skinRigid not used. 2017-10-27 15:31:32 -07:00
Pär Winzell 6137694595
Merge pull request #26 from facebookincubator/error-fix
Fix error reporting.
2017-10-27 12:28:15 -07:00
Michael Bayne a03e252866 Fix error reporting. 2017-10-27 12:21:13 -07:00
Par Winzell b1076fa373 Default opts to empty array if it's not supplied.
Fixes a NPE on line 33 for a common case.
2017-10-25 13:24:30 -07:00
Par Winzell 91d11c88b7 Do the right thing with empty directory path.
When we convert a file that's in our CWD, on Unix the folder component
of the path will simply be "", whereas opendir() wants ".".

I want to take another more substantial pass at texture resolution, once
we're out of urgent bugfix mode.
2017-10-25 12:02:25 -07:00
Par Winzell db3f232fc9 Cope with geometry opting out of geometry.
Some FBX files have index arrays that contain -1 (indeed, that are
nothing but negative ones). Presumably the intention is to specify "no
material". In any case, let's not segfault.
2017-10-24 20:57:55 -07:00
Pär Winzell 4e0f5e4fcf Merge pull request #15 from robertlong/master
Skip invalid animations with 0 channels.
2017-10-24 11:32:48 -07:00
Par Winzell fb7dae380b Actually initialize the dracoMesh pointer. 2017-10-24 09:15:59 -07:00
Par Winzell e417fbe90e Fix the --keep-attribute option. 2017-10-24 08:50:46 -07:00
Par Winzell f535effb8b NPM-publish of patch release 0.9.1. 2017-10-23 20:13:17 -07:00
Pär Winzell 72eb620d87 Copy texture files to output dir when appropriate. (#23)
When we've successfully located a referenced texture image on the local
filesystem and we're generating non-binary, non-embedded output, copy
the source folder wholesale into the destination directory.

This means the output folder is always a full, free-standing deployment,
one that can be dragged into e.g. https://gltf-viewer.donmccurdy.com/
2017-10-23 19:30:51 -07:00
Par Winzell 5580dcfa92 Whitespace tweaks in log output. 2017-10-23 18:59:32 -07:00
Par Winzell 5a4959d86c Don't mention texture here.
This is a general-purpose function now.
2017-10-23 17:29:00 -07:00
Pär Winzell dfb026033d Document the new v-flipping default. (#22)
The READMEs need updating, too.
2017-10-22 16:43:20 -07:00
Pär Winzell d46aeb3d46 Merge pull request #21 from zellski/feat-default-to-flipped-textures
Default to vertically flipping texture coordinates.
2017-10-22 16:32:50 -07:00
Par Winzell 998b7719cb Default to vertically flipping texture coordinates.
In the FBX world, (0, 0) is generally the lower left. By the glTF
specification, (0, 0) is the upper left. The only recourse is to
literally flip all texture files (generally unwise) or to remap the UV
space.

Is this confusing in an artist-to-engineer workflow? Maybe. But it's the
best option, and it seems reasonably easy to communicate.

To request unflipped coordinates, send in a --no-flip-v command switch.
2017-10-22 16:27:43 -07:00
Pär Winzell e0e81404f9 Multiple tweaks to README. (#19)
Update README.md with recent developments, add various relevant links to the GitHub project, clean up the language, various misc line edits.
2017-10-21 10:45:56 -07:00
Pär Winzell 8cf7f446b7 Further improvemens to texture resolution. (#16)
* Further improvemens to texture resolution.

- Move towards std::string over char * and FbxString where convenient,
- Make a clear distinction between textures whose image files have been
  located and those who haven't; warn early in the latter case.
- Extend RawTexture so we always know logical name in FBX, original file
  name in FBX, and inferred location in local filesystem.
- In non-binary mode, simply output the inferred local file basename as
  the URI; this will be the correct relative path as long as the texture
  files are located next to the .gltf and .bin files.

Primary remaining urge for a follow-up PR:

- We should be copying texture image files into the .gltf output folder,
  but before that we should switch to an off-the-shelf cross-platform
  file manipulation library like https://github.com/cginternals/cppfs.
  When we make that transition, all this texture resolution code will
  undergo another refactoring.
2017-10-20 09:42:39 -07:00
Robert Long 454cb7c864 Skip animations with 0 channels. 2017-10-19 16:27:47 -07:00
Michael Bayne 946f12361c Numerous improvements to Node API.
- Removed the shell scripts. We now invoke everything straight from Node.

- Allow passing command line arguments to the tool via the Node API.

- Require .glb or .gltf extension because the tool is automatically going to
  add that extension anyway, so we strip it off and add it back to shield the
  user from this weirdness. The tool may eventually stop adding an extension
  (and perhaps just validate it) and we can simplify our code.

- Automatically add --binary option if the requested target file is a .glb.

This also renames the bin/Windows directory to bin/Windows_NT, which is
unfortunate but that matches os.type(). This ultimately comes from uname and
that's what Windows chose to return. Let's just live with this historical
accident rather than try to paper over it.
2017-10-19 13:44:54 -07:00
Michael Bayne 9085b8d284 Add usage docs to README.
Fixes #8.
2017-10-19 11:01:51 -07:00
Par Winzell c194843239 More helpful README update. 2017-10-19 09:15:25 -07:00
Par Winzell 606d31997c Supply JSON-critical comma. 2017-10-19 09:15:01 -07:00
131 changed files with 34754 additions and 5369 deletions

87
.clang-format Normal file
View File

@ -0,0 +1,87 @@
---
AccessModifierOffset: -1
AlignAfterOpenBracket: AlwaysBreak
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlinesLeft: true
AlignOperands: false
AlignTrailingComments: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: true
BinPackArguments: false
BinPackParameters: false
BraceWrapping:
AfterClass: false
AfterControlStatement: false
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Attach
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: false
ColumnLimit: 100
CommentPragmas: '^ IWYU pragma:'
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat: false
ForEachMacros: [ FOR_EACH, FOR_EACH_ENUMERATE, FOR_EACH_KV, FOR_EACH_R, FOR_EACH_RANGE, FOR_EACH_RANGE_R, ]
IncludeCategories:
- Regex: '^<.*\.h(pp)?>'
Priority: 1
- Regex: '^<.*'
Priority: 2
- Regex: '.*'
Priority: 3
IndentCaseLabels: true
IndentWidth: 2
IndentWrappedFunctionNames: false
KeepEmptyLinesAtTheStartOfBlocks: false
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBlockIndentWidth: 2
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: false
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 200
PointerAlignment: Left
ReflowComments: true
SortIncludes: true
SpaceAfterCStyleCast: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeParens: ControlStatements
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Cpp11
TabWidth: 8
UseTab: Never
...

3
.dockerignore Normal file
View File

@ -0,0 +1,3 @@
.dockerignore
Dockerfile
sdk

14
.gitattributes vendored Normal file
View File

@ -0,0 +1,14 @@
# FBX SDK
*.a filter=lfs diff=lfs merge=lfs -text
*.dylib filter=lfs diff=lfs merge=lfs -text
*.so filter=lfs diff=lfs merge=lfs -text
*.dll filter=lfs diff=lfs merge=lfs -text
*.lib filter=lfs diff=lfs merge=lfs -text
# TEST FILES
*.glb filter=lfs diff=lfs merge=lfs -text
*.fbx filter=lfs diff=lfs merge=lfs -text
*.tga filter=lfs diff=lfs merge=lfs -text
*.png filter=lfs diff=lfs merge=lfs -text
*.jpg filter=lfs diff=lfs merge=lfs -text

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
npm/fbx2gltf/bin/Darwin/FBX2glTF
npm/fbx2gltf/bin/Linux/FBX2glTF
npm/fbx2gltf/bin/Windows_NT/FBX2glTF.exe
npm/fbx2gltf/node_modules/
npm/tests/node_modules/
npm/tests/test/*.js
npm/tests/test/*.js.map

63
.travis.yml Normal file
View File

@ -0,0 +1,63 @@
git:
lfs_skip_smudge: true
matrix:
include:
- os: linux
dist: xenial
env:
- APP_NAME="FBX2glTF-linux-x64"
- CONAN_CONFIG="-s compiler.libcxx=libstdc++11"
- FBXSDK_TARBALL="https://github.com/zellski/FBXSDK-Linux/archive/2019.2.tar.gz"
- TAR_WILDCARDS="--wildcards"
- os: osx
osx_image: xcode10.2
env:
- APP_NAME="FBX2glTF-darwin-x64"
- CONAN_CONFIG="-s compiler=apple-clang -s compiler.version=10.0 -s compiler.libcxx=libc++"
- FBXSDK_TARBALL="https://github.com/zellski/FBXSDK-Darwin/archive/2019.2.tar.gz"
- TAR_WILDCARDS=""
compiler: gcc
language: generic
#disabled for now
#cache:
# directories:
# - ${HOME}/.conan
addons:
apt:
packages: zstd
homebrew:
packages: zstd
install:
- curl -sL "${FBXSDK_TARBALL}" | tar xz --strip-components=1 ${TAR_WILDCARDS} */sdk
- zstd -d -r --rm sdk
- git clone --depth 1 git://github.com/astropy/ci-helpers.git
- source ci-helpers/travis/setup_conda.sh
- conda config --set always_yes yes
- conda info -a
- conda create -n travis_env python=3.7 pip
- conda activate travis_env
- pip install conan
- conan user
- conan remote add --force bincrafters https://api.bintray.com/conan/bincrafters/public-conan
script:
- conan install . -i build -s build_type=Release ${CONAN_CONFIG}
- conan build . -bf build
- mv build/FBX2glTF build/${APP_NAME}
notifications:
webhooks:
- "https://code.facebook.com/travis/webhook/"
deploy:
provider: releases
api_key:
secure: V9CTmZKM7yvsT/WCesJ/tLTuapSf0oIp73zyZrwID7zQtXaq1QJSna4tWM2T0qeZIYhniH1/mqEr2jZVW1txmYn9ZxUMH1Nmp9zzOGl/q+JlRrJUi6HRUWWhCMz003L90whngyOcGI+T7rHtcVcby4owVsze15SrQqqV74NXI8DYNIbNgQR1Nwmqsrg0QirFPEBaIKDAiKonnRDWKPy2P8vqnN9fLhj00uHLwuvahlKAnWFEbNnFbiRScKifB+Mlo6Pf6r64iikrxS2jBxAgSsvPLkuemWLmaHTeGbJMM82aqh5vGSvgYcExvZi+0RdXeIcBdv/jaivM/xge4aZ+4P+IJoX32ZNCcYFMsqES+a6TztkywMs2k1r5gV6LrTjeXJsINSW+BDFdmrwmkudETc4gelQgkMmEkdCwFHENtZGl65z8HJDQKcu9F8NQlhNU7Z5rwQNLmYccvktSDhwbFSG5eq2kFFfcbVx3ovvn1voRTNnyhhVD2ZnLepSQInAVkZbaLkE90bQ+t9icf8uDdHDn17zOQaAZuecPlSW1y4XUCJnZCi0JPLhdSmQYiF60LHYI6xDneC8pmIz8kCUbk921zu8bJBy7zKHmfHy2vqNlPKuRULRIs5QzY31jf2PVZHzB5zX3KSqx9Dd+3DtgbLX2HLaZnANbkQc0rr1X2kk=
file: build/${APP_NAME}
skip_cleanup: true
on:
repo: facebookincubator/FBX2glTF
tags: true

View File

@ -1,37 +1,68 @@
cmake_minimum_required(VERSION 3.5)
project(FBX2glTF)
set(typical_usage_str
"Example usage:\n\
> mkdir -p build_debug\n\
> conan install . -i build_debug -s build_type=Debug -e FBXSDK_SDKS=/home/zell/FBXSDK\n\
> conan build . -bf build_debug")
if ("${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}")
message(FATAL_ERROR
"Building from within the source tree is not supported.\n"
"Hint: mkdir -p build; cmake -H. -Bbuild; make -Cbuild\n")
"Building from within the source tree is not supported! ${typical_usage_str}")
endif ()
set(CMAKE_CXX_STANDARD 11)
find_package(Threads REQUIRED)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}")
include(ExternalProject)
# FBX
find_package(FBX REQUIRED)
foreach (FBXSDK_VERSION "2019.2")
find_package(FBX)
if (FBXSDK_FOUND)
break()
endif()
endforeach(FBXSDK_VERSION)
if (NOT FBXSDK_FOUND)
message(FATAL_ERROR
"Can't find FBX SDK in either:\n"
" - Mac OS X: ${FBXSDK_APPLE_ROOT}\n"
" - Windows: ${FBXSDK_WINDOWS_ROOT}\n"
" - Linux: ${FBXSDK_LINUX_ROOT}"
)
)
endif()
if(NOT EXISTS "${CMAKE_BINARY_DIR}/conan_paths.cmake")
message(FATAL_ERROR
"The Conan package manager must run ('install') first. ${typical_usage_str}")
endif()
include("${CMAKE_BINARY_DIR}/conan_paths.cmake")
set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
find_package(Threads REQUIRED)
list(INSERT CMAKE_MODULE_PATH 0 "${CMAKE_BINARY_DIR}")
# stuff we get from Conan
find_package(boost_filesystem MODULE REQUIRED)
find_package(boost_optional MODULE REQUIRED)
find_package(libxml2 MODULE REQUIRED)
find_package(zlib MODULE REQUIRED)
find_package(fmt MODULE REQUIRED)
# create a compilation database for e.g. clang-tidy
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# DRACO
ExternalProject_Add(Draco
UPDATE_DISCONNECTED TRUE
GIT_REPOSITORY https://github.com/google/draco
GIT_TAG 1.3.4
PREFIX draco
INSTALL_DIR
CMAKE_ARGS
-DCMAKE_INSTALL_PREFIX=<INSTALL_DIR>
-DCMAKE_INSTALL_PREFIX=<INSTALL_DIR>
-DBUILD_FOR_GLTF=1
)
set(DRACO_INCLUDE_DIR "${CMAKE_BINARY_DIR}/draco/include")
if (WIN32)
@ -44,9 +75,9 @@ endif()
set(mathfu_build_benchmarks OFF CACHE BOOL "")
set(mathfu_build_tests OFF CACHE BOOL "")
ExternalProject_Add(MathFu
UPDATE_DISCONNECTED TRUE
GIT_REPOSITORY https://github.com/google/mathfu
PREFIX mathfu
GIT_REPOSITORY https://github.com/google/mathfu
GIT_TAG v1.1.0
CONFIGURE_COMMAND ${CMAKE_COMMAND} -E echo "Skipping MathFu configure step."
BUILD_COMMAND ${CMAKE_COMMAND} -E echo "Skipping MathFu build step."
INSTALL_COMMAND ${CMAKE_COMMAND} -E echo "Skipping MathFu install step."
@ -55,124 +86,161 @@ set(MATHFU_INCLUDE_DIRS
"${CMAKE_BINARY_DIR}/mathfu/src/MathFu/include/"
"${CMAKE_BINARY_DIR}/mathfu/src/MathFu/dependencies/vectorial/include")
# JSON
ExternalProject_Add(Json
UPDATE_DISCONNECTED TRUE
GIT_REPOSITORY https://github.com/nlohmann/json
PREFIX json
CONFIGURE_COMMAND ${CMAKE_COMMAND} -E echo "Skipping JSON configure step."
BUILD_COMMAND ${CMAKE_COMMAND} -E echo "Skipping JSON build step."
INSTALL_COMMAND ${CMAKE_COMMAND} -E echo "Skipping JSON install step."
# OrderedMap
ExternalProject_Add(FiFoMap
PREFIX fifo_map
GIT_REPOSITORY https://github.com/nlohmann/fifo_map
CONFIGURE_COMMAND ${CMAKE_COMMAND} -E echo "Skipping FiFoMap configure step."
BUILD_COMMAND ${CMAKE_COMMAND} -E echo "Skipping FiFoMap build step."
INSTALL_COMMAND ${CMAKE_COMMAND} -E echo "Skipping FiFoMap install step."
)
set(JSON_INCLUDE_DIR "${CMAKE_BINARY_DIR}/json/src/Json/src")
set(FIFO_MAP_INCLUDE_DIR "${CMAKE_BINARY_DIR}/fifo_map/src/FiFoMap/src")
# cppcodec
ExternalProject_Add(CPPCodec
UPDATE_DISCONNECTED TRUE
GIT_REPOSITORY https://github.com/tplgy/cppcodec
PREFIX cppcodec
GIT_REPOSITORY https://github.com/tplgy/cppcodec
CONFIGURE_COMMAND ${CMAKE_COMMAND} -E echo "Skipping CPPCodec configure step."
BUILD_COMMAND ${CMAKE_COMMAND} -E echo "Skipping CPPCodec build step."
INSTALL_COMMAND ${CMAKE_COMMAND} -E echo "Skipping CPPCodec install step."
)
set(CPPCODEC_INCLUDE_DIR "${CMAKE_BINARY_DIR}/cppcodec/src/CPPCodec")
# CXXOPTS
ExternalProject_Add(CxxOpts
UPDATE_DISCONNECTED TRUE
GIT_REPOSITORY https://github.com/jarro2783/cxxopts
PREFIX cxxopts
CONFIGURE_COMMAND ${CMAKE_COMMAND} -E echo "Skipping cxxopts configure step."
BUILD_COMMAND ${CMAKE_COMMAND} -E echo "Skipping cxxopts build step."
INSTALL_COMMAND ${CMAKE_COMMAND} -E echo "Skipping cxxopts install step."
)
set(CXXOPTS_INCLUDE_DIR "${CMAKE_BINARY_DIR}/cxxopts/src/CxxOpts/include")
# FMT
ExternalProject_Add(Fmt
UPDATE_DISCONNECTED TRUE
GIT_REPOSITORY https://github.com/fmtlib/fmt
CMAKE_CACHE_ARGS "-DFMT_DOC:BOOL=OFF" "-DFMT_INSTALL:BOOL=ON" "-DFMT_TEST:BOOL=OFF"
CMAKE_ARGS
-DCMAKE_INSTALL_PREFIX=<INSTALL_DIR>
PREFIX fmt
)
set(FMT_INCLUDE_DIR "${CMAKE_BINARY_DIR}/fmt/include")
if (WIN32)
set(FMT_LIB "${CMAKE_BINARY_DIR}/fmt/lib/fmt.lib")
else()
set(FMT_LIB "${CMAKE_BINARY_DIR}/fmt/lib/libfmt.a")
endif()
if (APPLE)
find_library(CF_FRAMEWORK CoreFoundation)
message("CoreFoundation Framework: ${CF_FRAMEWORK}")
set(FRAMEWORKS ${CF_FRAMEWORK})
endif()
set(SOURCE_FILES
set(LIB_SOURCE_FILES
src/FBX2glTF.h
src/fbx/materials/3dsMaxPhysicalMaterial.cpp
src/fbx/materials/FbxMaterials.cpp
src/fbx/materials/FbxMaterials.hpp
src/fbx/materials/RoughnessMetallicMaterials.hpp
src/fbx/materials/StingrayPBSMaterial.cpp
src/fbx/materials/TraditionalMaterials.cpp
src/fbx/materials/TraditionalMaterials.hpp
src/fbx/Fbx2Raw.cpp
src/fbx/Fbx2Raw.hpp
src/fbx/FbxBlendShapesAccess.cpp
src/fbx/FbxBlendShapesAccess.hpp
src/fbx/FbxLayerElementAccess.hpp
src/fbx/FbxSkinningAccess.cpp
src/fbx/FbxSkinningAccess.hpp
src/gltf/Raw2Gltf.cpp
src/gltf/Raw2Gltf.hpp
src/gltf/GltfModel.cpp
src/gltf/GltfModel.hpp
src/gltf/TextureBuilder.cpp
src/gltf/TextureBuilder.hpp
src/gltf/properties/AccessorData.cpp
src/gltf/properties/AccessorData.hpp
src/gltf/properties/AnimationData.cpp
src/gltf/properties/AnimationData.hpp
src/gltf/properties/BufferData.cpp
src/gltf/properties/BufferData.hpp
src/gltf/properties/BufferViewData.cpp
src/gltf/properties/BufferViewData.hpp
src/gltf/properties/CameraData.cpp
src/gltf/properties/CameraData.hpp
src/gltf/properties/ImageData.cpp
src/gltf/properties/ImageData.hpp
src/gltf/properties/LightData.cpp
src/gltf/properties/LightData.hpp
src/gltf/properties/MaterialData.cpp
src/gltf/properties/MaterialData.hpp
src/gltf/properties/MeshData.cpp
src/gltf/properties/MeshData.hpp
src/gltf/properties/NodeData.cpp
src/gltf/properties/NodeData.hpp
src/gltf/properties/PrimitiveData.cpp
src/gltf/properties/PrimitiveData.hpp
src/gltf/properties/SamplerData.hpp
src/gltf/properties/SceneData.cpp
src/gltf/properties/SceneData.hpp
src/gltf/properties/SkinData.cpp
src/gltf/properties/SkinData.hpp
src/gltf/properties/TextureData.cpp
src/gltf/properties/TextureData.hpp
src/mathfu.hpp
src/raw/RawModel.cpp
src/raw/RawModel.hpp
src/utils/File_Utils.cpp
src/utils/File_Utils.hpp
src/utils/Image_Utils.cpp
src/utils/String_Utils.cpp
src/main.cpp
src/Fbx2Raw.cpp
src/Raw2Gltf.cpp
src/RawModel.cpp
src/glTF/BufferData.cpp
src/glTF/MaterialData.cpp
src/glTF/MeshData.cpp
src/glTF/NodeData.cpp
src/glTF/PrimitiveData.cpp
src/glTF/BufferViewData.cpp
src/glTF/BufferViewData.h
src/glTF/AccessorData.cpp
src/glTF/AccessorData.h
src/glTF/ImageData.cpp
src/glTF/TextureData.cpp
src/glTF/SkinData.cpp
src/glTF/AnimationData.cpp
src/glTF/CameraData.cpp
src/glTF/SceneData.cpp
src/utils/Image_Utils.hpp
src/utils/String_Utils.hpp
third_party/CLI11/CLI11.hpp
)
add_executable(FBX2glTF ${SOURCE_FILES})
add_library(libFBX2glTF STATIC ${LIB_SOURCE_FILES})
set_target_properties(libFBX2glTF PROPERTIES OUTPUT_NAME "FBX2glTF")
add_executable(appFBX2glTF src/FBX2glTF.cpp)
set_target_properties(appFBX2glTF PROPERTIES OUTPUT_NAME "FBX2glTF")
add_dependencies(FBX2glTF
add_dependencies(libFBX2glTF
Draco
MathFu
Json
CxxOpts
FiFoMap
CPPCodec
Fmt
)
if (NOT MSVC)
# Disable annoying & spammy warning from FBX SDK header file
target_compile_options(FBX2glTF PRIVATE
target_compile_options(libFBX2glTF PRIVATE
"-Wno-null-dereference"
"-Wunused"
)
target_compile_options(appFBX2glTF PRIVATE
"-Wno-null-dereference"
"-Wunused"
)
endif()
target_link_libraries(FBX2glTF
target_link_libraries(libFBX2glTF
${FRAMEWORKS}
${CMAKE_DL_LIBS}
${CMAKE_THREAD_LIBS_INIT}
boost_filesystem::boost_filesystem
boost_optional::boost_optional
${DRACO_LIB}
${FMT_LIB}
optimized ${FBXSDK_LIBRARY}
debug ${FBXSDK_LIBRARY_DEBUG}
fmt::fmt
libxml2::libxml2
zlib::zlib
${CMAKE_DL_LIBS}
${CMAKE_THREAD_LIBS_INIT}
)
target_include_directories(FBX2glTF PUBLIC
if (APPLE)
find_package(Iconv MODULE REQUIRED)
target_link_libraries(libFBX2glTF Iconv)
else()
find_package(libiconv MODULE REQUIRED)
target_link_libraries(libFBX2glTF libiconv::libiconv)
endif()
target_include_directories(libFBX2glTF PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/src
)
target_include_directories(libFBX2glTF SYSTEM PUBLIC
"third_party/stb"
"third_party/json"
${FBXSDK_INCLUDE_DIR}
${DRACO_INCLUDE_DIR}
${MATHFU_INCLUDE_DIRS}
${JSON_INCLUDE_DIR}
${CXXOPTS_INCLUDE_DIR}
${FIFO_MAP_INCLUDE_DIR}
${CPPCODEC_INCLUDE_DIR}
${FMT_INCLUDE_DIR}
)
install (TARGETS FBX2glTF DESTINATION bin)
target_include_directories(appFBX2glTF PUBLIC
"third_party/CLI11"
)
target_link_libraries(appFBX2glTF libFBX2glTF)
install (TARGETS libFBX2glTF appFBX2glTF
RUNTIME DESTINATION bin
ARCHIVE DESTINATION lib
)

5
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,5 @@
# Code of Conduct
Facebook has adopted a Code of Conduct that we expect project participants to adhere to.
Please read the [full text](https://code.fb.com/codeofconduct/)
so that you can understand what actions will and will not be tolerated.

29
Dockerfile Normal file
View File

@ -0,0 +1,29 @@
FROM ubuntu:16.04
RUN apt-get update && \
apt-get install -y software-properties-common && \
add-apt-repository ppa:jonathonf/python-3.6 && \
add-apt-repository ppa:git-core/ppa && \
apt-get update && \
apt-get install -y python3.6 curl build-essential cmake libxml2-dev zlib1g-dev git && \
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py && python3 get-pip.py && \
pip install conan && \
conan remote add bincrafters https://api.bintray.com/conan/bincrafters/public-conan
# Install FBX SDK
RUN mkdir -p /fbx2gltf/sdk/Linux/2019.2 && \
curl -L https://www.autodesk.com/content/dam/autodesk/www/adn/fbx/20192/fbx20192_fbxsdk_linux.tar.gz -o fbx20192_fbxsdk_linux.tar.gz && \
tar -xvf fbx20192_fbxsdk_linux.tar.gz && \
echo "yes\nn" | ./fbx20192_fbxsdk_linux /fbx2gltf/sdk/Linux/2019.2 && \
rm -rf /fbxsdktemp
COPY . /fbx2gltf
WORKDIR /fbx2gltf
# Build and install
RUN conan install . -i docker-build -s build_type=Release -s compiler=gcc -s compiler.version=5 -s compiler.libcxx=libstdc++11 && \
conan build -bf docker-build . && \
cp docker-build/FBX2glTF /usr/bin && \
cd / && \
rm -rf /fbx2gltf /root/.conan

View File

@ -1,3 +1,5 @@
# Copyright (c) 2014-present, Facebook, Inc.
# All rights reserved.
#
# Helper function for finding the FBX SDK.
# Cribbed & tweaked from https://github.com/floooh/fbxc/
@ -21,27 +23,24 @@ else()
set(ARCH_32 OFF)
endif()
set(FBXSDK_VERSION "2018.1.1" CACHE STRING "Precise version string of FBX SDK to use.")
if (NOT DEFINED FBXSDK_VERSION)
set(FBXSDK_VERSION "2019.2")
endif()
set(_fbxsdk_vstudio_version "vs2015")
set(_fbxsdk_vstudio_version "vs2017")
message("Looking for FBX SDK version: ${FBXSDK_VERSION}")
if (DEFINED FBXSDK_SDKS)
get_filename_component(FBXSDK_SDKS_ABS ${FBXSDK_SDKS} ABSOLUTE)
set(FBXSDK_APPLE_ROOT "${FBXSDK_SDKS_ABS}/Darwin/${FBXSDK_VERSION}")
set(FBXSDK_LINUX_ROOT "${FBXSDK_SDKS_ABS}/Linux/${FBXSDK_VERSION}")
set(FBXSDK_WINDOWS_ROOT "${FBXSDK_SDKS_ABS}/Windows/${FBXSDK_VERSION}")
else()
set(FBXSDK_APPLE_ROOT
"/Applications/Autodesk/FBX SDK/${FBXSDK_VERSION}")
set(FBXSDK_LINUX_ROOT
"/usr")
set(FBXSDK_WINDOWS_ROOT
"C:/Program Files/Autodesk/FBX/FBX SDK/${FBXSDK_VERSION}")
if (NOT DEFINED FBXSDK_SDKS)
set(FBXSDK_SDKS "${CMAKE_CURRENT_SOURCE_DIR}/sdk")
endif()
get_filename_component(FBXSDK_SDKS_ABS ${FBXSDK_SDKS} ABSOLUTE)
set(FBXSDK_APPLE_ROOT "${FBXSDK_SDKS_ABS}/Darwin/${FBXSDK_VERSION}")
set(FBXSDK_LINUX_ROOT "${FBXSDK_SDKS_ABS}/Linux/${FBXSDK_VERSION}")
set(FBXSDK_WINDOWS_ROOT "${FBXSDK_SDKS_ABS}/Windows/${FBXSDK_VERSION}")
if (APPLE)
set(_fbxsdk_root "${FBXSDK_APPLE_ROOT}")
set(_fbxsdk_libdir_debug "lib/clang/debug")
@ -62,11 +61,11 @@ elseif (WIN32)
elseif (UNIX)
set(_fbxsdk_root "${FBXSDK_LINUX_ROOT}")
if (ARCH_32)
set(_fbxsdk_libdir_debug "lib/gcc4/x86/debug")
set(_fbxsdk_libdir_release "lib/gcc4/x86/release")
set(_fbxsdk_libdir_debug "lib/gcc/x86/debug")
set(_fbxsdk_libdir_release "lib/gcc/x86/release")
else()
set(_fbxsdk_libdir_debug "lib/gcc4/x64/debug")
set(_fbxsdk_libdir_release "lib/gcc4/x64/release")
set(_fbxsdk_libdir_debug "lib/gcc/x64/debug")
set(_fbxsdk_libdir_release "lib/gcc/x64/release")
endif()
set(_fbxsdk_libname_debug "libfbxsdk.a")
set(_fbxsdk_libname_release "libfbxsdk.a")

View File

@ -2,7 +2,7 @@ BSD License
For FBX2glTF software
Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

33
PATENTS
View File

@ -1,33 +0,0 @@
Additional Grant of Patent Rights Version 2
"Software" means the FBX2glTF software contributed by Facebook, Inc.
Facebook, Inc. ("Facebook") hereby grants to each recipient of the Software
("you") a perpetual, worldwide, royalty-free, non-exclusive, irrevocable
(subject to the termination provision below) license under any Necessary
Claims, to make, have made, use, sell, offer to sell, import, and otherwise
transfer the Software. For avoidance of doubt, no license is granted under
Facebooks rights in any patent claims that are infringed by (i) modifications
to the Software made by you or any third party or (ii) the Software in
combination with any software or other technology.
The license granted hereunder will terminate, automatically and without notice,
if you (or any of your subsidiaries, corporate affiliates or agents) initiate
directly or indirectly, or take a direct financial interest in, any Patent
Assertion: (i) against Facebook or any of its subsidiaries or corporate
affiliates, (ii) against any party if such Patent Assertion arises in whole or
in part from any software, technology, product or service of Facebook or any of
its subsidiaries or corporate affiliates, or (iii) against any party relating
to the Software. Notwithstanding the foregoing, if Facebook or any of its
subsidiaries or corporate affiliates files a lawsuit alleging patent
infringement against you in the first instance, and you respond by filing a
patent infringement counterclaim in that lawsuit against that party that is
unrelated to the Software, the license granted hereunder will not terminate
under section (i) of this paragraph due to such counterclaim.
A "Necessary Claim" is a claim of a patent owned by Facebook that is
necessarily infringed by the Software standing alone.
A "Patent Assertion" is any lawsuit or other action alleging direct, indirect,
or contributory infringement or inducement to infringe any patent, including a
cross-claim or counterclaim.

348
README.md
View File

@ -1,148 +1,315 @@
# FBX2glTF
[![License](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause)
This is a command line tool for converting 3D model assets on Autodesk's
venerable [FBX](https://www.autodesk.com/products/fbx/overview) format to
[glTF 2.0](https://github.com/KhronosGroup/glTF/tree/master/specification/2.0),
a modern runtime asset delivery format.
## Building & Running
Precompiled binaries releases for Windows, Mac OS X and Linux may be
found [here](https://github.com/facebookincubator/FBX2glTF/releases).
This tool has been tested on Linux, Mac OS X and Windows. It requires CMake 3.5+
and a reasonably C++11 compliant toolchain.
Bleeding-edge binaries for Windows may be found [here](https://ci.appveyor.com/project/Facebook/fbx2gltf/build/artifacts). Linux and Mac OS X to come; meanwhile, you can [build your own](#building-it-on-your-own).
[![Build Status](https://travis-ci.com/facebookincubator/FBX2glTF.svg?branch=master)](https://travis-ci.com/facebookincubator/FBX2glTF)
[![Build status](https://ci.appveyor.com/api/projects/status/5mq4vbc44vmyec4w?svg=true)](https://ci.appveyor.com/project/Facebook/fbx2gltf)
## Running
The tool can be invoked like so:
```
> FBX2glTF ~/models/butterfly.fbx
```
Or perhaps, as part of a more complex pipeline:
```
> FBX2glTF --binary --draco --verbose \
--input ~/models/source/butterfly.fbx \
--output ~/models/target/butterfly.glb
```
There are also some friendly & hands-on instructions available [over at Facebook](https://developers.facebook.com/docs/sharing/3d-posts/glb-tutorials/#convert-from-fbx).
### CLI Switches
You can always run the binary with --help to see what options it takes:
```
FBX2glTF 0.9.7: Generate a glTF 2.0 representation of an FBX model.
Usage: FBX2glTF [OPTIONS] [FBX Model]
Positionals:
FBX Model FILE The FBX model to convert.
Options:
-h,--help Print this help message and exit
-v,--verbose Include blend shape tangents, if reported present by the FBX SDK.
-V,--version
-i,--input FILE The FBX model to convert.
-o,--output TEXT Where to generate the output, without suffix.
-e,--embed Inline buffers as data:// URIs within generated non-binary glTF.
-b,--binary Output a single binary format .glb file.
--long-indices (never|auto|always)
Whether to use 32-bit indices.
--compute-normals (never|broken|missing|always)
When to compute vertex normals from mesh geometry.
--anim-framerate (bake24|bake30|bake60)
Select baked animation framerate.
--flip-u Flip all U texture coordinates.
--no-flip-u Don't flip U texture coordinates.
--flip-v Flip all V texture coordinates.
--no-flip-v Don't flip V texture coordinates.
--no-khr-lights-punctual Don't use KHR_lights_punctual extension to export FBX lights.
--user-properties Transcribe FBX User Properties into glTF node and material 'extras'.
--blend-shape-normals Include blend shape normals, if reported present by the FBX SDK.
--blend-shape-tangents Include blend shape tangents, if reported present by the FBX SDK.
-k,--keep-attribute (position|normal|tangent|binormial|color|uv0|uv1|auto) ...
Used repeatedly to build a limiting set of vertex attributes to keep.
--fbx-temp-dir DIR Temporary directory to be used by FBX SDK.
Materials:
--pbr-metallic-roughness Try to glean glTF 2.0 native PBR attributes from the FBX.
--khr-materials-unlit Use KHR_materials_unlit extension to request an unlit shader.
Draco:
-d,--draco Apply Draco mesh compression to geometries.
--draco-compression-level INT in [0 - 10]=7
The compression level to tune Draco to.
--draco-bits-for-position INT in [1 - 32]=14
How many bits to quantize position to.
--draco-bits-for-uv INT in [1 - 32]=10
How many bits to quantize UV coordinates to.
--draco-bits-for-normals INT in [1 - 32]=10
How many bits to quantize nornals to.
--draco-bits-for-colors INT in [1 - 32]=8
How many bits to quantize colors to.
--draco-bits-for-other INT in [1 - 32]=8
How many bits to quantize all other vertex attributes to.
```
Some of these switches are not obvious:
- `--embed` is the way to get a single distributable file without using the
binary format. It encodes the binary buffer(s) as a single base64-encoded
`data://` URI. This is a very slow and space-consuming way to accomplish what
the binary format was invented to do simply and efficiently, but it can be
useful e.g. for loaders that don't understand the .glb format.
- `--flip-u` and `--flip-v`, when enabled, will apply a `x -> (1.0 - x)`
function to all `u` or `v` texture coordinates respectively. The `u` version
is perhaps not commonly used, but flipping `v` is **the default behaviour**.
Your FBX is likely constructed with the assumption that `(0, 0)` is bottom
left, whereas glTF has `(0, 0)` as top left. To produce spec-compliant glTF,
we must flip the texcoords. To request unflipped coordinates:
- `--long-indices` lets you force the use of either 16-bit or 32-bit indices.
The default option is auto, which make the choice on a per-mesh-size basis.
- `--compute-normals` controls when automatic vertex normals should be computed
from the mesh. By default, empty normals (which are forbidden by glTF) are
replaced. A choice of 'missing' implies 'broken', but additionally creates
normals for models that lack them completely.
- `--no-flip-v` will actively disable v coordinat flipping. This can be useful
if your textures are pre-flipped, or if for some other reason you were already
in a glTF-centric texture coordinate system.
- All three material options are, in their own way, works in progress, but the
`--pbr-metallic-roughness` switch is at least compliant with the core spec;
unlike the others, it does not depend on an unratified extension. That option
will be chosen by default if you supply none of the others. Material switches
are documented further below.
- If you supply any `-keep-attribute` option, you enable a mode wherein you must
supply it repeatedly to list _all_ the vertex attributes you wish to keep in
the conversion process. This is a way to trim the size of the resulting glTF
if you know the FBX contains superfluous attributes. The supported arguments
are `position`, `normal`, `tangent`, `color`, `uv0`, and `uv1`.
- When **blend shapes** are present, you may use `--blend-shape-normals` and
`--blend-shape-tangents` to include normal and tangent attributes in the glTF
morph targets. They are not included by default because they rarely or never
seem to be correctly present in the actual FBX source, which means the SDK
must be computing them from geometry, unasked? In any case, they are beyond
the control of the artist, and can yield strange crinkly behaviour. Since
they also take up significant space in the output file, we made them opt-in.
## Building it on your own
We currently depend on the open source projects
[Draco](https://github.com/google/draco),
[MathFu](https://github.com/google/mathfu),
[Json](https://github.com/nlohmann/json),
[cppcodec](https://github.com/tplgy/cppcodec),
[cxxopts](https://github.com/jarro2783/cxxopts),
[CLI11](https://github.com/CLIUtils/CLI11),
[stb](https://github.com/nothings/stb),
and [fmt](https://github.com/fmtlib/fmt);
all of which are automatically downloaded, configured and built.
all of which are automatically downloaded and/or built.
You must manually download and install the
[Autodesk FBX SDK](https://www.autodesk.com/products/fbx/overview) 2018.1.1 and
accept its license agreement. Once installed, the build system will attempt to
find the SDK in its default location for each system.
Once that's all done...
**At present, only version 2019.2 of the FBX SDK is supported**. The
build system will not successfully locate any other version.
### Linux and MacOS X
Compilation on Unix machines should be as simple as:
Your development environment will need to have:
- build essentials (gcc for Linux, clang for Mac)
- cmake
- python 3.\* and associated pip3/pip command
- zstd
Then, compilation on Unix machines will look something like:
```
> cd <FBX2glTF directory>
> cmake -H. -Bbuild
> make -Cbuild
# Determine SDK location & build settings for Linux vs (Recent) Mac OS X
> if [[ "$OSTYPE" == "darwin"* ]]; then
export CONAN_CONFIG="-s compiler=apple-clang -s compiler.version=10.0 -s compiler.libcxx=libc++"
export FBXSDK_TARBALL="https://github.com/zellski/FBXSDK-Darwin/archive/2019.2.tar.gz"
elif [[ "$OSTYPE" == "linux"* ]]; then
export CONAN_CONFIG="-s compiler.libcxx=libstdc++11"
export FBXSDK_TARBALL="https://github.com/zellski/FBXSDK-Linux/archive/2019.2.tar.gz"
else
echo "This snippet only handles Mac OS X and Linux."
fi
# Fetch Project
> git clone https://github.com/facebookincubator/FBX2glTF.git
> cd FBX2glTF
# Fetch and unpack FBX SDK
> curl -sL "${FBXSDK_TARBALL}" | tar xz --strip-components=1 --include */sdk/
# Then decompress the contents
> zstd -d -r --rm sdk
# Install and configure Conan, if needed
> pip3 install conan # or sometimes just "pip"; you may need to install Python/PIP
> conan remote add --force bincrafters https://api.bintray.com/conan/bincrafters/public-conan
# Initialize & run build
> conan install . -i build -s build_type=Release ${CONAN_CONFIG}
> conan build . -bf build
```
If all goes well, you will end up with a statically linked executable that can
be invoked like so:
```
> ./build/FBX2glTF ~/models/butterfly.fbx
```
Or perhaps, as part of a more complex pipeline:
```
> ./build/FBX2glTF --binary --draco --flip-v \
--khr-materials-common \
--input ~/models/source/butterfly.fbx \
--output ~/models/target/butterfly.glb
```
If all goes well, you will end up with a statically linked executable in `./build/FBX2glTF`.
### Windows
<TODO> the below is out of date
Windows users may [download](https://cmake.org/download) CMake for Windows,
install it and [run it](https://cmake.org/runningcmake/) on the FBX2glTF
checkout (choose a build directory distinct from the source). As part of this
process, you will be asked to choose which generator to use; it should be fine
to pick any recent Visual Studio option relevant to your system.
checkout (choose a build directory distinct from the source).
Note that the CMAKE_BUILD_TYPE variable from the Unix Makefile system is
entirely ignored here; the Visual Studio solution that's generated handles all
the canonical build types -- Debug, Release, MinSizeRel, and so on. You will
choose which one to build in the Visual Studio IDE.
As part of this process, you will be asked to choose which generator
to use. **At present, only Visual Studio 2017 or 2019 is supported.** Older
versions of the IDE are unlikely to successfully build the tool.
Note that the `CMAKE_BUILD_TYPE` variable from the Unix Makefile system is
entirely ignored here; it is when you open the generated solution that
you will be choose one of the canonical build types — _Debug_,
_Release_, _MinSizeRel_, and so on.
## Conversion Process
The actual translation begins with the FBX SDK parsing the input file, and ends
with the generation of the core `JSON` description that forms the core of glTF,
along with binary buffers that hold geometry and animations (and optionally also
with the generation of the descriptive `JSON` that forms the core of glTF, along
with binary buffers that hold geometry and animations (and optionally also
emedded resources such as textures.)
In the process, each node and mesh in the FBX is ripped apart into a long list
of surfaces and associated triangles, with a material assigned to each one. A
similar process happens in reverse when we construct meshes and materials that
conform to the expectations of the glTF format.
In the process, each mesh is ripped apart into a long list of triangles and
their associated vertices, with a material assigned to each one. A similar
process happens in reverse when we construct meshes and materials that conform
to the expectations of the glTF format.
### Animations
Every animation in the FBX file becomes an animation in the glTF file. The
method used is one of "baking": we step through the interval of time spanned by
the animation, keyframe by keyframe, calculate the local transform of each node,
and whenever we find any node that's rotated, translated or scaled, we record
that fact in the output.
the animation, keyframe by keyframe, calculate the local transform of each
node, and whenever we find any node that's rotated, translated or scaled, we
record that fact in the output.
This method has the benefit of being simple and precise. It has the drawback of
creating potentially very large files. The more complex the animation rig, the
less avoidable this situation is.
Beyond skeleton-based animation, _Blend Shapes_ are also supported; they are
read from the FBX file on a per-mesh basis, and clips can use them by varying
the weights associated with each one.
The baking method has the benefit of being simple and precise. It has the
drawback of creating potentially very large files. The more complex the
animation rig, the less avoidable this data explosion is.
There are three future enhancements we hope to see for animations:
There are two future enhancements we hope to see for animations:
- Version 2.0 of glTF brought us support for expressing quadratic animation
curves, where previously we had only had linear. Not coincidentally, quadratic
splines are one of the key ways animations are expressed inside the FBX. When
we find such a curve, it would be more efficient to output it without baking
it into a long sequence of linear approximations.
- Perhaps more useful in practice is the idea of compressing animation curves
- We do not yet ever generate
[sparse accessors](https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#sparse-accessors),
but many animations (especially morph targets) would benefit from this
storage optimisation.
- Perhaps most useful in practice is the idea of compressing animation curves
the same way we use Draco to compress meshes (see below). Like geometry,
animations are highly redundant -- each new value is highly predictable from
animations are highly redundant each new value is highly predictable from
preceding values. If Draco extends its support for animations (it's on their
roadmap), or if someone else develops a glTF extension for animation
compression, we will likely add support in this tool.
### Materials
With glTF 2.0, we leaped headlong into physically-based rendering (BPR), where
canonical way of expressing what a mesh looks like is by describing its visible
material in fundamental attributes like "how rough is this surface".
With glTF 2.0, we leaped headlong into physically-based rendering (PBR), where
the canonical way of expressing what a mesh looks like is by describing its
visible material in fundamental attributes like "how rough is this surface".
By contrast, FBX's material support remains in the older world of Lambert and
Phong, with much simpler illumination and shading models. These are modes are
largely incompatible (for example, textures in the old workflow often contain
baked lighting that would arise naturally in a PBR environment).
By contrast, FBX's material support remains largely in the older world of
Lambert and Phong, with simpler and more direct illumination and shading
models. These modes are inherently incompatible — for example, textures in the
old workflow often contain baked lighting of the type that would arise naturally
in a PBR environment.
Some material settings remain well supported and transfer automatically:
- Emissive constants and textures
- Occlusion maps
- Normal maps
This leaves the other traditional settings of Lambert:
- Ambient -- this is anathema in the PBR world, where such effects should
emerge naturally from the fundamental colour of the material and any ambient
lighting present.
- Diffuse -- the material's direction-agnostic, non-specular reflection,
and additionally, with Blinn/Phong:
- Specular -- a more polished material's direction-sensitive reflection,
- Shininess -- just how polished the material is,
- Emissive constants and textures
- Occlusion maps
- Normal maps
This leaves the other traditional settings, first of Lambert:
- Ambient — this is anathema in the PBR world, where such effects should
emerge naturally from the fundamental colour of the material and any ambient
lighting present.
- Diffuse — the material's direction-agnostic, non-specular reflection,
and additionally, with Blinn/Phong:
- Specular — a more polished material's direction-sensitive reflection,
- Shininess — just how polished the material is; a higher value here yields a
more mirror-like surface.
(All these can be either constants or textures.)
Increasingly with PBR materials, those properties are just left at sensible zero
or default values in the FBX. But when they're there, and they're how you want
to define your materials, one option is to use the --khr-materials-common
command line switch, which incurs a required dependency on the glTF extension
`KHR_materials_common`. **Note that at the time of writing, this glTF extension
is still undergoing the ratification process, and is furthermore likely to
change names.**
#### Exporting as Unlit
Given the command line flag --pbr-metallic-roughness, we accept glTF 2.0's PBR
mode, but we do so very partially, filling in a couple of reasonable constants
for metalness and roughness and using the diffuse texture, if it exists, as the
`base colour` texture.
If you have a model was constructed using an unlit workflow, e.g. a photogrammetry
capture or a landscape with careful baked-in lighting, you may choose to export
it using the --khr-materials-common switch. This incurs a dependency on the glTF
extension 'KHR_materials_unlit; a client that accepts that extension is making
a promise it'll do its best to render pixel values without lighting calculations.
More work is needed to harness the power of glTF's 2.0's materials. The biggest
issue here is the lack of any obviously emerging standards to complement FBX
itself. It's not clear what format an artist can export their PBR materials on,
and when they can, how to communicate this information well to `FBX2glTF`.
**Note that at the time of writing, this glTF extension is still undergoing the
ratification process**
#### Exporting as Metallic-Roughness PBR
Given the command line flag --pbr-metallic-roughness, we throw ourselves into
the warm embrace of glTF 2.0's PBR preference.
As mentioned above, there is little consensus in the world on how PBR should be
represented in FBX. At present, we support only one format: Stingray PBS. This
is a feature that comes bundled with Maya, and any PBR model exported through
that route should be digested propertly by FBX2glTF.
(A happy note: Allegorithmic's Substance Painter also exports Stingray PBS,
when hooked up to Maya.)
## Draco Compression
The tool will optionally apply [Draco](https://github.com/google/draco)
compression to the geometric data of each mesh (vertex indices, positions,
normals, per-vertex color, and so on). This can be dramatically effective
@ -156,13 +323,18 @@ viewer that is willing and able to decompress the data.
ratification process.**
## Future Improvements
This tool is under continuous development. We do not have a development roadmap
per se, but some aspirations have been noted above.
per se, but some aspirations have been noted above. The canonical list of active
TODO items can be found
[on GitHub](https://github.com/facebookincubator/FBX2glTF/labels/enhancement).
## Authors
- Pär Winzell
- J.M.P. van Waveren
- Amanda Watson
- Pär Winzell
- J.M.P. van Waveren
- Amanda Watson
## License
`FBX2glTF` is BSD-licensed. We also provide an additional patent grant.
FBX2glTF is licensed under the [3-clause BSD license](LICENSE).

101
azure-pipelines.yml Normal file
View File

@ -0,0 +1,101 @@
# C/C++ with GCC
# Build your C/C++ project with GCC using make.
# Add steps that publish test results, save build artifacts, deploy, and more:
# https://docs.microsoft.com/azure/devops/pipelines/apps/c-cpp/gcc
jobs:
- job: Linux
pool:
vmImage: 'Ubuntu 16.04'
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '3.6'
architecture: 'x64'
- script: python -m pip install --upgrade pip setuptools wheel
displayName: 'Install Python tools'
- script: |
pip install conan
conan remote add bincrafters https://api.bintray.com/conan/bincrafters/public-conan
displayName: 'Install & configure Conan'
- script: |
conan install . -i build -s build_type=Release -e FBXSDK_SDKS=sdk
displayName: 'Resolve binary dependencies and build CMake files.'
- script: |
conan build -bf build .
mv build/FBX2glTF build/FBX2glTF-linux-x64
displayName: 'Build FBX2glTF'
- task: PublishBuildArtifacts@1
inputs:
pathtoPublish: 'build/FBX2glTF-linux-x64'
artifactName: 'binaries'
- job: Mac
pool:
vmImage: 'macOS-10.14'
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '3.6'
architecture: 'x64'
- script: python -m pip install --upgrade pip setuptools wheel
displayName: 'Install Python tools'
- script: |
pip install conan
conan remote add bincrafters https://api.bintray.com/conan/bincrafters/public-conan
displayName: 'Install Conan'
- script: |
conan install . -i build -s compiler=apple-clang -s compiler=apple-clang -s compiler.version=10.0 -s compiler.libcxx=libc++ -s build_type=Release -e FBXSDK_SDKS=sdk
displayName: 'Resolve binary dependencies and build CMake files.'
- script: |
conan build -bf build .
mv build/FBX2glTF build/FBX2glTF-darwin-x64
displayName: 'Build FBX2glTF'
- task: PublishBuildArtifacts@1
inputs:
pathtoPublish: 'build/FBX2glTF-darwin-x64'
artifactName: 'binaries'
- job: Windows
pool:
vmImage: 'vs2017-win2016'
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '3.6'
architecture: 'x64'
- script: python -m pip install --upgrade pip setuptools wheel
displayName: 'Install Python tools'
- script: |
pip install conan
conan remote add bincrafters https://api.bintray.com/conan/bincrafters/public-conan
displayName: 'Install Conan'
- script: |
conan install . -i build -s build_type=Release -e FBXSDK_SDKS=sdk
displayName: 'Resolve binary dependencies and build CMake files.'
- script: |
conan build -bf build .
move build\Release\FBX2glTF.exe build\Release\FBX2glTF-windows-x64.exe
displayName: 'Build FBX2glTF'
- task: PublishBuildArtifacts@1
inputs:
pathtoPublish: 'build/Release/FBX2glTF-windows-x64.exe'
artifactName: 'binaries'

33
conanfile.py Normal file
View File

@ -0,0 +1,33 @@
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
#
import os
from conans import ConanFile, CMake
class FBX2glTFConan(ConanFile):
settings = "os", "compiler", "build_type", "arch"
requires = (
("boost_filesystem/1.69.0@bincrafters/stable"),
("libiconv/1.15@bincrafters/stable"),
("zlib/1.2.11@conan/stable"),
("libxml2/2.9.9@bincrafters/stable"),
("fmt/5.3.0@bincrafters/stable"),
)
generators = "cmake_find_package", "cmake_paths"
def configure(self):
if (
self.settings.compiler == "gcc"
and self.settings.compiler.libcxx == "libstdc++"
):
raise Exception(
"Rerun 'conan install' with argument: '-s compiler.libcxx=libstdc++11'"
)
def build(self):
cmake = CMake(self)
cmake.definitions["FBXSDK_SDKS"] = os.getenv("FBXSDK_SDKS", "sdk")
cmake.configure()
cmake.build()

5
docker-compose.yaml Normal file
View File

@ -0,0 +1,5 @@
version: '3.7'
services:
fbx2gltf:
build:
context: .

View File

@ -1,33 +0,0 @@
Additional Grant of Patent Rights Version 2
"Software" means the FBX2glTF software contributed by Facebook, Inc.
Facebook, Inc. ("Facebook") hereby grants to each recipient of the Software
("you") a perpetual, worldwide, royalty-free, non-exclusive, irrevocable
(subject to the termination provision below) license under any Necessary
Claims, to make, have made, use, sell, offer to sell, import, and otherwise
transfer the Software. For avoidance of doubt, no license is granted under
Facebooks rights in any patent claims that are infringed by (i) modifications
to the Software made by you or any third party or (ii) the Software in
combination with any software or other technology.
The license granted hereunder will terminate, automatically and without notice,
if you (or any of your subsidiaries, corporate affiliates or agents) initiate
directly or indirectly, or take a direct financial interest in, any Patent
Assertion: (i) against Facebook or any of its subsidiaries or corporate
affiliates, (ii) against any party if such Patent Assertion arises in whole or
in part from any software, technology, product or service of Facebook or any of
its subsidiaries or corporate affiliates, or (iii) against any party relating
to the Software. Notwithstanding the foregoing, if Facebook or any of its
subsidiaries or corporate affiliates files a lawsuit alleging patent
infringement against you in the first instance, and you respond by filing a
patent infringement counterclaim in that lawsuit against that party that is
unrelated to the Software, the license granted hereunder will not terminate
under section (i) of this paragraph due to such counterclaim.
A "Necessary Claim" is a claim of a patent owned by Facebook that is
necessarily infringed by the Software standing alone.
A "Patent Assertion" is any lawsuit or other action alleging direct, indirect,
or contributory infringement or inducement to infringe any patent, including a
cross-claim or counterclaim.

View File

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

View File

@ -1,43 +0,0 @@
#!/bin/bash
#
# fbx2glb.sh <input FBX> <output GLB>
#
# TODO: Pass command line switches through to binary.
set -e
BINDIR=`dirname $0`
BINDIR=`cd ${BINDIR} ; pwd`
SYSTEM=`uname -s`
FBX2GLTF="${BINDIR}/${SYSTEM}/FBX2glTF"
if [ ! -f "${FBX2GLTF}" ]; then
echo "Unable to find 'FBX2glTF' binary: ${FBX2GLTF}"
exit 1
fi
if [ "$#" != 2 ]; then
echo "Usage: <fbx input> <glb output>"
exit 1
fi
fullpath() {
OLDPWD=$PWD
cd "$(dirname "$1")"
FULLPATH="$PWD/$(basename "$1")"
cd "$OLDPWD"
echo "$FULLPATH"
}
INFILE=$(fullpath $1)
OUTFILE=$(fullpath $(basename $2 ".glb"))
# construct a safe work dir
SCRIPT_BASE=`basename $0`
TEMP_DIR=`mktemp -d "/tmp/${SCRIPT_BASE}.XXXX"`
trap "rm -rf ${TEMP_DIR}" EXIT
cd ${TEMP_DIR}
# some hard-coded defaults for now
"${FBX2GLTF}" --binary --flip-v --input "${INFILE}" --output "${OUTFILE}"

View File

@ -2,7 +2,7 @@ BSD License
For FBX2glTF software
Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

View File

@ -1,5 +1,8 @@
# FBX2glTF
[![License](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause)
This is a command line tool for converting 3D model assets on the
well-established [FBX](https://www.autodesk.com/products/fbx/overview) format to
[glTF 2.0](https://github.com/KhronosGroup/glTF/tree/master/specification/2.0),
@ -11,7 +14,36 @@ This package contains three versions of `FBX2glTF`, compiled for three platforms
and located in three eponymous directories:
- bin/Darwin/FBX2glTF
- bin/Linux/FBX2glTF
- bin/Windows/FBX2glTF.exe
- bin/Windows_NT/FBX2glTF.exe
# Usage
```js
/**
* Converts an FBX to a GTLF or GLB file.
* @param string srcFile path to the source file.
* @param string destFile path to the destination file.
* This must end in `.glb` or `.gltf` (case matters).
* @param string[] [opts] options to pass to the converter tool.
* @return Promise<string> a promise that yields the full path to the converted
* file, an error on conversion failure.
*/
convert(srcPath :string, destPath :string, args :?string[]) :Promise<string>
```
For example:
```js
const convert = require('fbx2gltf');
convert('path/to/some.fbx', 'path/to/target.glb', ['--khr-materials-unlit']).then(
destPath => {
// yay, do what we will with our shiny new GLB file!
},
error => {
// ack, conversion failed: inspect 'error' for details
}
);
```
# Further Reading
@ -24,6 +56,9 @@ The home of this tool is [here](https://github.com/facebookincubator/FBX2glTF).
# Legal
FBX2glTF is licensed under the [3-clause BSD license](LICENSE).
```
This software contains Autodesk® FBX® code developed by Autodesk, Inc. Copyright
2017 Autodesk, Inc. All rights, reserved. Such code is provided “as is” and
Autodesk, Inc. disclaims any and all warranties, whether express or implied,
@ -35,4 +70,4 @@ of substitute goods or services; loss of use, data, or profits; or business
interruption) however caused and on any theory of liability, whether in
contract, strict liability, or tort (including negligence or otherwise) arising
in any way out of such code.
```

97
npm/fbx2gltf/index.js Normal file
View File

@ -0,0 +1,97 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*/
const childProcess = require('child_process');
const fs = require('fs');
const os = require('os');
const path = require('path');
const rimraf = require('rimraf');
const binaries = {
'darwin': `bin/darwin/Fbx2Gtlf`,
'linux': `bin/linux/Fbx2Gtlf`,
'win32': `bin\windows\Fbx2Gtlf.exe`,
};
/**
* Converts an FBX to a GTLF or GLB file.
* @param string srcFile path to the source file.
* @param string destFile path to the destination file or destination path.
* This must end in `.glb` or `.gltf` (case matters).
* @param string[] [opts] options to pass to the converter tool.
* @return Promise<string> a promise that yields the full path to the converted
* file, an error on conversion failure.
*/
function convert(srcFile, destFile, opts = []) {
return new Promise((resolve, reject) => {
try {
let binExt = os.type() === 'Windows_NT' ? '.exe' : '';
let tool = path.join(__dirname, 'bin', os.type(), 'FBX2glTF' + binExt);
if (!fs.existsSync(tool)) {
throw new Error(`Unsupported OS: ${os.type()}`);
}
let destExt = path.extname(destFile).toLowerCase();
if (!destExt) {
destExt = '.gltf'
const srcFilename = path.basename(srcFile, path.extname(srcFile))
destFile = path.join(destFile, srcFilename + destExt)
}
if (destExt !== '.glb' && destExt !== '.gltf') {
throw new Error(`Unsupported file extension: ${destFile}`);
}
const binary = opts.includes('--binary') || opts.includes('-b');
if (binary && destExt !== '.glb') {
destExt = '.glb';
} else if (!binary && destExt === 'glb') {
opts.push('--binary');
}
let srcPath = fs.realpathSync(srcFile);
let destDir = fs.realpathSync(path.dirname(destFile));
let destFilename = path.basename(destFile, path.extname(destFile)) + destExt;
let destPath = path.join(destDir, destFilename);
let args = opts.slice(0);
args.push('--input', srcPath, '--output', destPath);
let child = childProcess.spawn(tool, args);
let output = '';
child.stdout.on('data', (data) => output += data);
child.stderr.on('data', (data) => output += data);
child.on('error', reject);
child.on('close', code => {
// the FBX SDK may create an .fbm dir during conversion; delete!
let fbmCruft = srcPath.replace(/.fbx$/i, '.fbm');
// don't stick a fork in things if this fails, just log a warning
const onError = error =>
error && console.warn(`Failed to delete ${fbmCruft}: ${error}`);
try {
fs.existsSync(fbmCruft) && rimraf(fbmCruft, {}, onError);
} catch (error) {
onError(error);
}
// non-zero exit code is failure
if (code != 0) {
reject(new Error(`Converter output:\n` +
(output.length ? output : "<none>")));
} else {
resolve(destPath);
}
});
} catch (error) {
reject(error);
}
});
}
module.exports = convert;

93
npm/fbx2gltf/package-lock.json generated Normal file
View File

@ -0,0 +1,93 @@
{
"name": "fbx2gltf",
"version": "0.9.7-p1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
},
"glob": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
"integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==",
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"requires": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"requires": {
"brace-expansion": "^1.1.7"
}
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"requires": {
"wrappy": "1"
}
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
},
"rimraf": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"requires": {
"glob": "^7.1.3"
}
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
}
}
}

View File

@ -1,6 +1,6 @@
{
"name": "fbx2gltf",
"version": "0.9.0",
"version": "0.9.7-p1",
"description": "Node wrapper around FBX2glTF tools.",
"main": "index.js",
"repository": {
@ -16,12 +16,14 @@
"bugs": {
"url": "https://github.com/facebookincubator/FBX2glTF/issues"
},
"homepage": "https://github.com/facebookincubator/FBX2glTF"
"homepage": "https://github.com/facebookincubator/FBX2glTF",
"files": [
"LICENSE",
"PATENTS",
"README.md",
"bin",
"index.js"
]
],
"dependencies": {
"rimraf": "^2.6.2"
}
}

82
npm/fbx2gltf/yarn.lock Normal file
View File

@ -0,0 +1,82 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
balanced-match@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
dependencies:
balanced-match "^1.0.0"
concat-map "0.0.1"
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
glob@^7.1.3:
version "7.1.4"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255"
integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^3.0.4"
once "^1.3.0"
path-is-absolute "^1.0.0"
inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
dependencies:
once "^1.3.0"
wrappy "1"
inherits@2:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
minimatch@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
dependencies:
brace-expansion "^1.1.7"
once@^1.3.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
dependencies:
wrappy "1"
path-is-absolute@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
rimraf@^2.6.2:
version "2.6.3"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==
dependencies:
glob "^7.1.3"
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=

View File

@ -1,43 +0,0 @@
const childProcess = require('child_process');
const os = require('os');
const path = require('path');
const binaries = {
'darwin': `bin/darwin/Fbx2Gtlf`,
'linux': `bin/linux/Fbx2Gtlf`,
'win32': `bin\windows\Fbx2Gtlf.exe`,
};
function fbx2glb(srcFile, destFile, cwd) {
return new Promise((resolve, reject) => {
let script = os.type() === 'Windows_NT' ? 'fbx2glb.bat' : 'fbx2glb.sh';
let child;
try {
let opts = {};
cwd && (opts.cwd = cwd);
child = childProcess.spawn(
path.join(__dirname, 'bin', script),
[ srcFile, destFile ],
opts
);
} catch (error) {
reject(error);
return;
}
let output = '';
child.stdout.on('data', (data) => output += data);
child.stderr.on('data', (data) => output += data);
child.on('error', reject);
child.on('close', code => {
// non-zero exit code is failure
if (code != 0) {
reject(new Error(`Script ${script} output:\n` +
(output.length ? output : "<none>")));
} else {
resolve(destFile);
}
});
});
}
module.exports = fbx2glb;

394
src/FBX2glTF.cpp Normal file
View File

@ -0,0 +1,394 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <fstream>
#include <iostream>
#include <map>
#include <unordered_map>
#include <vector>
#include <CLI11.hpp>
#include "FBX2glTF.h"
#include "fbx/Fbx2Raw.hpp"
#include "gltf/Raw2Gltf.hpp"
#include "utils/File_Utils.hpp"
#include "utils/String_Utils.hpp"
bool verboseOutput = false;
int main(int argc, char* argv[]) {
GltfOptions gltfOptions;
CLI::App app{
fmt::sprintf(
"FBX2glTF %s: Generate a glTF 2.0 representation of an FBX model.", FBX2GLTF_VERSION),
"FBX2glTF"};
app.add_flag(
"-v,--verbose",
verboseOutput,
"Include blend shape tangents, if reported present by the FBX SDK.");
app.add_flag_function("-V,--version", [&](size_t count) {
fmt::printf("FBX2glTF version %s\nCopyright (c) 2016-2018 Oculus VR, LLC.\n", FBX2GLTF_VERSION);
exit(0);
});
std::string inputPath;
app.add_option("FBX Model", inputPath, "The FBX model to convert.")->check(CLI::ExistingFile);
app.add_option("-i,--input", inputPath, "The FBX model to convert.")->check(CLI::ExistingFile);
std::string outputPath;
app.add_option("-o,--output", outputPath, "Where to generate the output, without suffix.");
app.add_flag(
"-e,--embed",
gltfOptions.embedResources,
"Inline buffers as data:// URIs within generated non-binary glTF.");
app.add_flag("-b,--binary", gltfOptions.outputBinary, "Output a single binary format .glb file.");
app.add_option(
"--long-indices",
[&](std::vector<std::string> choices) -> bool {
for (const std::string choice : choices) {
if (choice == "never") {
gltfOptions.useLongIndices = UseLongIndicesOptions::NEVER;
} else if (choice == "auto") {
gltfOptions.useLongIndices = UseLongIndicesOptions::AUTO;
} else if (choice == "always") {
gltfOptions.useLongIndices = UseLongIndicesOptions::ALWAYS;
} else {
fmt::printf("Unknown --long-indices: %s\n", choice);
throw CLI::RuntimeError(1);
}
}
return true;
},
"Whether to use 32-bit indices.")
->type_name("(never|auto|always)");
app.add_option(
"--compute-normals",
[&](std::vector<std::string> choices) -> bool {
for (const std::string choice : choices) {
if (choice == "never") {
gltfOptions.computeNormals = ComputeNormalsOption::NEVER;
} else if (choice == "broken") {
gltfOptions.computeNormals = ComputeNormalsOption::BROKEN;
} else if (choice == "missing") {
gltfOptions.computeNormals = ComputeNormalsOption::MISSING;
} else if (choice == "always") {
gltfOptions.computeNormals = ComputeNormalsOption::ALWAYS;
} else {
fmt::printf("Unknown --compute-normals option: %s\n", choice);
throw CLI::RuntimeError(1);
}
}
return true;
},
"When to compute vertex normals from mesh geometry.")
->type_name("(never|broken|missing|always)");
app.add_option(
"--anim-framerate",
[&](std::vector<std::string> choices) -> bool {
for (const std::string choice : choices) {
if (choice == "bake24") {
gltfOptions.animationFramerate = AnimationFramerateOptions::BAKE24;
} else if (choice == "bake30") {
gltfOptions.animationFramerate = AnimationFramerateOptions::BAKE30;
} else if (choice == "bake60") {
gltfOptions.animationFramerate = AnimationFramerateOptions::BAKE60;
} else {
fmt::printf("Unknown --anim-framerate: %s\n", choice);
throw CLI::RuntimeError(1);
}
}
return true;
},
"Select baked animation framerate.")
->type_name("(bake24|bake30|bake60)");
const auto opt_flip_u = app.add_flag("--flip-u", "Flip all U texture coordinates.");
const auto opt_no_flip_u = app.add_flag("--no-flip-u", "Don't flip U texture coordinates.");
const auto opt_flip_v = app.add_flag("--flip-v", "Flip all V texture coordinates.");
const auto opt_no_flip_v = app.add_flag("--no-flip-v", "Don't flip V texture coordinates.");
app.add_flag(
"--pbr-metallic-roughness",
gltfOptions.usePBRMetRough,
"Try to glean glTF 2.0 native PBR attributes from the FBX.")
->group("Materials");
app.add_flag(
"--khr-materials-unlit",
gltfOptions.useKHRMatUnlit,
"Use KHR_materials_unlit extension to request an unlit shader.")
->group("Materials");
app.add_flag_function(
"--no-khr-lights-punctual",
[&](size_t count) { gltfOptions.useKHRLightsPunctual = (count == 0); },
"Don't use KHR_lights_punctual extension to export FBX lights.");
app.add_flag(
"--user-properties",
gltfOptions.enableUserProperties,
"Transcribe FBX User Properties into glTF node and material 'extras'.");
app.add_flag(
"--blend-shape-normals",
gltfOptions.useBlendShapeNormals,
"Include blend shape normals, if reported present by the FBX SDK.");
app.add_flag(
"--blend-shape-tangents",
gltfOptions.useBlendShapeTangents,
"Include blend shape tangents, if reported present by the FBX SDK.");
app.add_option(
"-k,--keep-attribute",
[&](std::vector<std::string> attributes) -> bool {
gltfOptions.keepAttribs =
RAW_VERTEX_ATTRIBUTE_JOINT_INDICES | RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS;
for (std::string attribute : attributes) {
if (attribute == "position") {
gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_POSITION;
} else if (attribute == "normal") {
gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_NORMAL;
} else if (attribute == "tangent") {
gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_TANGENT;
} else if (attribute == "binormal") {
gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_BINORMAL;
} else if (attribute == "color") {
gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_COLOR;
} else if (attribute == "uv0") {
gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_UV0;
} else if (attribute == "uv1") {
gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_UV1;
} else if (attribute == "auto") {
gltfOptions.keepAttribs |= RAW_VERTEX_ATTRIBUTE_AUTO;
} else {
fmt::printf("Unknown --keep-attribute option: %s\n", attribute);
throw CLI::RuntimeError(1);
}
}
return true;
},
"Used repeatedly to build a limiting set of vertex attributes to keep.")
->type_size(-1)
->type_name("(position|normal|tangent|binormial|color|uv0|uv1|auto)");
app.add_flag(
"-d,--draco", gltfOptions.draco.enabled, "Apply Draco mesh compression to geometries.")
->group("Draco");
app.add_option(
"--draco-compression-level",
gltfOptions.draco.compressionLevel,
"The compression level to tune Draco to.",
true)
->check(CLI::Range(0, 10))
->group("Draco");
app.add_option(
"--draco-bits-for-position",
gltfOptions.draco.quantBitsPosition,
"How many bits to quantize position to.",
true)
->check(CLI::Range(1, 32))
->group("Draco");
app.add_option(
"--draco-bits-for-uv",
gltfOptions.draco.quantBitsTexCoord,
"How many bits to quantize UV coordinates to.",
true)
->check(CLI::Range(1, 32))
->group("Draco");
app.add_option(
"--draco-bits-for-normals",
gltfOptions.draco.quantBitsNormal,
"How many bits to quantize nornals to.",
true)
->check(CLI::Range(1, 32))
->group("Draco");
app.add_option(
"--draco-bits-for-colors",
gltfOptions.draco.quantBitsColor,
"How many bits to quantize colors to.",
true)
->check(CLI::Range(1, 32))
->group("Draco");
app.add_option(
"--draco-bits-for-other",
gltfOptions.draco.quantBitsGeneric,
"How many bits to quantize all other vertex attributes to.",
true)
->check(CLI::Range(1, 32))
->group("Draco");
app.add_option("--fbx-temp-dir", gltfOptions.fbxTempDir, "Temporary directory to be used by FBX SDK.")->check(CLI::ExistingDirectory);
CLI11_PARSE(app, argc, argv);
bool do_flip_u = false;
bool do_flip_v = true;
// somewhat tedious way to resolve --flag vs --no-flag in order provided
for (const auto opt : app.parse_order()) {
do_flip_u = (do_flip_u || (opt == opt_flip_u)) && (opt != opt_no_flip_u);
do_flip_v = (do_flip_v || (opt == opt_flip_v)) && (opt != opt_no_flip_v);
}
std::vector<std::function<Vec2f(Vec2f)>> texturesTransforms;
if (do_flip_u || do_flip_v) {
if (do_flip_u && do_flip_v) {
texturesTransforms.emplace_back([](Vec2f uv) { return Vec2f(1.0 - uv[0], 1.0 - uv[1]); });
} else if (do_flip_u) {
texturesTransforms.emplace_back([](Vec2f uv) { return Vec2f(1.0 - uv[0], uv[1]); });
} else {
texturesTransforms.emplace_back([](Vec2f uv) { return Vec2f(uv[0], 1.0 - uv[1]); });
}
}
if (verboseOutput) {
if (do_flip_u) {
fmt::printf("Flipping texture coordinates in the 'U' dimension.\n");
}
if (!do_flip_v) {
fmt::printf("NOT flipping texture coordinates in the 'V' dimension.\n");
}
}
if (inputPath.empty()) {
fmt::printf("You must supply a FBX file to convert.\n");
exit(1);
}
if (!gltfOptions.useKHRMatUnlit && !gltfOptions.usePBRMetRough) {
if (verboseOutput) {
fmt::printf("Defaulting to --pbr-metallic-roughness material support.\n");
}
gltfOptions.usePBRMetRough = true;
}
if (gltfOptions.embedResources && gltfOptions.outputBinary) {
fmt::printf("Note: Ignoring --embed; it's meaningless with --binary.\n");
}
if (outputPath.empty()) {
// if -o is not given, default to the basename of the .fbx
outputPath = "./" + FileUtils::GetFileBase(inputPath);
}
// the output folder in .gltf mode, not used for .glb
std::string outputFolder;
// the path of the actual .glb or .gltf file
std::string modelPath;
const auto& suffix = FileUtils::GetFileSuffix(outputPath);
// Assume binary output if extension is glb
if (suffix.has_value() && suffix.value() == "glb") {
gltfOptions.outputBinary = true;
}
if (gltfOptions.outputBinary) {
// add .glb to output path, unless it already ends in exactly that
if (suffix.has_value() && suffix.value() == "glb") {
modelPath = outputPath;
} else {
modelPath = outputPath + ".glb";
}
// if the extension is gltf set the output folder to the parent directory
} else if (suffix.has_value() && suffix.value() == "gltf") {
outputFolder = FileUtils::getFolder(outputPath) + "/";
modelPath = outputPath;
} else {
// in gltf mode, we create a folder and write into that
outputFolder = fmt::format("{}_out/", outputPath.c_str());
modelPath = outputFolder + FileUtils::GetFileName(outputPath) + ".gltf";
}
if (!FileUtils::CreatePath(modelPath.c_str())) {
fmt::fprintf(stderr, "ERROR: Failed to create folder: %s'\n", outputFolder.c_str());
return 1;
}
ModelData* data_render_model = nullptr;
RawModel raw;
if (verboseOutput) {
fmt::printf("Loading FBX File: %s\n", inputPath);
}
if (!LoadFBXFile(raw, inputPath, {"png", "jpg", "jpeg"}, gltfOptions)) {
fmt::fprintf(stderr, "ERROR:: Failed to parse FBX: %s\n", inputPath);
return 1;
}
if (!texturesTransforms.empty()) {
raw.TransformTextures(texturesTransforms);
}
raw.Condense();
raw.TransformGeometry(gltfOptions.computeNormals);
std::ofstream outStream; // note: auto-flushes in destructor
const auto streamStart = outStream.tellp();
outStream.open(modelPath, std::ios::trunc | std::ios::ate | std::ios::out | std::ios::binary);
if (outStream.fail()) {
fmt::fprintf(stderr, "ERROR:: Couldn't open file for writing: %s\n", modelPath.c_str());
return 1;
}
data_render_model = Raw2Gltf(outStream, outputFolder, raw, gltfOptions);
if (gltfOptions.outputBinary) {
fmt::printf(
"Wrote %lu bytes of binary glTF to %s.\n",
(unsigned long)(outStream.tellp() - streamStart),
modelPath);
delete data_render_model;
return 0;
}
fmt::printf(
"Wrote %lu bytes of glTF to %s.\n",
(unsigned long)(outStream.tellp() - streamStart),
modelPath);
if (gltfOptions.embedResources) {
// we're done: everything was inlined into the glTF JSON
delete data_render_model;
return 0;
}
assert(!outputFolder.empty());
const std::string binaryPath = outputFolder + extBufferFilename;
FILE* fp = fopen(binaryPath.c_str(), "wb");
if (fp == nullptr) {
fmt::fprintf(stderr, "ERROR:: Couldn't open file '%s' for writing.\n", binaryPath);
return 1;
}
if (data_render_model->binary->empty() == false) {
const unsigned char* binaryData = &(*data_render_model->binary)[0];
unsigned long binarySize = data_render_model->binary->size();
if (fwrite(binaryData, binarySize, 1, fp) != 1) {
fmt::fprintf(
stderr, "ERROR: Failed to write %lu bytes to file '%s'.\n", binarySize, binaryPath);
fclose(fp);
return 1;
}
fclose(fp);
fmt::printf("Wrote %lu bytes of binary data to %s.\n", binarySize, binaryPath);
}
delete data_render_model;
return 0;
}

View File

@ -1,26 +1,128 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* Copyright (c) Facebook, Inc. and its affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
* LICENSE file in the root directory of this source tree.
*/
#ifndef __FBX2GLTF_H__
#define __FBX2GLTF_H__
#pragma once
#if defined ( _WIN32 )
// This can be a macro under Windows, confusing FMT
#undef isnan
#include <climits>
#include <string>
#if defined(_WIN32)
// Tell Windows not to define min() and max() macros
#define NOMINMAX
#include <Windows.h>
#endif
#define FBX2GLTF_VERSION std::string("0.9.7")
#include <fmt/printf.h>
#include <fbxsdk.h>
#include "mathfu.h"
#if defined(_WIN32)
// this is defined in fbxmath.h
#undef isnan
#undef snprintf
#endif
#endif // !__FBX2GLTF_H__
#include "mathfu.hpp"
// give all modules access to our tweaked JSON
#include <fifo_map.hpp>
#include <json.hpp>
template <class K, class V, class ignore, class A>
using workaround_fifo_map = nlohmann::fifo_map<K, V, nlohmann::fifo_map_compare<K>, A>;
using json = nlohmann::basic_json<workaround_fifo_map>;
extern bool verboseOutput;
/**
* Centralises all the laborious downcasting from your OS' 64-bit
* index variables down to the uint32s that glTF is built out of.
*/
inline uint32_t to_uint32(size_t n) {
assert(n < UINT_MAX);
return static_cast<uint32_t>(n);
}
/**
* The variuos situations in which the user may wish for us to (re-)compute normals for our
* vertices.
*/
enum class ComputeNormalsOption {
NEVER, // do not ever compute any normals (results in broken glTF for some sources)
BROKEN, // replace zero-length normals in any mesh that has a normal layer
MISSING, // if a mesh lacks normals, compute them all
ALWAYS // compute a new normal for every vertex, obliterating whatever may have been there before
};
enum class UseLongIndicesOptions {
NEVER, // only ever use 16-bit indices
AUTO, // use shorts or longs depending on vertex count
ALWAYS, // only ever use 32-bit indices
};
enum class AnimationFramerateOptions {
BAKE24, // bake animations at 24 fps
BAKE30, // bake animations at 30 fps
BAKE60, // bake animations at 60 fps
};
/**
* User-supplied options that dictate the nature of the glTF being generated.
*/
struct GltfOptions {
/**
* If negative, disabled. Otherwise, a bitfield of RawVertexAttributes that
* specify the largest set of attributes that'll ever be kept for a vertex.
* The special bit RAW_VERTEX_ATTRIBUTE_AUTO triggers smart mode, where the
* attributes to keep are inferred from which textures are supplied.
*/
int keepAttribs{-1};
/** Whether to output a .glb file, the binary format of glTF. */
bool outputBinary{false};
/** If non-binary, whether to inline all resources, for a single (large) .glTF file. */
bool embedResources{false};
/** Whether and how to use KHR_draco_mesh_compression to minimize static geometry size. */
struct {
bool enabled = false;
int compressionLevel = 7;
int quantBitsPosition = 14;
int quantBitsTexCoord = 10;
int quantBitsNormal = 10;
int quantBitsColor = 8;
int quantBitsGeneric = 8;
} draco;
/** Whether to include FBX User Properties as 'extras' metadata in glTF nodes. */
bool enableUserProperties{false};
/** Whether to use KHR_materials_unlit to extend materials definitions. */
bool useKHRMatUnlit{false};
/** Whether to populate the pbrMetallicRoughness substruct in materials. */
bool usePBRMetRough{false};
/** Whether to include lights through the KHR_punctual_lights extension. */
bool useKHRLightsPunctual{true};
/** Whether to include blend shape normals, if present according to the SDK. */
bool useBlendShapeNormals{false};
/** Whether to include blend shape tangents, if present according to the SDK. */
bool useBlendShapeTangents{false};
/** When to compute vertex normals from geometry. */
ComputeNormalsOption computeNormals = ComputeNormalsOption::BROKEN;
/** When to use 32-bit indices. */
UseLongIndicesOptions useLongIndices = UseLongIndicesOptions::AUTO;
/** Select baked animation framerate. */
AnimationFramerateOptions animationFramerate = AnimationFramerateOptions::BAKE24;
/** Temporary directory used by FBX SDK. */
std::string fbxTempDir;
};

File diff suppressed because it is too large Load Diff

View File

@ -1,17 +0,0 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#ifndef __FBX2RAW_H__
#define __FBX2RAW_H__
#include "RawModel.h"
bool LoadFBXFile(RawModel &raw, const char *fbxFileName, const char *textureExtensions);
#endif // !__FBX2RAW_H__

View File

@ -1,751 +0,0 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#include <cstdint>
#include <cassert>
#include <iostream>
#include <fstream>
#include "FBX2glTF.h"
#include "utils/String_Utils.h"
#include "RawModel.h"
#include "Raw2Gltf.h"
#include "glTF/AccessorData.h"
#include "glTF/AnimationData.h"
#include "glTF/BufferData.h"
#include "glTF/BufferViewData.h"
#include "glTF/CameraData.h"
#include "glTF/ImageData.h"
#include "glTF/MaterialData.h"
#include "glTF/MeshData.h"
#include "glTF/NodeData.h"
#include "glTF/PrimitiveData.h"
#include "glTF/SamplerData.h"
#include "glTF/SceneData.h"
#include "glTF/SkinData.h"
#include "glTF/TextureData.h"
typedef unsigned short TriangleIndex;
extern bool verboseOutput;
const static std::string defaultSceneName = "Root Scene";
/**
* glTF 2.0 is based on the idea that data structs within a file are referenced by index; an accessor will
* point to the n:th buffer view, and so on. The Holder class takes a freshly instantiated class, and then
* creates, stored, and returns a shared_ptr<T> for it.
*
* The idea is that every glTF resource in the file will live as long as the Holder does, and the Holders
* are all kept in the GLTFData struct. Clients may certainly cnhoose to perpetuate the full shared_ptr<T>
* reference counting type, but generally speaking we pass around simple T& and T* types because the GLTFData
* struct will, by design, outlive all other activity that takes place during in a single conversion run.
*/
template<typename T>
struct Holder
{
std::vector<std::shared_ptr<T>> ptrs;
std::shared_ptr<T> hold(T *ptr)
{
ptr->ix = ptrs.size();
ptrs.emplace_back(ptr);
return ptrs.back();
}
};
struct GLTFData
{
explicit GLTFData(bool _isGlb)
: binary(new std::vector<uint8_t>),
isGlb(_isGlb)
{
}
std::shared_ptr<BufferViewData> GetAlignedBufferView(BufferData &buffer, const BufferViewData::GL_ArrayType target)
{
unsigned long bufferSize = this->binary->size();
if ((bufferSize % 4) > 0) {
bufferSize += (4 - (bufferSize % 4));
this->binary->resize(bufferSize);
}
return this->bufferViews.hold(new BufferViewData(buffer, bufferSize, target));
}
// add a bufferview on the fly and copy data into it
std::shared_ptr<BufferViewData> AddRawBufferView(BufferData &buffer, const char *source, uint32_t bytes)
{
auto bufferView = GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_NONE);
bufferView->byteLength = bytes;
// make space for the new bytes (possibly moving the underlying data)
unsigned long bufferSize = this->binary->size();
this->binary->resize(bufferSize + bytes);
// and copy them into place
memcpy(&(*this->binary)[bufferSize], source, bytes);
return bufferView;
}
std::shared_ptr<BufferViewData> AddBufferViewForFile(BufferData &buffer, const std::string &filename)
{
// see if we've already created a BufferViewData for this precise file
auto iter = filenameToBufferView.find(filename);
if (iter != filenameToBufferView.end()) {
return iter->second;
}
std::shared_ptr<BufferViewData> result;
std::ifstream file(filename, std::ios::binary | std::ios::ate);
if (file) {
std::streamsize size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<char> fileBuffer(size);
if (file.read(fileBuffer.data(), size)) {
result = AddRawBufferView(buffer, fileBuffer.data(), size);
} else {
fmt::printf("Warning: Couldn't read %lu bytes from %s, skipping\n", size, filename);
}
} else {
fmt::printf("Warning: Couldn't open texture file %s, skipping\n", filename);
}
// note that we persist here not only success, but also failure, as nullptr
filenameToBufferView[filename] = result;
return result;
}
template<class T>
std::shared_ptr<AccessorData> AddAccessorWithView(
BufferViewData &bufferView, const GLType &type, const std::vector<T> &source)
{
auto accessor = accessors.hold(new AccessorData(bufferView, type));
accessor->appendAsBinaryArray(source, *binary);
bufferView.byteLength = accessor->byteLength();
return accessor;
}
template<class T>
std::shared_ptr<AccessorData> AddAccessorAndView(
BufferData &buffer, const GLType &type, const std::vector<T> &source)
{
auto bufferView = GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_NONE);
return AddAccessorWithView(*bufferView, type, source);
}
template<class T>
std::shared_ptr<AccessorData> AddAttributeToPrimitive(
BufferData &buffer, const RawModel &surfaceModel, PrimitiveData &primitive,
const AttributeDefinition<T> &attrDef)
{
// copy attribute data into vector
std::vector<T> attribArr;
surfaceModel.GetAttributeArray<T>(attribArr, attrDef.rawAttributeIx);
std::shared_ptr<AccessorData> accessor;
if (attrDef.dracoComponentType != draco::DT_INVALID && primitive.dracoMesh != nullptr) {
primitive.AddDracoAttrib(attrDef, attribArr);
accessor = accessors.hold(new AccessorData(attrDef.glType));
accessor->count = attribArr.size();
} else {
auto bufferView = GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_BUFFER);
accessor = AddAccessorWithView(*bufferView, attrDef.glType, attribArr);
}
primitive.AddAttrib(attrDef.gltfName, *accessor);
return accessor;
};
template<class T>
void serializeHolder(json &glTFJson, std::string key, const Holder<T> holder)
{
if (!holder.ptrs.empty()) {
std::vector<json> bits;
for (const auto &ptr : holder.ptrs) {
bits.push_back(ptr->serialize());
}
glTFJson[key] = bits;
}
}
void serializeHolders(json &glTFJson)
{
serializeHolder(glTFJson, "buffers", buffers);
serializeHolder(glTFJson, "bufferViews", bufferViews);
serializeHolder(glTFJson, "scenes", scenes);
serializeHolder(glTFJson, "accessors", accessors);
serializeHolder(glTFJson, "images", images);
serializeHolder(glTFJson, "samplers", samplers);
serializeHolder(glTFJson, "textures", textures);
serializeHolder(glTFJson, "materials", materials);
serializeHolder(glTFJson, "meshes", meshes);
serializeHolder(glTFJson, "skins", skins);
serializeHolder(glTFJson, "animations", animations);
serializeHolder(glTFJson, "cameras", cameras);
serializeHolder(glTFJson, "nodes", nodes);
}
const bool isGlb;
// cache BufferViewData instances that've already been created from a given filename
std::map<std::string, std::shared_ptr<BufferViewData>> filenameToBufferView;
std::shared_ptr<std::vector<uint8_t> > binary;
Holder<BufferData> buffers;
Holder<BufferViewData> bufferViews;
Holder<AccessorData> accessors;
Holder<ImageData> images;
Holder<SamplerData> samplers;
Holder<TextureData> textures;
Holder<MaterialData> materials;
Holder<MeshData> meshes;
Holder<SkinData> skins;
Holder<AnimationData> animations;
Holder<CameraData> cameras;
Holder<NodeData> nodes;
Holder<SceneData> scenes;
};
/**
* This method sanity-checks existance and then returns a *reference* to the *Data instance
* registered under that name. This is safe in the context of this tool, where all such data
* classes are guaranteed to stick around for the duration of the process.
*/
template<typename T>
T &require(std::map<std::string, std::shared_ptr<T>> map, std::string key)
{
auto iter = map.find(key);
assert(iter != map.end());
T &result = *iter->second;
return result;
}
static const std::vector<TriangleIndex> getIndexArray(const RawModel &raw)
{
std::vector<TriangleIndex> result;
for (int i = 0; i < raw.GetTriangleCount(); i++) {
result.push_back((TriangleIndex) raw.GetTriangle(i).verts[0]);
result.push_back((TriangleIndex) raw.GetTriangle(i).verts[1]);
result.push_back((TriangleIndex) raw.GetTriangle(i).verts[2]);
}
return result;
}
// TODO: replace with a proper MaterialHasher class
static const std::string materialHash(const RawMaterial &m) {
return m.name + "_" + std::to_string(m.type);
}
ModelData *Raw2Gltf(
std::ofstream &gltfOutStream,
const RawModel &raw,
const GltfOptions &options
)
{
if (verboseOutput) {
fmt::printf("Building render model...\n");
for (int i = 0; i < raw.GetMaterialCount(); i++) {
fmt::printf(
"Material %d: %s [shading: %s]\n", i, raw.GetMaterial(i).name.c_str(),
raw.GetMaterial(i).shadingModel.c_str());
}
if (raw.GetVertexCount() > 2 * raw.GetTriangleCount()) {
fmt::printf(
"Warning: High vertex count. Make sure there are no unnecessary vertex attributes. (see -keepAttribute cmd-line option)");
}
}
std::vector<RawModel> materialModels;
raw.CreateMaterialModels(materialModels, (1 << (sizeof(TriangleIndex) * 8)), options.keepAttribs, true);
if (verboseOutput) {
fmt::printf("%7d vertices\n", raw.GetVertexCount());
fmt::printf("%7d triangles\n", raw.GetTriangleCount());
fmt::printf("%7d textures\n", raw.GetTextureCount());
fmt::printf("%7d nodes\n", raw.GetNodeCount());
fmt::printf("%7d surfaces\n", (int) materialModels.size());
fmt::printf("%7d animations\n", raw.GetAnimationCount());
}
std::unique_ptr<GLTFData> gltf(new GLTFData(options.outputBinary));
std::map<std::string, std::shared_ptr<NodeData>> nodesByName;
std::map<std::string, std::shared_ptr<MaterialData>> materialsByName;
std::map<std::string, std::shared_ptr<MeshData>> meshByNodeName;
// for now, we only have one buffer; data->binary points to the same vector as that BufferData does.
BufferData &buffer = *gltf->buffers.hold(
options.outputBinary ?
new BufferData(gltf->binary) :
new BufferData(extBufferFilename, gltf->binary, options.embedResources));
{
//
// nodes
//
for (int i = 0; i < raw.GetNodeCount(); i++) {
// assumption: RawNode index == NodeData index
const RawNode &node = raw.GetNode(i);
auto nodeData = gltf->nodes.hold(
new NodeData(node.name, node.translation, node.rotation, node.scale, node.isJoint));
for (const auto &childName : node.childNames) {
int childIx = raw.GetNodeByName(childName.c_str());
assert(childIx >= 0);
nodeData->AddChildNode(childIx);
}
assert(nodesByName.find(nodeData->name) == nodesByName.end());
nodesByName.insert(std::make_pair(nodeData->name, nodeData));
}
//
// animations
//
for (int i = 0; i < raw.GetAnimationCount(); i++) {
const RawAnimation &animation = raw.GetAnimation(i);
auto accessor = gltf->AddAccessorAndView(buffer, GLT_FLOAT, animation.times);
accessor->min = { *std::min_element(std::begin(animation.times), std::end(animation.times)) };
accessor->max = { *std::max_element(std::begin(animation.times), std::end(animation.times)) };
AnimationData &aDat = *gltf->animations.hold(new AnimationData(animation.name, *accessor));
if (verboseOutput) {
fmt::printf("Animation '%s' has %lu channels:\n", animation.name.c_str(), animation.channels.size());
}
for (size_t j = 0; j < animation.channels.size(); j++) {
const RawChannel &channel = animation.channels[j];
const RawNode &node = raw.GetNode(channel.nodeIndex);
if (verboseOutput) {
fmt::printf(
" Channel %lu (%s) has translations/rotations/scales: [%lu, %lu, %lu]\n",
j, node.name.c_str(), channel.translations.size(),
channel.rotations.size(), channel.scales.size());
}
NodeData &nDat = require(nodesByName, node.name);
if (!channel.translations.empty()) {
aDat.AddNodeChannel(nDat, *gltf->AddAccessorAndView(buffer, GLT_VEC3F, channel.translations), "translation");
}
if (!channel.rotations.empty()) {
aDat.AddNodeChannel(nDat, *gltf->AddAccessorAndView(buffer, GLT_QUATF, channel.rotations), "rotation");
}
if (!channel.scales.empty()) {
aDat.AddNodeChannel(nDat, *gltf->AddAccessorAndView(buffer, GLT_VEC3F, channel.scales), "scale");
}
}
}
//
// samplers
//
SamplerData &defaultSampler = *gltf->samplers.hold(new SamplerData());
//
// textures
//
for (int textureIndex = 0; textureIndex < raw.GetTextureCount(); textureIndex++) {
const RawTexture &texture = raw.GetTexture(textureIndex);
const std::string textureName = Gltf::StringUtils::GetFileBaseString(texture.name);
const std::string texFilename = texture.fileName;
ImageData *source = nullptr;
if (options.outputBinary) {
auto bufferView = gltf->AddBufferViewForFile(buffer, texFilename);
if (bufferView) {
source = new ImageData(textureName, *bufferView, "image/png");
}
} else {
// TODO: don't add .ktx here; try to work out a reasonable relative path.
source = new ImageData(textureName, textureName + ".ktx");
}
if (!source) {
// fallback is tiny transparent gif
source = new ImageData(textureName, "data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=");
}
TextureData &texDat = *gltf->textures.hold(
new TextureData(textureName, defaultSampler, *gltf->images.hold(source)));
assert(texDat.ix == textureIndex);
}
//
// materials
//
for (int materialIndex = 0; materialIndex < raw.GetMaterialCount(); materialIndex++) {
const RawMaterial &material = raw.GetMaterial(materialIndex);
const bool isTransparent =
material.type == RAW_MATERIAL_TYPE_VERTEX_COLORED ||
material.type == RAW_MATERIAL_TYPE_SKINNED_VERTEX_COLORED ||
material.type == RAW_MATERIAL_TYPE_TRANSPARENT ||
material.type == RAW_MATERIAL_TYPE_SKINNED_TRANSPARENT;
// find a texture by usage and return it as a TextureData*, or nullptr if none exists.
auto getTex = [&](RawTextureUsage usage)
{
// note that we depend on TextureData.ix == rawTexture's index
return (material.textures[usage] >= 0) ? gltf->textures.ptrs[material.textures[usage]].get() : nullptr;
};
std::shared_ptr<PBRMetallicRoughness> pbrMetRough;
if (options.usePBRMetRough) {
pbrMetRough.reset(new PBRMetallicRoughness(getTex(RAW_TEXTURE_USAGE_DIFFUSE), material.diffuseFactor));
}
std::shared_ptr<PBRSpecularGlossiness> pbrSpecGloss;
if (options.usePBRSpecGloss) {
pbrSpecGloss.reset(
new PBRSpecularGlossiness(
getTex(RAW_TEXTURE_USAGE_DIFFUSE), material.diffuseFactor,
getTex(RAW_TEXTURE_USAGE_SPECULAR), material.specularFactor, material.shininess));
}
std::shared_ptr<KHRCommonMats> khrComMat;
if (options.useKHRMatCom) {
auto type = KHRCommonMats::MaterialType::Constant;
if (material.shadingModel == "Lambert") {
type = KHRCommonMats::MaterialType::Lambert;
} else if (material.shadingModel == "Blinn") {
type = KHRCommonMats::MaterialType::Blinn;
} else if (material.shadingModel == "Phong") {
type = KHRCommonMats::MaterialType::Phong;
}
khrComMat.reset(
new KHRCommonMats(
type,
getTex(RAW_TEXTURE_USAGE_SHININESS), material.shininess,
getTex(RAW_TEXTURE_USAGE_AMBIENT), material.ambientFactor,
getTex(RAW_TEXTURE_USAGE_DIFFUSE), material.diffuseFactor,
getTex(RAW_TEXTURE_USAGE_SPECULAR), material.specularFactor));
}
std::shared_ptr<MaterialData> mData = gltf->materials.hold(
new MaterialData(
material.name, isTransparent, getTex(RAW_TEXTURE_USAGE_NORMAL),
getTex(RAW_TEXTURE_USAGE_EMISSIVE), material.emissiveFactor,
khrComMat, pbrMetRough, pbrSpecGloss));
materialsByName[materialHash(material)] = mData;
}
//
// surfaces
//
// in GLTF 2.0, the structural limitation is that a node can
// only belong to a single mesh. A mesh can however contain any
// number of primitives, which are essentially little meshes.
//
// so each RawSurface turns into a primitive, and we sort them
// by root node using this map; one mesh per node.
for (size_t surfaceIndex = 0; surfaceIndex < materialModels.size(); surfaceIndex++) {
const RawModel &surfaceModel = materialModels[surfaceIndex];
assert(surfaceModel.GetSurfaceCount() == 1);
const RawSurface &rawSurface = surfaceModel.GetSurface(0);
const std::string surfaceName = std::to_string(surfaceIndex) + "_" + rawSurface.name;
const RawMaterial &rawMaterial = surfaceModel.GetMaterial(surfaceModel.GetTriangle(0).materialIndex);
if (rawMaterial.textures[RAW_TEXTURE_USAGE_DIFFUSE] < 0 &&
(surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_COLOR) == 0) {
if (verboseOutput) {
fmt::printf("Warning: surface %s has neither texture nor vertex colors.\n", surfaceName.c_str());
}
}
const MaterialData &mData = require(materialsByName, materialHash(rawMaterial));
std::string nodeName = rawSurface.nodeName;
NodeData &meshNode = require(nodesByName, nodeName);
MeshData *mesh = nullptr;
auto meshIter = meshByNodeName.find(nodeName);
if (meshIter != meshByNodeName.end()) {
mesh = meshIter->second.get();
} else {
auto meshPtr = gltf->meshes.hold(new MeshData(rawSurface.name));
meshByNodeName[nodeName] = meshPtr;
meshNode.SetMesh(meshPtr->ix);
mesh = meshPtr.get();
}
//
// surface skin
//
if (!rawSurface.jointNames.empty()) {
if (meshNode.skin == -1) {
// glTF uses column-major matrices
std::vector<Mat4f> inverseBindMatrices;
for (const auto &inverseBindMatrice : rawSurface.inverseBindMatrices) {
inverseBindMatrices.push_back(inverseBindMatrice.Transpose());
}
std::vector<uint32_t> jointIndexes;
for (const auto &jointName : rawSurface.jointNames) {
jointIndexes.push_back(require(nodesByName, jointName).ix);
}
// Write out inverseBindMatrices
auto accIBM = gltf->AddAccessorAndView(buffer, GLT_MAT4F, inverseBindMatrices);
auto skeletonRoot = require(nodesByName, rawSurface.skeletonRootName);
auto skin = *gltf->skins.hold(new SkinData(jointIndexes, *accIBM, skeletonRoot));
meshNode.SetSkin(skin.ix);
}
}
std::shared_ptr<PrimitiveData> primitive;
if (options.useDraco) {
int triangleCount = surfaceModel.GetTriangleCount();
// initialize Draco mesh with vertex index information
std::shared_ptr<draco::Mesh> dracoMesh;
dracoMesh->SetNumFaces(static_cast<size_t>(triangleCount));
for (uint32_t ii = 0; ii < triangleCount; ii++) {
draco::Mesh::Face face;
face[0] = surfaceModel.GetTriangle(ii).verts[0];
face[1] = surfaceModel.GetTriangle(ii).verts[1];
face[2] = surfaceModel.GetTriangle(ii).verts[2];
dracoMesh->SetFace(draco::FaceIndex(ii), face);
}
AccessorData &indexes = *gltf->accessors.hold(new AccessorData(GLT_USHORT));
indexes.count = 3 * triangleCount;
primitive.reset(new PrimitiveData(indexes, mData, dracoMesh));
} else {
const AccessorData &indexes = *gltf->AddAccessorWithView(
*gltf->GetAlignedBufferView(buffer, BufferViewData::GL_ELEMENT_ARRAY_BUFFER),
GLT_USHORT, getIndexArray(surfaceModel));
primitive.reset(new PrimitiveData(indexes, mData));
};
//
// surface vertices
//
{
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_POSITION) != 0) {
const AttributeDefinition<Vec3f> ATTR_POSITION("POSITION", &RawVertex::position,
GLT_VEC3F, draco::GeometryAttribute::POSITION, draco::DT_FLOAT32);
auto accessor = gltf->AddAttributeToPrimitive<Vec3f>(
buffer, surfaceModel, *primitive, ATTR_POSITION);
accessor->min = toStdVec(rawSurface.bounds.min);
accessor->max = toStdVec(rawSurface.bounds.max);
}
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_NORMAL) != 0) {
const AttributeDefinition<Vec3f> ATTR_NORMAL("NORMAL", &RawVertex::normal,
GLT_VEC3F, draco::GeometryAttribute::NORMAL, draco::DT_FLOAT32);
gltf->AddAttributeToPrimitive<Vec3f>(buffer, surfaceModel, *primitive, ATTR_NORMAL);
}
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_TANGENT) != 0) {
const AttributeDefinition<Vec4f> ATTR_TANGENT("TANGENT", &RawVertex::tangent, GLT_VEC4F);
gltf->AddAttributeToPrimitive<Vec4f>(buffer, surfaceModel, *primitive, ATTR_TANGENT);
}
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_COLOR) != 0) {
const AttributeDefinition<Vec4f> ATTR_COLOR("COLOR_0", &RawVertex::color, GLT_VEC4F,
draco::GeometryAttribute::COLOR, draco::DT_FLOAT32);
gltf->AddAttributeToPrimitive<Vec4f>(buffer, surfaceModel, *primitive, ATTR_COLOR);
}
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_UV0) != 0) {
const AttributeDefinition<Vec2f> ATTR_TEXCOORD_0("TEXCOORD_0", &RawVertex::uv0,
GLT_VEC2F, draco::GeometryAttribute::TEX_COORD, draco::DT_FLOAT32);
gltf->AddAttributeToPrimitive<Vec2f>(buffer, surfaceModel, *primitive, ATTR_TEXCOORD_0);
}
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_UV1) != 0) {
const AttributeDefinition<Vec2f> ATTR_TEXCOORD_1("TEXCOORD_1", &RawVertex::uv1,
GLT_VEC2F, draco::GeometryAttribute::TEX_COORD, draco::DT_FLOAT32);
gltf->AddAttributeToPrimitive<Vec2f>(buffer, surfaceModel, *primitive, ATTR_TEXCOORD_1);
}
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_JOINT_INDICES) != 0) {
const AttributeDefinition<Vec4i> ATTR_JOINTS("JOINTS_0", &RawVertex::jointIndices,
GLT_VEC4I, draco::GeometryAttribute::GENERIC, draco::DT_UINT16);
gltf->AddAttributeToPrimitive<Vec4i>(buffer, surfaceModel, *primitive, ATTR_JOINTS);
}
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS) != 0) {
const AttributeDefinition<Vec4f> ATTR_WEIGHTS("WEIGHTS_0", &RawVertex::jointWeights,
GLT_VEC4F, draco::GeometryAttribute::GENERIC, draco::DT_FLOAT32);
gltf->AddAttributeToPrimitive<Vec4f>(buffer, surfaceModel, *primitive, ATTR_WEIGHTS);
}
}
if (options.useDraco) {
// Set up the encoder.
draco::Encoder encoder;
// TODO: generalize / allow configuration
encoder.SetSpeedOptions(5, 5);
encoder.SetAttributeQuantization(draco::GeometryAttribute::POSITION, 14);
encoder.SetAttributeQuantization(draco::GeometryAttribute::TEX_COORD, 10);
encoder.SetAttributeQuantization(draco::GeometryAttribute::NORMAL, 10);
encoder.SetAttributeQuantization(draco::GeometryAttribute::COLOR, 8);
encoder.SetAttributeQuantization(draco::GeometryAttribute::GENERIC, 8);
encoder.SetEncodingMethod(draco::MeshEncoderMethod::MESH_EDGEBREAKER_ENCODING);
draco::EncoderBuffer dracoBuffer;
draco::Status status = encoder.EncodeMeshToBuffer(*primitive->dracoMesh, &dracoBuffer);
assert(status.code() == draco::Status::OK);
auto view = gltf->AddRawBufferView(buffer, dracoBuffer.data(), dracoBuffer.size());
primitive->NoteDracoBuffer(*view);
}
mesh->AddPrimitive(primitive);
}
//
// cameras
//
for (int i = 0; i < raw.GetCameraCount(); i++) {
const RawCamera &cam = raw.GetCamera(i);
CameraData &camera = *gltf->cameras.hold(new CameraData());
camera.name = cam.name;
if (cam.mode == RawCamera::CAMERA_MODE_PERSPECTIVE) {
camera.type = "perspective";
camera.aspectRatio = cam.perspective.aspectRatio;
camera.yfov = cam.perspective.fovDegreesY * ((float) M_PI / 180.0f);
camera.znear = cam.perspective.nearZ;
camera.zfar = cam.perspective.farZ;
} else {
camera.type = "orthographic";
camera.xmag = cam.orthographic.magX;
camera.ymag = cam.orthographic.magY;
camera.znear = cam.orthographic.nearZ;
camera.zfar = cam.orthographic.farZ;
}
// Add the camera to the node hierarchy.
auto iter = nodesByName.find(cam.nodeName);
if (iter == nodesByName.end()) {
fmt::printf("Warning: Camera node name %s does not exist.\n", cam.nodeName);
continue;
}
iter->second->AddCamera(cam.name);
}
}
NodeData &rootNode = require(nodesByName, "RootNode");
const SceneData &rootScene = *gltf->scenes.hold(new SceneData(defaultSceneName, rootNode));
if (options.outputBinary) {
// note: glTF binary is little-endian
const char glbHeader[] = {
'g', 'l', 'T', 'F', // magic
0x02, 0x00, 0x00, 0x00, // version
0x00, 0x00, 0x00, 0x00, // total length: written in later
};
gltfOutStream.write(glbHeader, 12);
// binary glTF 2.0 has a sub-header for each of the JSON and BIN chunks
const char glb2JsonHeader[] = {
0x00, 0x00, 0x00, 0x00, // chunk length: written in later
'J', 'S', 'O', 'N', // chunk type: 0x4E4F534A aka JSON
};
gltfOutStream.write(glb2JsonHeader, 8);
}
{
std::vector<std::string> extensionsUsed, extensionsRequired;
if (options.useKHRMatCom) {
extensionsUsed.push_back(KHR_MATERIALS_COMMON);
if (!options.usePBRSpecGloss && !options.usePBRMetRough) {
extensionsRequired.push_back(KHR_MATERIALS_COMMON);
}
}
if (options.usePBRSpecGloss) {
extensionsUsed.push_back(KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS);
if (!options.useKHRMatCom && !options.usePBRMetRough) {
extensionsRequired.push_back(KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS);
}
}
if (options.useDraco) {
extensionsUsed.push_back(KHR_DRACO_MESH_COMPRESSION);
extensionsRequired.push_back(KHR_DRACO_MESH_COMPRESSION);
}
json glTFJson {
{ "asset", {
{ "generator", "FBX2glTF" },
{ "version", "2.0" }}},
{ "scene", rootScene.ix }
};
if (!extensionsUsed.empty()) {
glTFJson["extensionsUsed"] = extensionsUsed;
}
if (!extensionsRequired.empty()) {
glTFJson["extensionsRequired"] = extensionsRequired;
}
gltf->serializeHolders(glTFJson);
gltfOutStream << glTFJson.dump(options.outputBinary ? 0 : 4);
}
if (options.outputBinary) {
uint32_t jsonLength = (uint32_t) gltfOutStream.tellp() - 20;
// the binary body must begin on a 4-aligned address, so pad json with spaces if necessary
while ((jsonLength % 4) != 0) {
gltfOutStream.put(' ');
jsonLength++;
}
uint32_t binHeader = (uint32_t) gltfOutStream.tellp();
// binary glTF 2.0 has a sub-header for each of the JSON and BIN chunks
const char glb2BinaryHeader[] = {
0x00, 0x00, 0x00, 0x00, // chunk length: written in later
'B', 'I', 'N', 0x00, // chunk type: 0x004E4942 aka BIN
};
gltfOutStream.write(glb2BinaryHeader, 8);
// append binary buffer directly to .glb file
uint32_t binaryLength = gltf->binary->size();
gltfOutStream.write((const char *) &(*gltf->binary)[0], binaryLength);
while ((binaryLength % 4) != 0) {
gltfOutStream.put('\0');
binaryLength++;
}
uint32_t totalLength = (uint32_t) gltfOutStream.tellp();
// seek back to sub-header for json chunk
gltfOutStream.seekp(8);
// write total length, little-endian
gltfOutStream.put((totalLength >> 0) & 0xFF);
gltfOutStream.put((totalLength >> 8) & 0xFF);
gltfOutStream.put((totalLength >> 16) & 0xFF);
gltfOutStream.put((totalLength >> 24) & 0xFF);
// write JSON length, little-endian
gltfOutStream.put((jsonLength >> 0) & 0xFF);
gltfOutStream.put((jsonLength >> 8) & 0xFF);
gltfOutStream.put((jsonLength >> 16) & 0xFF);
gltfOutStream.put((jsonLength >> 24) & 0xFF);
// seek back to the gltf 2.0 binary chunk header
gltfOutStream.seekp(binHeader);
// write total length, little-endian
gltfOutStream.put((binaryLength >> 0) & 0xFF);
gltfOutStream.put((binaryLength >> 8) & 0xFF);
gltfOutStream.put((binaryLength >> 16) & 0xFF);
gltfOutStream.put((binaryLength >> 24) & 0xFF);
// be tidy and return write pointer to end-of-file
gltfOutStream.seekp(0, std::ios::end);
}
return new ModelData(gltf->binary);
}

View File

@ -1,204 +0,0 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#ifndef __RAW2GLTF_H__
#define __RAW2GLTF_H__
#include <memory>
#include <string>
// This can be a macro under Windows, confusing Draco
#undef ERROR
#include <draco/compression/encode.h>
#include <json.hpp>
using json = nlohmann::json;
#include "FBX2glTF.h"
#include "RawModel.h"
static const std::string KHR_DRACO_MESH_COMPRESSION = "KHR_draco_mesh_compression";
static const std::string KHR_MATERIALS_COMMON = "KHR_materials_common";
static const std::string KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS = "KHR_materials_pbrSpecularGlossiness";
static const std::string extBufferFilename = "buffer.bin";
/**
* User-supplied options that dictate the nature of the glTF being generated.
*/
struct GltfOptions
{
/**
* If negative, disabled. Otherwise, a bitfield of RawVertexAttributes that
* specify the largest set of attributes that'll ever be kept for a vertex.
* The special bit RAW_VERTEX_ATTRIBUTE_AUTO triggers smart mode, where the
* attributes to keep are inferred from which textures are supplied.
*/
int keepAttribs;
/** Whether to output a .glb file, the binary format of glTF. */
bool outputBinary;
/** If non-binary, whether to inline all resources, for a single (large) .glTF file. */
bool embedResources;
/** Whether to use KHR_draco_mesh_compression to minimize static geometry size. */
bool useDraco;
/** Whether to use KHR_materials_common to extend materials definitions. */
bool useKHRMatCom;
/** Whether to populate the pbrMetallicRoughness substruct in materials. */
bool usePBRMetRough;
/** Whether to use KHR_materials_pbrSpecularGlossiness to extend material definitions. */
bool usePBRSpecGloss;
};
struct ComponentType {
// OpenGL Datatype enums
enum GL_DataType
{
GL_BYTE = 5120,
GL_UNSIGNED_BYTE,
GL_SHORT,
GL_UNSIGNED_SHORT,
GL_INT,
GL_UNSIGNED_INT,
GL_FLOAT
};
const GL_DataType glType;
const unsigned int size;
};
static const ComponentType CT_USHORT = {ComponentType::GL_UNSIGNED_SHORT, 2};
static const ComponentType CT_FLOAT = {ComponentType::GL_FLOAT, 4};
// Map our low-level data types for glTF output
struct GLType {
GLType(const ComponentType &componentType, unsigned int count, const std::string dataType)
: componentType(componentType),
count(count),
dataType(dataType)
{}
unsigned int byteStride() const { return componentType.size * count; }
void write(uint8_t *buf, const float scalar) const { *((float *) buf) = scalar; }
void write(uint8_t *buf, const uint16_t scalar) const { *((uint16_t *) buf) = scalar; }
template<class T, int d>
void write(uint8_t *buf, const mathfu::Vector<T, d> &vector) const {
for (int ii = 0; ii < d; ii ++) {
((T *)buf)[ii] = vector(ii);
}
}
template<class T, int d>
void write(uint8_t *buf, const mathfu::Matrix<T, d> &matrix) const {
// three matrix types require special alignment considerations that we don't handle
// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#data-alignment
assert(!(sizeof(T) == 1 && d == 2));
assert(!(sizeof(T) == 1 && d == 3));
assert(!(sizeof(T) == 2 && d == 2));
for (int col = 0; col < d; col ++) {
for (int row = 0; row < d; row ++) {
// glTF matrices are column-major
((T *)buf)[col * d + row] = matrix(row, col);
}
}
}
template<class T>
void write(uint8_t *buf, const mathfu::Quaternion<T> &quaternion) const {
for (int ii = 0; ii < 3; ii++) {
((T *)buf)[ii] = quaternion.vector()(ii);
}
((T *)buf)[3] = quaternion.scalar();
}
const ComponentType componentType;
const uint8_t count;
const std::string dataType;
};
static const GLType GLT_FLOAT = {CT_FLOAT, 1, "SCALAR"};
static const GLType GLT_USHORT = {CT_USHORT, 1, "SCALAR"};
static const GLType GLT_VEC2F = {CT_FLOAT, 2, "VEC2"};
static const GLType GLT_VEC3F = {CT_FLOAT, 3, "VEC3"};
static const GLType GLT_VEC4F = {CT_FLOAT, 4, "VEC4"};
static const GLType GLT_VEC4I = {CT_USHORT, 4, "VEC4"};
static const GLType GLT_MAT2F = {CT_USHORT, 4, "MAT2"};
static const GLType GLT_MAT3F = {CT_USHORT, 9, "MAT3"};
static const GLType GLT_MAT4F = {CT_FLOAT, 16, "MAT4"};
static const GLType GLT_QUATF = {CT_FLOAT, 4, "VEC4"};
/**
* The base of any indexed glTF entity.
*/
struct Holdable
{
uint32_t ix;
virtual json serialize() const = 0;
};
template<class T>
struct AttributeDefinition
{
const std::string gltfName;
const T RawVertex::* rawAttributeIx;
const GLType glType;
const draco::GeometryAttribute::Type dracoAttribute;
const draco::DataType dracoComponentType;
AttributeDefinition(
const std::string gltfName, const T RawVertex::*rawAttributeIx, const GLType &_glType,
const draco::GeometryAttribute::Type dracoAttribute, const draco::DataType dracoComponentType)
: gltfName(gltfName),
rawAttributeIx(rawAttributeIx),
glType(_glType),
dracoAttribute(dracoAttribute),
dracoComponentType(dracoComponentType) {}
AttributeDefinition(
const std::string gltfName, const T RawVertex::*rawAttributeIx, const GLType &_glType)
: gltfName(gltfName),
rawAttributeIx(rawAttributeIx),
glType(_glType),
dracoAttribute(draco::GeometryAttribute::INVALID),
dracoComponentType(draco::DataType::DT_INVALID) {}
};
struct AccessorData;
struct AnimationData;
struct BufferData;
struct BufferViewData;
struct CameraData;
struct GLTFData;
struct ImageData;
struct MaterialData;
struct MeshData;
struct NodeData;
struct PrimitiveData;
struct SamplerData;
struct SceneData;
struct SkinData;
struct TextureData;
struct ModelData
{
explicit ModelData(std::shared_ptr<const std::vector<uint8_t> > const &_binary)
: binary(_binary)
{
}
std::shared_ptr<const std::vector<uint8_t> > const binary;
};
ModelData *Raw2Gltf(
std::ofstream &gltfOutStream,
const RawModel &raw,
const GltfOptions &options
);
#endif // !__RAW2GLTF_H__

View File

@ -1,542 +0,0 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#include <vector>
#include <string>
#include <unordered_map>
#include <map>
#if defined( __unix__ )
#include <algorithm>
#endif
#include "FBX2glTF.h"
#include "utils/String_Utils.h"
#include "utils/Image_Utils.h"
#include "RawModel.h"
static float Log2f(float f)
{
return logf(f) * 1.442695041f;
}
bool RawVertex::operator==(const RawVertex &other) const
{
return (position == other.position) &&
(normal == other.normal) &&
(tangent == other.tangent) &&
(binormal == other.binormal) &&
(color == other.color) &&
(uv0 == other.uv0) &&
(uv1 == other.uv1) &&
(jointIndices == other.jointIndices) &&
(jointWeights == other.jointWeights) &&
(polarityUv0 == other.polarityUv0);
}
size_t RawVertex::Difference(const RawVertex &other) const
{
size_t attributes = 0;
if (position != other.position) { attributes |= RAW_VERTEX_ATTRIBUTE_POSITION; }
if (normal != other.normal) { attributes |= RAW_VERTEX_ATTRIBUTE_NORMAL; }
if (tangent != other.tangent) { attributes |= RAW_VERTEX_ATTRIBUTE_TANGENT; }
if (binormal != other.binormal) { attributes |= RAW_VERTEX_ATTRIBUTE_BINORMAL; }
if (color != other.color) { attributes |= RAW_VERTEX_ATTRIBUTE_COLOR; }
if (uv0 != other.uv0) { attributes |= RAW_VERTEX_ATTRIBUTE_UV0; }
if (uv1 != other.uv1) { attributes |= RAW_VERTEX_ATTRIBUTE_UV1; }
// Always need both or neither.
if (jointIndices != other.jointIndices) { attributes |= RAW_VERTEX_ATTRIBUTE_JOINT_INDICES | RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS; }
if (jointWeights != other.jointWeights) { attributes |= RAW_VERTEX_ATTRIBUTE_JOINT_INDICES | RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS; }
return attributes;
}
RawModel::RawModel()
: vertexAttributes(0)
{
}
void RawModel::AddVertexAttribute(const RawVertexAttribute attrib)
{
vertexAttributes |= attrib;
}
int RawModel::AddVertex(const RawVertex &vertex)
{
auto it = vertexHash.find(vertex);
if (it != vertexHash.end()) {
return it->second;
}
vertexHash.emplace(vertex, (int) vertices.size());
vertices.push_back(vertex);
return (int) vertices.size() - 1;
}
int RawModel::AddTriangle(const int v0, const int v1, const int v2, const int materialIndex, const int surfaceIndex)
{
const RawTriangle triangle = {{v0, v1, v2}, materialIndex, surfaceIndex};
triangles.push_back(triangle);
return (int) triangles.size() - 1;
}
int RawModel::AddTexture(const char *name, const char *fileName, const RawTextureUsage usage)
{
if (name[0] == '\0') {
return -1;
}
for (size_t i = 0; i < textures.size(); i++) {
if (Gltf::StringUtils::CompareNoCase(textures[i].name, name) == 0 && textures[i].usage == usage) {
return (int) i;
}
}
const ImageProperties properties = GetImageProperties(fileName);
RawTexture texture;
texture.name = name;
texture.width = properties.width;
texture.height = properties.height;
texture.mipLevels = (int) ceilf(Log2f(std::max((float) properties.width, (float) properties.height)));
texture.usage = usage;
texture.occlusion = (properties.occlusion == IMAGE_TRANSPARENT) ?
RAW_TEXTURE_OCCLUSION_TRANSPARENT : RAW_TEXTURE_OCCLUSION_OPAQUE;
texture.fileName = fileName;
textures.emplace_back(texture);
return (int) textures.size() - 1;
}
int RawModel::AddMaterial(const RawMaterial &material)
{
return AddMaterial(
material.name.c_str(), material.shadingModel.c_str(), material.type, material.textures, material.ambientFactor,
material.diffuseFactor, material.specularFactor, material.emissiveFactor, material.shininess);
}
int RawModel::AddMaterial(
const char *name, const char *shadingModel, const RawMaterialType materialType,
const int textures[RAW_TEXTURE_USAGE_MAX], const Vec3f ambientFactor,
const Vec4f diffuseFactor, const Vec3f specularFactor,
const Vec3f emissiveFactor, float shinineness)
{
for (size_t i = 0; i < materials.size(); i++) {
if (materials[i].name != name) {
continue;
}
if (materials[i].shadingModel != shadingModel) {
continue;
}
if (materials[i].type != materialType) {
continue;
}
if (materials[i].ambientFactor != ambientFactor ||
materials[i].diffuseFactor != diffuseFactor ||
materials[i].specularFactor != specularFactor ||
materials[i].emissiveFactor != emissiveFactor ||
materials[i].shininess != shinineness) {
continue;
}
bool match = true;
for (int j = 0; match && j < RAW_TEXTURE_USAGE_MAX; j++) {
match = match && (materials[i].textures[j] == textures[j]);
}
if (match) {
return (int) i;
}
}
RawMaterial material;
material.name = name;
material.shadingModel = shadingModel;
material.type = materialType;
material.ambientFactor = ambientFactor;
material.diffuseFactor = diffuseFactor;
material.specularFactor = specularFactor;
material.emissiveFactor = emissiveFactor;
material.shininess = shinineness;
for (int i = 0; i < RAW_TEXTURE_USAGE_MAX; i++) {
material.textures[i] = textures[i];
}
materials.emplace_back(material);
return (int) materials.size() - 1;
}
int RawModel::AddSurface(const RawSurface &surface)
{
for (size_t i = 0; i < surfaces.size(); i++) {
if (Gltf::StringUtils::CompareNoCase(surfaces[i].name, surface.name) == 0) {
return (int) i;
}
}
surfaces.emplace_back(surface);
return (int) (surfaces.size() - 1);
}
int RawModel::AddSurface(const char *name, const char *nodeName)
{
assert(name[0] != '\0');
for (size_t i = 0; i < surfaces.size(); i++) {
if (Gltf::StringUtils::CompareNoCase(surfaces[i].name, name) == 0) {
return (int) i;
}
}
RawSurface surface;
surface.name = name;
surface.nodeName = nodeName;
surface.bounds.Clear();
surface.discrete = false;
surface.skinRigid = false;
surfaces.emplace_back(surface);
return (int) (surfaces.size() - 1);
}
int RawModel::AddAnimation(const RawAnimation &animation)
{
animations.emplace_back(animation);
return (int) (animations.size() - 1);
}
int RawModel::AddNode(const RawNode &node)
{
for (size_t i = 0; i < nodes.size(); i++) {
if (Gltf::StringUtils::CompareNoCase(nodes[i].name.c_str(), node.name) == 0) {
return (int) i;
}
}
nodes.emplace_back(node);
return (int) nodes.size() - 1;
}
int RawModel::AddCameraPerspective(
const char *name, const char *nodeName, const float aspectRatio, const float fovDegreesX, const float fovDegreesY, const float nearZ,
const float farZ)
{
RawCamera camera;
camera.name = name;
camera.nodeName = nodeName;
camera.mode = RawCamera::CAMERA_MODE_PERSPECTIVE;
camera.perspective.aspectRatio = aspectRatio;
camera.perspective.fovDegreesX = fovDegreesX;
camera.perspective.fovDegreesY = fovDegreesY;
camera.perspective.nearZ = nearZ;
camera.perspective.farZ = farZ;
cameras.emplace_back(camera);
return (int) cameras.size() - 1;
}
int RawModel::AddCameraOrthographic(
const char *name, const char *nodeName, const float magX, const float magY, const float nearZ, const float farZ)
{
RawCamera camera;
camera.name = name;
camera.nodeName = nodeName;
camera.mode = RawCamera::CAMERA_MODE_ORTHOGRAPHIC;
camera.orthographic.magX = magX;
camera.orthographic.magY = magY;
camera.orthographic.nearZ = nearZ;
camera.orthographic.farZ = farZ;
cameras.emplace_back(camera);
return (int) cameras.size() - 1;
}
int RawModel::AddNode(const char *name, const char *parentName)
{
assert(name[0] != '\0');
for (size_t i = 0; i < nodes.size(); i++) {
if (Gltf::StringUtils::CompareNoCase(nodes[i].name, name) == 0) {
return (int) i;
}
}
RawNode joint;
joint.isJoint = false;
joint.name = name;
joint.parentName = parentName;
joint.translation = Vec3f(0, 0, 0);
joint.rotation = Quatf(0, 0, 0, 1);
joint.scale = Vec3f(1, 1, 1);
nodes.emplace_back(joint);
return (int) nodes.size() - 1;
}
void RawModel::Condense()
{
// Only keep surfaces that are referenced by one or more triangles.
{
std::vector<RawSurface> oldSurfaces = surfaces;
surfaces.clear();
for (auto &triangle : triangles) {
const RawSurface &surface = oldSurfaces[triangle.surfaceIndex];
const int surfaceIndex = AddSurface(surface.name.c_str(), surface.nodeName.c_str());
surfaces[surfaceIndex] = surface;
triangle.surfaceIndex = surfaceIndex;
}
}
// Only keep materials that are referenced by one or more triangles.
{
std::vector<RawMaterial> oldMaterials = materials;
materials.clear();
for (auto &triangle : triangles) {
const RawMaterial &material = oldMaterials[triangle.materialIndex];
const int materialIndex = AddMaterial(material);
materials[materialIndex] = material;
triangle.materialIndex = materialIndex;
}
}
// Only keep textures that are referenced by one or more materials.
{
std::vector<RawTexture> oldTextures = textures;
textures.clear();
for (auto &material : materials) {
for (int j = 0; j < RAW_TEXTURE_USAGE_MAX; j++) {
if (material.textures[j] >= 0) {
const RawTexture &texture = oldTextures[material.textures[j]];
const int textureIndex = AddTexture(texture.name.c_str(), texture.fileName.c_str(), texture.usage);
textures[textureIndex] = texture;
material.textures[j] = textureIndex;
}
}
}
}
// Only keep vertices that are referenced by one or more triangles.
{
std::vector<RawVertex> oldVertices = vertices;
vertexHash.clear();
vertices.clear();
for (auto &triangle : triangles) {
for (int j = 0; j < 3; j++) {
triangle.verts[j] = AddVertex(oldVertices[triangle.verts[j]]);
}
}
}
}
void RawModel::TransformTextures(const std::vector<std::function<Vec2f(Vec2f)>> &transforms)
{
for (auto &vertice : vertices) {
if ((vertexAttributes & RAW_VERTEX_ATTRIBUTE_UV0) != 0) {
for (const auto &fun : transforms) {
vertice.uv0 = fun(vertice.uv0);
}
}
if ((vertexAttributes & RAW_VERTEX_ATTRIBUTE_UV1) != 0) {
for (const auto &fun : transforms) {
vertice.uv1 = fun(vertice.uv1);
}
}
}
}
struct TriangleModelSortPos
{
static bool Compare(const RawTriangle &a, const RawTriangle &b)
{
if (a.materialIndex != b.materialIndex) {
return a.materialIndex < b.materialIndex;
}
if (a.surfaceIndex != b.surfaceIndex) {
return a.surfaceIndex < b.surfaceIndex;
}
return a.verts[0] < b.verts[0];
}
};
struct TriangleModelSortNeg
{
static bool Compare(const RawTriangle &a, const RawTriangle &b)
{
if (a.materialIndex != b.materialIndex) {
return a.materialIndex < b.materialIndex;
}
if (a.surfaceIndex != b.surfaceIndex) {
return a.surfaceIndex < b.surfaceIndex;
}
return a.verts[0] > b.verts[0];
}
};
void RawModel::CreateMaterialModels(
std::vector<RawModel> &materialModels, const int maxModelVertices, const int keepAttribs, const bool forceDiscrete) const
{
// Sort all triangles based on material first, then surface, then first vertex index.
std::vector<RawTriangle> sortedTriangles;
bool invertedTransparencySort = true;
if (invertedTransparencySort) {
// Split the triangles into opaque and transparent triangles.
std::vector<RawTriangle> opaqueTriangles;
std::vector<RawTriangle> transparentTriangles;
for (const auto &triangle : triangles) {
const int materialIndex = triangle.materialIndex;
if (materialIndex < 0) {
opaqueTriangles.push_back(triangle);
continue;
}
const int textureIndex = materials[materialIndex].textures[RAW_TEXTURE_USAGE_DIFFUSE];
if (textureIndex < 0) {
if (vertices[triangle.verts[0]].color.w < 1.0f ||
vertices[triangle.verts[1]].color.w < 1.0f ||
vertices[triangle.verts[2]].color.w < 1.0f) {
transparentTriangles.push_back(triangle);
continue;
}
opaqueTriangles.push_back(triangle);
continue;
}
if (textures[textureIndex].occlusion == RAW_TEXTURE_OCCLUSION_TRANSPARENT) {
transparentTriangles.push_back(triangle);
} else {
opaqueTriangles.push_back(triangle);
}
}
// Sort the opaque triangles.
std::sort(opaqueTriangles.begin(), opaqueTriangles.end(), TriangleModelSortPos::Compare);
// Sort the transparent triangles in the reverse direction.
std::sort(transparentTriangles.begin(), transparentTriangles.end(), TriangleModelSortNeg::Compare);
// Add the triangles to the sorted list.
for (const auto &opaqueTriangle : opaqueTriangles) {
sortedTriangles.push_back(opaqueTriangle);
}
for (const auto &transparentTriangle : transparentTriangles) {
sortedTriangles.push_back(transparentTriangle);
}
} else {
sortedTriangles = triangles;
std::sort(sortedTriangles.begin(), sortedTriangles.end(), TriangleModelSortPos::Compare);
}
// Overestimate the number of models that will be created to avoid massive reallocation.
int discreteCount = 0;
for (const auto &surface : surfaces) {
discreteCount += (surface.discrete != false);
}
materialModels.clear();
materialModels.reserve(materials.size() + discreteCount);
const RawVertex defaultVertex;
// Create a separate model for each material.
RawModel *model;
for (size_t i = 0; i < sortedTriangles.size(); i++) {
if (sortedTriangles[i].materialIndex < 0 || sortedTriangles[i].surfaceIndex < 0) {
continue;
}
if (i == 0 ||
model->GetVertexCount() > maxModelVertices - 3 ||
sortedTriangles[i].materialIndex != sortedTriangles[i - 1].materialIndex ||
(sortedTriangles[i].surfaceIndex != sortedTriangles[i - 1].surfaceIndex &&
(forceDiscrete || surfaces[sortedTriangles[i].surfaceIndex].discrete ||
surfaces[sortedTriangles[i - 1].surfaceIndex].discrete))) {
materialModels.resize(materialModels.size() + 1);
model = &materialModels[materialModels.size() - 1];
}
// FIXME: will have to unlink from the nodes, transform both surfaces into a
// common space, and reparent to a new node with appropriate transform.
const int prevSurfaceCount = model->GetSurfaceCount();
const int materialIndex = model->AddMaterial(materials[sortedTriangles[i].materialIndex]);
const int surfaceIndex = model->AddSurface(surfaces[sortedTriangles[i].surfaceIndex]);
RawSurface &rawSurface = model->GetSurface(surfaceIndex);
if (model->GetSurfaceCount() > prevSurfaceCount) {
const std::vector<std::string> &jointNames = surfaces[sortedTriangles[i].surfaceIndex].jointNames;
for (const auto &jointName : jointNames) {
const int nodeIndex = GetNodeByName(jointName.c_str());
assert(nodeIndex != -1);
model->AddNode(GetNode(nodeIndex));
}
rawSurface.bounds.Clear();
}
int verts[3];
for (int j = 0; j < 3; j++) {
RawVertex vertex = vertices[sortedTriangles[i].verts[j]];
if (keepAttribs != -1) {
int keep = keepAttribs;
if ((keepAttribs & RAW_VERTEX_ATTRIBUTE_POSITION) != 0) {
keep |= RAW_VERTEX_ATTRIBUTE_JOINT_INDICES | RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS;
}
if ((keepAttribs & RAW_VERTEX_ATTRIBUTE_AUTO) != 0) {
keep |= RAW_VERTEX_ATTRIBUTE_POSITION;
const RawMaterial &mat = model->GetMaterial(materialIndex);
if (mat.textures[RAW_TEXTURE_USAGE_DIFFUSE] != -1) {
keep |= RAW_VERTEX_ATTRIBUTE_UV0;
}
if (mat.textures[RAW_TEXTURE_USAGE_NORMAL] != -1) {
keep |= RAW_VERTEX_ATTRIBUTE_NORMAL |
RAW_VERTEX_ATTRIBUTE_TANGENT |
RAW_VERTEX_ATTRIBUTE_BINORMAL |
RAW_VERTEX_ATTRIBUTE_UV0;
}
if (mat.textures[RAW_TEXTURE_USAGE_SPECULAR] != -1) {
keep |= RAW_VERTEX_ATTRIBUTE_NORMAL |
RAW_VERTEX_ATTRIBUTE_UV0;
}
if (mat.textures[RAW_TEXTURE_USAGE_EMISSIVE] != -1) {
keep |= RAW_VERTEX_ATTRIBUTE_UV1;
}
}
if ((keep & RAW_VERTEX_ATTRIBUTE_POSITION) == 0) { vertex.position = defaultVertex.position; }
if ((keep & RAW_VERTEX_ATTRIBUTE_NORMAL) == 0) { vertex.normal = defaultVertex.normal; }
if ((keep & RAW_VERTEX_ATTRIBUTE_TANGENT) == 0) { vertex.tangent = defaultVertex.tangent; }
if ((keep & RAW_VERTEX_ATTRIBUTE_BINORMAL) == 0) { vertex.binormal = defaultVertex.binormal; }
if ((keep & RAW_VERTEX_ATTRIBUTE_COLOR) == 0) { vertex.color = defaultVertex.color; }
if ((keep & RAW_VERTEX_ATTRIBUTE_UV0) == 0) { vertex.uv0 = defaultVertex.uv0; }
if ((keep & RAW_VERTEX_ATTRIBUTE_UV1) == 0) { vertex.uv1 = defaultVertex.uv1; }
if ((keep & RAW_VERTEX_ATTRIBUTE_JOINT_INDICES) == 0) { vertex.jointIndices = defaultVertex.jointIndices; }
if ((keep & RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS) == 0) { vertex.jointWeights = defaultVertex.jointWeights; }
}
verts[j] = model->AddVertex(vertex);
model->vertexAttributes |= vertex.Difference(defaultVertex);
rawSurface.bounds.AddPoint(vertex.position);
}
model->AddTriangle(verts[0], verts[1], verts[2], materialIndex, surfaceIndex);
}
}
int RawModel::GetNodeByName(const char *name) const
{
for (size_t i = 0; i < nodes.size(); i++) {
if (nodes[i].name == name) {
return (int) i;
}
}
return -1;
}

View File

@ -1,333 +0,0 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#ifndef __RAWMODEL_H__
#define __RAWMODEL_H__
#include <unordered_map>
#include <functional>
enum RawVertexAttribute
{
RAW_VERTEX_ATTRIBUTE_POSITION = 1 << 0,
RAW_VERTEX_ATTRIBUTE_NORMAL = 1 << 1,
RAW_VERTEX_ATTRIBUTE_TANGENT = 1 << 2,
RAW_VERTEX_ATTRIBUTE_BINORMAL = 1 << 3,
RAW_VERTEX_ATTRIBUTE_COLOR = 1 << 4,
RAW_VERTEX_ATTRIBUTE_UV0 = 1 << 5,
RAW_VERTEX_ATTRIBUTE_UV1 = 1 << 6,
RAW_VERTEX_ATTRIBUTE_JOINT_INDICES = 1 << 7,
RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS = 1 << 8,
RAW_VERTEX_ATTRIBUTE_AUTO = 1 << 31
};
struct RawVertex
{
RawVertex() :
polarityUv0(false),
pad1(false),
pad2(false),
pad3(false) {}
Vec3f position { 0.0f };
Vec3f normal { 0.0f };
Vec3f binormal { 0.0f };
Vec4f tangent { 0.0f };
Vec4f color { 0.0f };
Vec2f uv0 { 0.0f };
Vec2f uv1 { 0.0f };
Vec4i jointIndices { 0, 0, 0, 0 };
Vec4f jointWeights { 0.0f };
bool polarityUv0;
bool pad1;
bool pad2;
bool pad3;
bool operator==(const RawVertex &other) const;
size_t Difference(const RawVertex &other) const;
};
class VertexHasher
{
public:
size_t operator()(const RawVertex &v) const
{
size_t seed = 5381;
const auto hasher = std::hash<float>{};
seed ^= hasher(v.position[0]) + 0x9e3779b9 + (seed<<6) + (seed>>2);
seed ^= hasher(v.position[1]) + 0x9e3779b9 + (seed<<6) + (seed>>2);
seed ^= hasher(v.position[2]) + 0x9e3779b9 + (seed<<6) + (seed>>2);
return seed;
}
};
struct RawTriangle
{
int verts[3];
int materialIndex;
int surfaceIndex;
};
enum RawTextureUsage
{
RAW_TEXTURE_USAGE_AMBIENT,
RAW_TEXTURE_USAGE_DIFFUSE,
RAW_TEXTURE_USAGE_NORMAL,
RAW_TEXTURE_USAGE_SPECULAR,
RAW_TEXTURE_USAGE_SHININESS,
RAW_TEXTURE_USAGE_EMISSIVE,
RAW_TEXTURE_USAGE_REFLECTION,
RAW_TEXTURE_USAGE_MAX
};
inline std::string DescribeTextureUsage(int usage)
{
if (usage < 0) {
return "<none>";
}
switch (static_cast<RawTextureUsage>(usage)) {
case RAW_TEXTURE_USAGE_AMBIENT:
return "ambient";
case RAW_TEXTURE_USAGE_DIFFUSE:
return "diffuse";
case RAW_TEXTURE_USAGE_NORMAL:
return "normal";
case RAW_TEXTURE_USAGE_SPECULAR:
return "specuar";
case RAW_TEXTURE_USAGE_SHININESS:
return "shininess";
case RAW_TEXTURE_USAGE_EMISSIVE:
return "emissive";
case RAW_TEXTURE_USAGE_REFLECTION:
return "reflection";
case RAW_TEXTURE_USAGE_MAX:
default:
return "unknown";
}
};
enum RawTextureOcclusion
{
RAW_TEXTURE_OCCLUSION_OPAQUE,
RAW_TEXTURE_OCCLUSION_TRANSPARENT
};
struct RawTexture
{
std::string name;
int width;
int height;
int mipLevels;
RawTextureUsage usage;
RawTextureOcclusion occlusion;
std::string fileName;
};
enum RawMaterialType
{
RAW_MATERIAL_TYPE_OPAQUE,
RAW_MATERIAL_TYPE_TRANSPARENT,
RAW_MATERIAL_TYPE_VERTEX_COLORED,
RAW_MATERIAL_TYPE_SKINNED_OPAQUE,
RAW_MATERIAL_TYPE_SKINNED_TRANSPARENT,
RAW_MATERIAL_TYPE_SKINNED_VERTEX_COLORED
};
struct RawMaterial
{
std::string name;
std::string shadingModel; // typically "Surface", "Anisotropic", "Blinn", "Lambert", "Phong", "Phone E"
RawMaterialType type;
Vec3f ambientFactor;
Vec4f diffuseFactor;
Vec3f specularFactor;
Vec3f emissiveFactor;
float shininess;
int textures[RAW_TEXTURE_USAGE_MAX];
};
struct RawSurface
{
std::string name; // The name of this surface
std::string nodeName; // The node that links to this surface.
std::string skeletonRootName; // The name of the root of the skeleton.
Bounds<float, 3> bounds;
std::vector<std::string> jointNames;
std::vector<Vec3f> jointGeometryMins;
std::vector<Vec3f> jointGeometryMaxs;
std::vector<Mat4f> inverseBindMatrices;
bool discrete;
bool skinRigid;
};
struct RawChannel
{
int nodeIndex;
std::vector<Vec3f> translations;
std::vector<Quatf> rotations;
std::vector<Vec3f> scales;
};
struct RawAnimation
{
std::string name;
std::vector<float> times;
std::vector<RawChannel> channels;
};
struct RawCamera
{
std::string name;
std::string nodeName;
enum
{
CAMERA_MODE_PERSPECTIVE,
CAMERA_MODE_ORTHOGRAPHIC
} mode;
struct
{
float aspectRatio;
float fovDegreesX;
float fovDegreesY;
float nearZ;
float farZ;
} perspective;
struct
{
float magX;
float magY;
float nearZ;
float farZ;
} orthographic;
};
struct RawNode
{
bool isJoint;
std::string name;
std::string parentName;
std::vector<std::string> childNames;
Vec3f translation;
Quatf rotation;
Vec3f scale;
};
class RawModel
{
public:
RawModel();
// Add geometry.
void AddVertexAttribute(const RawVertexAttribute attrib);
int AddVertex(const RawVertex &vertex);
int AddTriangle(const int v0, const int v1, const int v2, const int materialIndex, const int surfaceIndex);
int AddTexture(const char *name, const char *fileName, const RawTextureUsage usage);
int AddMaterial(const RawMaterial &material);
int AddMaterial(
const char *name, const char *shadingModel, RawMaterialType materialType,
const int textures[RAW_TEXTURE_USAGE_MAX], Vec3f ambientFactor,
Vec4f diffuseFactor, Vec3f specularFactor,
Vec3f emissiveFactor, float shinineness);
int AddSurface(const RawSurface &suface);
int AddSurface(const char *name, const char *nodeName);
int AddAnimation(const RawAnimation &animation);
int AddCameraPerspective(
const char *name, const char *nodeName, const float aspectRatio, const float fovDegreesX, const float fovDegreesY,
const float nearZ, const float farZ);
int
AddCameraOrthographic(const char *name, const char *nodeName, const float magX, const float magY, const float nearZ, const float farZ);
int AddNode(const RawNode &node);
int AddNode(const char *name, const char *parentName);
void SetRootNode(const char *name) { rootNodeName = name; }
const char *GetRootNode() const { return rootNodeName.c_str(); }
// Remove unused vertices, textures or materials after removing vertex attributes, textures, materials or surfaces.
void Condense();
void TransformTextures(const std::vector<std::function<Vec2f(Vec2f)>> &transforms);
// Get the attributes stored per vertex.
int GetVertexAttributes() const { return vertexAttributes; }
// Iterate over the vertices.
int GetVertexCount() const { return (int) vertices.size(); }
const RawVertex &GetVertex(const int index) const { return vertices[index]; }
// Iterate over the triangles.
int GetTriangleCount() const { return (int) triangles.size(); }
const RawTriangle &GetTriangle(const int index) const { return triangles[index]; }
// Iterate over the textures.
int GetTextureCount() const { return (int) textures.size(); }
const RawTexture &GetTexture(const int index) const { return textures[index]; }
// Iterate over the materials.
int GetMaterialCount() const { return (int) materials.size(); }
const RawMaterial &GetMaterial(const int index) const { return materials[index]; }
// Iterate over the surfaces.
int GetSurfaceCount() const { return (int) surfaces.size(); }
const RawSurface &GetSurface(const int index) const { return surfaces[index]; }
RawSurface &GetSurface(const int index) { return surfaces[index]; }
// Iterate over the animations.
int GetAnimationCount() const { return (int) animations.size(); }
const RawAnimation &GetAnimation(const int index) const { return animations[index]; }
// Iterate over the cameras.
int GetCameraCount() const { return (int) cameras.size(); }
const RawCamera &GetCamera(const int index) const { return cameras[index]; }
// Iterate over the nodes.
int GetNodeCount() const { return (int) nodes.size(); }
const RawNode &GetNode(const int index) const { return nodes[index]; }
RawNode &GetNode(const int index) { return nodes[index]; }
int GetNodeByName(const char *name) const;
// Create individual attribute arrays.
// Returns true if the vertices store the particular attribute.
template<typename _attrib_type_>
void GetAttributeArray(std::vector<_attrib_type_> &out, const _attrib_type_ RawVertex::* ptr) const;
// Create an array with a raw model for each material.
// Multiple surfaces with the same material will turn into a single model.
// However, surfaces that are marked as 'discrete' will turn into separate models.
void CreateMaterialModels(
std::vector<RawModel> &materialModels, const int maxModelVertices, const int keepAttribs, const bool forceDiscrete) const;
private:
std::string rootNodeName;
int vertexAttributes;
std::unordered_map<RawVertex, int, VertexHasher> vertexHash;
std::vector<RawVertex> vertices;
std::vector<RawTriangle> triangles;
std::vector<RawTexture> textures;
std::vector<RawMaterial> materials;
std::vector<RawSurface> surfaces;
std::vector<RawAnimation> animations;
std::vector<RawCamera> cameras;
std::vector<RawNode> nodes;
};
template<typename _attrib_type_>
void RawModel::GetAttributeArray(std::vector<_attrib_type_> &out, const _attrib_type_ RawVertex::* ptr) const
{
out.resize(vertices.size());
for (size_t i = 0; i < vertices.size(); i++) {
out[i] = vertices[i].*ptr;
}
}
#endif // !__RAWMODEL_H__

1275
src/fbx/Fbx2Raw.cpp Normal file

File diff suppressed because it is too large Load Diff

19
src/fbx/Fbx2Raw.hpp Normal file
View File

@ -0,0 +1,19 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include "raw/RawModel.hpp"
bool LoadFBXFile(
RawModel& raw,
const std::string fbxFileName,
const std::set<std::string>& textureExtensions,
const GltfOptions& options);
json TranscribeProperty(FbxProperty& prop);

View File

@ -0,0 +1,72 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "FbxBlendShapesAccess.hpp"
FbxBlendShapesAccess::TargetShape::TargetShape(const FbxShape* shape, FbxDouble fullWeight)
: shape(shape),
fullWeight(fullWeight),
count(shape->GetControlPointsCount()),
positions(shape->GetControlPoints()),
normals(FbxLayerElementAccess<FbxVector4>(
shape->GetElementNormal(),
shape->GetElementNormalCount())),
tangents(FbxLayerElementAccess<FbxVector4>(
shape->GetElementTangent(),
shape->GetElementTangentCount())) {}
FbxAnimCurve* FbxBlendShapesAccess::BlendChannel::ExtractAnimation(unsigned int animIx) const {
FbxAnimStack* stack = mesh->GetScene()->GetSrcObject<FbxAnimStack>(animIx);
FbxAnimLayer* layer = stack->GetMember<FbxAnimLayer>(0);
return mesh->GetShapeChannel(blendShapeIx, channelIx, layer, true);
}
FbxBlendShapesAccess::BlendChannel::BlendChannel(
FbxMesh* mesh,
const unsigned int blendShapeIx,
const unsigned int channelIx,
const FbxDouble deformPercent,
const std::vector<FbxBlendShapesAccess::TargetShape>& targetShapes,
std::string name)
: mesh(mesh),
blendShapeIx(blendShapeIx),
channelIx(channelIx),
deformPercent(deformPercent),
targetShapes(targetShapes),
name(name) {}
std::vector<FbxBlendShapesAccess::BlendChannel> FbxBlendShapesAccess::extractChannels(
FbxMesh* mesh) const {
std::vector<BlendChannel> channels;
for (int shapeIx = 0; shapeIx < mesh->GetDeformerCount(FbxDeformer::eBlendShape); shapeIx++) {
auto* fbxBlendShape =
static_cast<FbxBlendShape*>(mesh->GetDeformer(shapeIx, FbxDeformer::eBlendShape));
for (int channelIx = 0; channelIx < fbxBlendShape->GetBlendShapeChannelCount(); ++channelIx) {
FbxBlendShapeChannel* fbxChannel = fbxBlendShape->GetBlendShapeChannel(channelIx);
if (fbxChannel->GetTargetShapeCount() > 0) {
std::vector<TargetShape> targetShapes;
const double* fullWeights = fbxChannel->GetTargetShapeFullWeights();
std::string name = std::string(fbxChannel->GetName());
if (verboseOutput) {
fmt::printf("\rblendshape channel: %s\n", name);
}
for (int targetIx = 0; targetIx < fbxChannel->GetTargetShapeCount(); targetIx++) {
FbxShape* fbxShape = fbxChannel->GetTargetShape(targetIx);
targetShapes.emplace_back(fbxShape, fullWeights[targetIx]);
}
channels.emplace_back(
mesh, shapeIx, channelIx, fbxChannel->DeformPercent * 0.01, targetShapes, name);
}
}
}
return channels;
}

View File

@ -0,0 +1,104 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <algorithm>
#include <fstream>
#include <string>
#include <vector>
#include "FBX2glTF.h"
#include "FbxLayerElementAccess.hpp"
/**
* At the FBX level, each Mesh can have a set of FbxBlendShape deformers; organisational units that
* contain no data of their own. The actual deformation is determined by one or more
* FbxBlendShapeChannels, whose influences are all additively applied to the mesh. In a simpler
* world, each such channel would extend each base vertex with alternate position, and optionally
* normal and tangent.
*
* It's not quite so simple, though. We also have progressive morphing, where one logical morph
* actually consists of several concrete ones, each applied in sequence. For us, this means each
* channel contains a sequence of FbxShapes (aka target shape); these are the actual data-holding
* entities that provide the alternate vertex attributes. As such a channel is given more weight, it
* moves from one target shape to another.
*
* The total number of alternate sets of attributes, then, is the total number of target shapes
* across all the channels of all the blend shapes of the mesh.
*
* Each animation in the scene stack can yield one or zero FbxAnimCurves per channel (not target
* shape). We evaluate these curves to get the weight of the channel: this weight is further
* introspected on to figure out which target shapes we're currently interpolation between.
*/
class FbxBlendShapesAccess {
public:
/**
* A target shape is on a 1:1 basis with the eventual glTF morph target, and is the object which
* contains the actual morphed vertex data.
*/
struct TargetShape {
explicit TargetShape(const FbxShape* shape, FbxDouble fullWeight);
const FbxShape* shape;
const FbxDouble fullWeight;
const unsigned int count;
const FbxVector4* positions;
const FbxLayerElementAccess<FbxVector4> normals;
const FbxLayerElementAccess<FbxVector4> tangents;
};
/**
* A channel collects a sequence (often of length 1) of target shapes.
*/
struct BlendChannel {
BlendChannel(
FbxMesh* mesh,
const unsigned int blendShapeIx,
const unsigned int channelIx,
const FbxDouble deformPercent,
const std::vector<TargetShape>& targetShapes,
const std::string name);
FbxAnimCurve* ExtractAnimation(unsigned int animIx) const;
FbxMesh* const mesh;
const unsigned int blendShapeIx;
const unsigned int channelIx;
const std::vector<TargetShape> targetShapes;
const std::string name;
const FbxDouble deformPercent;
};
explicit FbxBlendShapesAccess(FbxMesh* mesh) : channels(extractChannels(mesh)) {}
size_t GetChannelCount() const {
return channels.size();
}
const BlendChannel& GetBlendChannel(size_t channelIx) const {
return channels.at(channelIx);
}
size_t GetTargetShapeCount(size_t channelIx) const {
return channels[channelIx].targetShapes.size();
}
const TargetShape& GetTargetShape(size_t channelIx, size_t targetShapeIx) const {
return channels.at(channelIx).targetShapes[targetShapeIx];
}
FbxAnimCurve* GetAnimation(size_t channelIx, size_t animIx) const {
return channels.at(channelIx).ExtractAnimation(to_uint32(animIx));
}
private:
std::vector<BlendChannel> extractChannels(FbxMesh* mesh) const;
const std::vector<BlendChannel> channels;
};

View File

@ -0,0 +1,94 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include "FBX2glTF.h"
template <typename _type_>
class FbxLayerElementAccess {
public:
FbxLayerElementAccess(const FbxLayerElementTemplate<_type_>* layer, int count);
bool LayerPresent() const {
return (mappingMode != FbxLayerElement::eNone);
}
_type_ GetElement(
const int polygonIndex,
const int polygonVertexIndex,
const int controlPointIndex,
const _type_ defaultValue) const;
_type_ GetElement(
const int polygonIndex,
const int polygonVertexIndex,
const int controlPointIndex,
const _type_ defaultValue,
const FbxMatrix& transform,
const bool normalize) const;
private:
FbxLayerElement::EMappingMode mappingMode;
const FbxLayerElementArrayTemplate<_type_>* elements;
const FbxLayerElementArrayTemplate<int>* indices;
};
template <typename _type_>
FbxLayerElementAccess<_type_>::FbxLayerElementAccess(
const FbxLayerElementTemplate<_type_>* layer,
int count)
: mappingMode(FbxLayerElement::eNone), elements(nullptr), indices(nullptr) {
if (count <= 0 || layer == nullptr) {
return;
}
const FbxLayerElement::EMappingMode newMappingMode = layer->GetMappingMode();
if (newMappingMode == FbxLayerElement::eByControlPoint ||
newMappingMode == FbxLayerElement::eByPolygonVertex ||
newMappingMode == FbxLayerElement::eByPolygon) {
mappingMode = newMappingMode;
elements = &layer->GetDirectArray();
indices = (layer->GetReferenceMode() == FbxLayerElement::eIndexToDirect ||
layer->GetReferenceMode() == FbxLayerElement::eIndex)
? &layer->GetIndexArray()
: nullptr;
}
}
template <typename _type_>
_type_ FbxLayerElementAccess<_type_>::GetElement(
const int polygonIndex,
const int polygonVertexIndex,
const int controlPointIndex,
const _type_ defaultValue) const {
if (mappingMode != FbxLayerElement::eNone) {
int index = (mappingMode == FbxLayerElement::eByControlPoint)
? controlPointIndex
: ((mappingMode == FbxLayerElement::eByPolygonVertex) ? polygonVertexIndex : polygonIndex);
index = (indices != nullptr) ? (*indices)[index] : index;
_type_ element = elements->GetAt(index);
return element;
}
return defaultValue;
}
template <typename _type_>
_type_ FbxLayerElementAccess<_type_>::GetElement(
const int polygonIndex,
const int polygonVertexIndex,
const int controlPointIndex,
const _type_ defaultValue,
const FbxMatrix& transform,
const bool normalize) const {
if (mappingMode != FbxLayerElement::eNone) {
_type_ element = transform.MultNormalize(
GetElement(polygonIndex, polygonVertexIndex, controlPointIndex, defaultValue));
if (normalize) {
element.Normalize();
}
return element;
}
return defaultValue;
}

View File

@ -0,0 +1,101 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "FbxSkinningAccess.hpp"
FbxSkinningAccess::FbxSkinningAccess(const FbxMesh* pMesh, FbxScene* pScene, FbxNode* pNode)
: rootIndex(-1) {
for (int deformerIndex = 0; deformerIndex < pMesh->GetDeformerCount(); deformerIndex++) {
FbxSkin* skin =
reinterpret_cast<FbxSkin*>(pMesh->GetDeformer(deformerIndex, FbxDeformer::eSkin));
if (skin != nullptr) {
const int clusterCount = skin->GetClusterCount();
if (clusterCount == 0) {
continue;
}
int controlPointCount = pMesh->GetControlPointsCount();
vertexJointIndices.resize(controlPointCount, Vec4i(0, 0, 0, 0));
vertexJointWeights.resize(controlPointCount, Vec4f(0.0f, 0.0f, 0.0f, 0.0f));
for (int clusterIndex = 0; clusterIndex < clusterCount; clusterIndex++) {
FbxCluster* cluster = skin->GetCluster(clusterIndex);
const int indexCount = cluster->GetControlPointIndicesCount();
const int* clusterIndices = cluster->GetControlPointIndices();
const double* clusterWeights = cluster->GetControlPointWeights();
assert(
cluster->GetLinkMode() == FbxCluster::eNormalize ||
cluster->GetLinkMode() == FbxCluster::eTotalOne);
// Transform link matrix.
FbxAMatrix transformLinkMatrix;
cluster->GetTransformLinkMatrix(transformLinkMatrix);
// The transformation of the mesh at binding time
FbxAMatrix transformMatrix;
cluster->GetTransformMatrix(transformMatrix);
// Inverse bind matrix.
FbxAMatrix globalBindposeInverseMatrix = transformLinkMatrix.Inverse() * transformMatrix;
inverseBindMatrices.emplace_back(globalBindposeInverseMatrix);
jointNodes.push_back(cluster->GetLink());
jointIds.push_back(cluster->GetLink()->GetUniqueID());
const FbxAMatrix globalNodeTransform = cluster->GetLink()->EvaluateGlobalTransform();
jointSkinningTransforms.push_back(
FbxMatrix(globalNodeTransform * globalBindposeInverseMatrix));
jointInverseGlobalTransforms.push_back(FbxMatrix(globalNodeTransform.Inverse()));
for (int i = 0; i < indexCount; i++) {
if (clusterIndices[i] < 0 || clusterIndices[i] >= controlPointCount) {
continue;
}
if (clusterWeights[i] <= vertexJointWeights[clusterIndices[i]][MAX_WEIGHTS - 1]) {
continue;
}
vertexJointIndices[clusterIndices[i]][MAX_WEIGHTS - 1] = clusterIndex;
vertexJointWeights[clusterIndices[i]][MAX_WEIGHTS - 1] = (float)clusterWeights[i];
for (int j = MAX_WEIGHTS - 1; j > 0; j--) {
if (vertexJointWeights[clusterIndices[i]][j - 1] >=
vertexJointWeights[clusterIndices[i]][j]) {
break;
}
std::swap(
vertexJointIndices[clusterIndices[i]][j - 1],
vertexJointIndices[clusterIndices[i]][j]);
std::swap(
vertexJointWeights[clusterIndices[i]][j - 1],
vertexJointWeights[clusterIndices[i]][j]);
}
}
}
for (int i = 0; i < controlPointCount; i++) {
const float weightSumRcp = 1.0 /
(vertexJointWeights[i][0] + vertexJointWeights[i][1] + vertexJointWeights[i][2] +
vertexJointWeights[i][3]);
vertexJointWeights[i] *= weightSumRcp;
}
}
}
rootIndex = -1;
for (size_t i = 0; i < jointNodes.size() && rootIndex == -1; i++) {
rootIndex = (int)i;
FbxNode* parent = jointNodes[i]->GetParent();
if (parent == nullptr) {
break;
}
for (size_t j = 0; j < jointNodes.size(); j++) {
if (jointNodes[j] == parent) {
rootIndex = -1;
break;
}
}
}
}

View File

@ -0,0 +1,79 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <algorithm>
#include <fstream>
#include <map>
#include <set>
#include <string>
#include <unordered_map>
#include <vector>
#include "FBX2glTF.h"
class FbxSkinningAccess {
public:
static const int MAX_WEIGHTS = 4;
FbxSkinningAccess(const FbxMesh* pMesh, FbxScene* pScene, FbxNode* pNode);
bool IsSkinned() const {
return (vertexJointWeights.size() > 0);
}
int GetNodeCount() const {
return (int)jointNodes.size();
}
FbxNode* GetJointNode(const int jointIndex) const {
return jointNodes[jointIndex];
}
const uint64_t GetJointId(const int jointIndex) const {
return jointIds[jointIndex];
}
const FbxMatrix& GetJointSkinningTransform(const int jointIndex) const {
return jointSkinningTransforms[jointIndex];
}
const FbxMatrix& GetJointInverseGlobalTransforms(const int jointIndex) const {
return jointInverseGlobalTransforms[jointIndex];
}
const uint64_t GetRootNode() const {
assert(rootIndex != -1);
return jointIds[rootIndex];
}
const FbxAMatrix& GetInverseBindMatrix(const int jointIndex) const {
return inverseBindMatrices[jointIndex];
}
const Vec4i GetVertexIndices(const int controlPointIndex) const {
return (!vertexJointIndices.empty()) ? vertexJointIndices[controlPointIndex]
: Vec4i(0, 0, 0, 0);
}
const Vec4f GetVertexWeights(const int controlPointIndex) const {
return (!vertexJointWeights.empty()) ? vertexJointWeights[controlPointIndex]
: Vec4f(0, 0, 0, 0);
}
private:
int rootIndex;
std::vector<uint64_t> jointIds;
std::vector<FbxNode*> jointNodes;
std::vector<FbxMatrix> jointSkinningTransforms;
std::vector<FbxMatrix> jointInverseGlobalTransforms;
std::vector<FbxAMatrix> inverseBindMatrices;
std::vector<Vec4i> vertexJointIndices;
std::vector<Vec4f> vertexJointWeights;
};

View File

@ -0,0 +1,171 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "RoughnessMetallicMaterials.hpp"
std::unique_ptr<FbxRoughMetMaterialInfo> Fbx3dsMaxPhysicalMaterialResolver::resolve() const {
const FbxProperty topProp = fbxMaterial->FindProperty("3dsMax", false);
if (topProp.GetPropertyDataType() != FbxCompoundDT) {
return nullptr;
}
const FbxProperty props = topProp.Find("Parameters", false);
if (!props.IsValid()) {
return nullptr;
}
FbxString shadingModel = fbxMaterial->ShadingModel.Get();
if (!shadingModel.IsEmpty() && shadingModel != "unknown") {
::fmt::printf(
"Warning: Material %s has surprising shading model: %s\n",
fbxMaterial->GetName(),
shadingModel);
}
auto getTex = [&](std::string propName) -> const FbxFileTexture* {
const FbxFileTexture* ptr = nullptr;
const FbxProperty texProp = props.Find((propName + "_map").c_str(), false);
if (texProp.IsValid()) {
const FbxProperty useProp = props.Find((propName + "_map_on").c_str(), false);
if (useProp.IsValid() && !useProp.Get<FbxBool>()) {
// skip this texture if the _on property exists *and* is explicitly false
return nullptr;
}
ptr = texProp.GetSrcObject<FbxFileTexture>();
if (ptr != nullptr && textureLocations.find(ptr) == textureLocations.end()) {
ptr = nullptr;
}
}
return ptr;
};
FbxDouble baseWeight = getValue(props, "base_weight", 1.0);
const auto* baseWeightMap = getTex("base_weight");
FbxDouble4 baseCol = getValue(props, "base_color", FbxDouble4(0.5, 0.5, 0.5, 1.0));
const auto* baseTex = getTex("base_color");
double emissiveWeight = getValue(props, "emission", 0.0);
const auto* emissiveWeightMap = getTex("emission");
FbxDouble4 emissiveColor = getValue(props, "emit_color", FbxDouble4(1, 1, 1, 1));
const auto* emissiveColorMap = getTex("emit_color");
double roughness = getValue(props, "roughness", 0.0);
const auto* roughnessMap = getTex("roughness");
double metalness = getValue(props, "metalness", 0.0);
const auto* metalnessMap = getTex("metalness");
// TODO: we need this to affect roughness map, too.
bool invertRoughness = getValue(props, "inv_roughness", false);
if (invertRoughness) {
roughness = 1.0f - roughness;
}
std::string unsupported;
const auto addUnsupported = [&](const std::string bit) {
if (!unsupported.empty()) {
unsupported += ", ";
}
unsupported += bit;
};
// TODO: turn this into a normal map through simple numerial differentiation
const auto* bumpMap = getTex("bump");
if (bumpMap != nullptr) {
addUnsupported("bump map");
}
// TODO: bake transparency > 0.0f into the alpha of baseColor?
double transparency = getValue(props, "transparency", 0.0);
const auto* transparencyMap = getTex("transparency");
if (transparency != 0.0 || transparencyMap != nullptr) {
addUnsupported("transparency");
}
// TODO: if/when we bake transparency, we'll need this
// double transparencyDepth = getValue(props, "trans_depth", 0.0);
// if (transparencyDepth != 0.0) {
// addUnsupported("transparency depth");
// }
// double transparencyColor = getValue(props, "trans_color", 0.0);
// const auto* transparencyColorMap = getTex("trans_color");
// if (transparencyColor != 0.0 || transparencyColorMap != nullptr) {
// addUnsupported("transparency color");
// }
// double thinWalledTransparency = getValue(props, "thin_walled", false);
// if (thinWalledTransparency) {
// addUnsupported("thin-walled transparency");
// }
const auto* displacementMap = getTex("displacement");
if (displacementMap != nullptr) {
addUnsupported("displacement");
}
double reflectivityWeight = getValue(props, "reflectivity", 1.0);
const auto* reflectivityWeightMap = getTex("reflectivity");
FbxDouble4 reflectivityColor = getValue(props, "refl_color", FbxDouble4(1, 1, 1, 1));
const auto* reflectivityColorMap = getTex("refl_color");
if (reflectivityWeight != 1.0 || reflectivityWeightMap != nullptr ||
reflectivityColor != FbxDouble4(1, 1, 1, 1) || reflectivityColorMap != nullptr) {
addUnsupported("reflectivity");
}
double scattering = getValue(props, "scattering", 0.0);
const auto* scatteringMap = getTex("scattering");
if (scattering != 0.0 || scatteringMap != nullptr) {
addUnsupported("sub-surface scattering");
}
double coating = getValue(props, "coating", 0.0);
if (coating != 0.0) {
addUnsupported("coating");
}
double diffuseRoughness = getValue(props, "diff_roughness", 0.0);
if (diffuseRoughness != 0.0) {
addUnsupported("diffuse roughness");
}
bool isBrdfMode = getValue(props, "brdf_mode", false);
if (isBrdfMode) {
addUnsupported("advanced reflectance custom curve");
}
double anisotropy = getValue(props, "anisotropy", 1.0);
if (anisotropy != 1.0) {
addUnsupported("anisotropy");
}
if (verboseOutput && !unsupported.empty()) {
fmt::printf(
"Warning: 3dsMax Physical Material %s uses features glTF cannot express:\n %s\n",
fbxMaterial->GetName(),
unsupported);
}
std::unique_ptr<FbxRoughMetMaterialInfo> res(new FbxRoughMetMaterialInfo(
fbxMaterial->GetUniqueID(),
fbxMaterial->GetName(),
FbxRoughMetMaterialInfo::FBX_SHADER_METROUGH,
baseCol,
metalness,
roughness));
res->texBaseColor = baseTex;
res->baseWeight = baseWeight;
res->texBaseWeight = baseWeightMap;
res->texMetallic = metalnessMap;
res->texRoughness = roughnessMap;
res->invertRoughnessMap = invertRoughness;
res->emissive = emissiveColor;
res->emissiveIntensity = emissiveWeight;
res->texEmissive = emissiveColorMap;
res->texEmissiveWeight = emissiveWeightMap;
return res;
}

View File

@ -0,0 +1,122 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "fbx/Fbx2Raw.hpp"
#include "FbxMaterials.hpp"
#include "RoughnessMetallicMaterials.hpp"
#include "TraditionalMaterials.hpp"
static int warnMtrCount = 0;
FbxMaterialsAccess::FbxMaterialsAccess(
const FbxMesh* pMesh,
const std::map<const FbxTexture*, FbxString>& textureLocations)
: mappingMode(FbxGeometryElement::eNone), mesh(nullptr), indices(nullptr) {
if (pMesh->GetElementMaterialCount() <= 0) {
return;
}
const FbxGeometryElement::EMappingMode materialMappingMode =
pMesh->GetElementMaterial()->GetMappingMode();
if (materialMappingMode != FbxGeometryElement::eByPolygon &&
materialMappingMode != FbxGeometryElement::eAllSame) {
return;
}
const FbxGeometryElement::EReferenceMode materialReferenceMode =
pMesh->GetElementMaterial()->GetReferenceMode();
if (materialReferenceMode != FbxGeometryElement::eIndexToDirect) {
return;
}
mappingMode = materialMappingMode;
mesh = pMesh;
indices = &pMesh->GetElementMaterial()->GetIndexArray();
for (int ii = 0; ii < indices->GetCount(); ii++) {
int materialNum = indices->GetAt(ii);
if (materialNum < 0) {
continue;
}
auto* surfaceMaterial =
mesh->GetNode()->GetSrcObject<FbxSurfaceMaterial>(materialNum);
if (!surfaceMaterial) {
if (++warnMtrCount == 1) {
fmt::printf("Warning: Reference to missing surface material.\n");
fmt::printf(" (Further warnings of this type squelched.)\n");
}
}
if (materialNum >= summaries.size()) {
summaries.resize(materialNum + 1);
}
auto summary = summaries[materialNum];
if (summary == nullptr) {
summary = summaries[materialNum] = GetMaterialInfo(surfaceMaterial, textureLocations);
}
if (materialNum >= userProperties.size()) {
userProperties.resize(materialNum + 1);
}
if (surfaceMaterial && userProperties[materialNum].empty()) {
FbxProperty objectProperty = surfaceMaterial->GetFirstProperty();
while (objectProperty.IsValid()) {
if (objectProperty.GetFlag(FbxPropertyFlags::eUserDefined)) {
userProperties[materialNum].push_back(TranscribeProperty(objectProperty).dump());
}
objectProperty = surfaceMaterial->GetNextProperty(objectProperty);
}
}
}
}
const std::shared_ptr<FbxMaterialInfo> FbxMaterialsAccess::GetMaterial(
const int polygonIndex) const {
if (mappingMode != FbxGeometryElement::eNone) {
const int materialNum =
indices->GetAt((mappingMode == FbxGeometryElement::eByPolygon) ? polygonIndex : 0);
if (materialNum < 0) {
return nullptr;
}
return summaries.at((unsigned long)materialNum);
}
return nullptr;
}
const std::vector<std::string> FbxMaterialsAccess::GetUserProperties(const int polygonIndex) const {
if (mappingMode != FbxGeometryElement::eNone) {
const int materialNum =
indices->GetAt((mappingMode == FbxGeometryElement::eByPolygon) ? polygonIndex : 0);
if (materialNum < 0) {
return std::vector<std::string>();
}
return userProperties.at((unsigned long)materialNum);
}
return std::vector<std::string>();
}
std::unique_ptr<FbxMaterialInfo> FbxMaterialsAccess::GetMaterialInfo(
FbxSurfaceMaterial* material,
const std::map<const FbxTexture*, FbxString>& textureLocations) {
if (!material) {
return nullptr;
}
std::unique_ptr<FbxMaterialInfo> res =
FbxStingrayPBSMaterialResolver(material, textureLocations).resolve();
if (res == nullptr) {
res = Fbx3dsMaxPhysicalMaterialResolver(material, textureLocations).resolve();
if (res == nullptr) {
res = FbxTraditionalMaterialResolver(material, textureLocations).resolve();
}
}
return res;
}

View File

@ -0,0 +1,60 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <map>
#include <vector>
#include "FBX2glTF.h"
class FbxMaterialInfo {
public:
FbxMaterialInfo(const FbxUInt64 id, const FbxString& name, const FbxString& shadingModel)
: id(id), name(name), shadingModel(shadingModel) {}
const FbxUInt64 id;
const FbxString name;
const FbxString shadingModel;
};
template <class T>
class FbxMaterialResolver {
public:
FbxMaterialResolver(
FbxSurfaceMaterial* fbxMaterial,
const std::map<const FbxTexture*, FbxString>& textureLocations)
: fbxMaterial(fbxMaterial), textureLocations(textureLocations) {}
virtual std::unique_ptr<T> resolve() const = 0;
protected:
const FbxSurfaceMaterial* fbxMaterial;
const std::map<const FbxTexture*, FbxString> textureLocations;
};
class FbxMaterialsAccess {
public:
FbxMaterialsAccess(
const FbxMesh* pMesh,
const std::map<const FbxTexture*, FbxString>& textureLocations);
const std::shared_ptr<FbxMaterialInfo> GetMaterial(const int polygonIndex) const;
const std::vector<std::string> GetUserProperties(const int polygonIndex) const;
std::unique_ptr<FbxMaterialInfo> GetMaterialInfo(
FbxSurfaceMaterial* material,
const std::map<const FbxTexture*, FbxString>& textureLocations);
private:
FbxGeometryElement::EMappingMode mappingMode;
std::vector<std::shared_ptr<FbxMaterialInfo>> summaries{};
std::vector<std::vector<std::string>> userProperties;
const FbxMesh* mesh;
const FbxLayerElementArrayTemplate<int>* indices;
};

View File

@ -0,0 +1,78 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <map>
#include "FbxMaterials.hpp"
struct FbxRoughMetMaterialInfo : FbxMaterialInfo {
static constexpr const char* FBX_SHADER_METROUGH = "MetallicRoughness";
static std::unique_ptr<FbxRoughMetMaterialInfo> From(
FbxSurfaceMaterial* fbxMaterial,
const std::map<const FbxTexture*, FbxString>& textureLocations);
FbxRoughMetMaterialInfo(
const FbxUInt64 id,
const FbxString& name,
const FbxString& shadingModel,
FbxDouble4 baseColor,
FbxDouble metallic,
FbxDouble roughness)
: FbxMaterialInfo(id, name, shadingModel),
baseColor(baseColor),
metallic(metallic),
roughness(roughness) {}
const FbxVector4 baseColor;
const FbxDouble metallic;
const FbxDouble roughness;
FbxBool invertRoughnessMap = false;
FbxDouble baseWeight = 1;
FbxVector4 emissive = FbxVector4(0, 0, 0, 1);
FbxDouble emissiveIntensity = 1;
const FbxFileTexture* texNormal = nullptr;
const FbxFileTexture* texBaseColor = nullptr;
const FbxFileTexture* texBaseWeight = nullptr;
const FbxFileTexture* texMetallic = nullptr;
const FbxFileTexture* texRoughness = nullptr;
const FbxFileTexture* texEmissive = nullptr;
const FbxFileTexture* texEmissiveWeight = nullptr;
const FbxFileTexture* texAmbientOcclusion = nullptr;
};
class FbxStingrayPBSMaterialResolver : FbxMaterialResolver<FbxRoughMetMaterialInfo> {
public:
FbxStingrayPBSMaterialResolver(
FbxSurfaceMaterial* fbxMaterial,
const std::map<const FbxTexture*, FbxString>& textureLocations)
: FbxMaterialResolver(fbxMaterial, textureLocations) {}
virtual std::unique_ptr<FbxRoughMetMaterialInfo> resolve() const;
};
class Fbx3dsMaxPhysicalMaterialResolver : FbxMaterialResolver<FbxRoughMetMaterialInfo> {
public:
Fbx3dsMaxPhysicalMaterialResolver(
FbxSurfaceMaterial* fbxMaterial,
const std::map<const FbxTexture*, FbxString>& textureLocations)
: FbxMaterialResolver(fbxMaterial, textureLocations) {}
virtual std::unique_ptr<FbxRoughMetMaterialInfo> resolve() const;
private:
template <typename T>
T getValue(const FbxProperty& props, std::string propName, const T& def) const {
const FbxProperty prop = props.FindHierarchical(propName.c_str());
return prop.IsValid() ? prop.Get<T>() : def;
}
};

View File

@ -0,0 +1,67 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "RoughnessMetallicMaterials.hpp"
std::unique_ptr<FbxRoughMetMaterialInfo> FbxStingrayPBSMaterialResolver::resolve() const {
const FbxProperty mayaProp = fbxMaterial->FindProperty("Maya");
if (mayaProp.GetPropertyDataType() != FbxCompoundDT) {
return nullptr;
}
if (!fbxMaterial->ShadingModel.Get().IsEmpty()) {
::fmt::printf(
"Warning: Material %s has surprising shading model: %s\n",
fbxMaterial->GetName(),
fbxMaterial->ShadingModel.Get());
}
auto getTex = [&](std::string propName) {
const FbxFileTexture* ptr = nullptr;
const FbxProperty useProp = mayaProp.FindHierarchical(("use_" + propName + "_map").c_str());
if (useProp.IsValid() && useProp.Get<bool>()) {
const FbxProperty texProp = mayaProp.FindHierarchical(("TEX_" + propName + "_map").c_str());
if (texProp.IsValid()) {
ptr = texProp.GetSrcObject<FbxFileTexture>();
if (ptr != nullptr && textureLocations.find(ptr) == textureLocations.end()) {
ptr = nullptr;
}
}
}
return ptr;
};
auto getVec = [&](std::string propName) -> FbxDouble3 {
const FbxProperty vecProp = mayaProp.FindHierarchical(propName.c_str());
return vecProp.IsValid() ? vecProp.Get<FbxDouble3>() : FbxDouble3(1, 1, 1);
};
auto getVal = [&](std::string propName) -> FbxDouble {
const FbxProperty vecProp = mayaProp.FindHierarchical(propName.c_str());
return vecProp.IsValid() ? vecProp.Get<FbxDouble>() : 0;
};
FbxDouble3 baseColor = getVec("base_color");
std::unique_ptr<FbxRoughMetMaterialInfo> res(new FbxRoughMetMaterialInfo(
fbxMaterial->GetUniqueID(),
fbxMaterial->GetName(),
FbxRoughMetMaterialInfo::FBX_SHADER_METROUGH,
FbxDouble4(baseColor[0], baseColor[1], baseColor[2], 1),
getVal("metallic"),
getVal("roughness")));
res->texNormal = getTex("normal");
res->texBaseColor = getTex("color");
res->texAmbientOcclusion = getTex("ao");
res->texEmissive = getTex("emissive");
res->emissive = getVec("emissive");
res->emissiveIntensity = getVal("emissive_intensity");
res->texMetallic = getTex("metallic");
res->texRoughness = getTex("roughness");
return res;
};

View File

@ -0,0 +1,134 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "TraditionalMaterials.hpp"
std::unique_ptr<FbxTraditionalMaterialInfo> FbxTraditionalMaterialResolver::resolve() const {
auto getSurfaceScalar = [&](const char* propName) -> std::tuple<FbxDouble, FbxFileTexture*> {
const FbxProperty prop = fbxMaterial->FindProperty(propName);
FbxDouble val(0);
FbxFileTexture* tex = prop.GetSrcObject<FbxFileTexture>();
if (tex != nullptr && textureLocations.find(tex) == textureLocations.end()) {
tex = nullptr;
}
if (tex == nullptr && prop.IsValid()) {
val = prop.Get<FbxDouble>();
}
return std::make_tuple(val, tex);
};
auto getSurfaceVector = [&](const char* propName) -> std::tuple<FbxDouble3, FbxFileTexture*> {
const FbxProperty prop = fbxMaterial->FindProperty(propName);
FbxDouble3 val(1, 1, 1);
FbxFileTexture* tex = prop.GetSrcObject<FbxFileTexture>();
if (tex != nullptr && textureLocations.find(tex) == textureLocations.end()) {
tex = nullptr;
}
if (tex == nullptr && prop.IsValid()) {
val = prop.Get<FbxDouble3>();
}
return std::make_tuple(val, tex);
};
auto getSurfaceValues =
[&](const char* colName,
const char* facName) -> std::tuple<FbxVector4, FbxFileTexture*, FbxFileTexture*> {
const FbxProperty colProp = fbxMaterial->FindProperty(colName);
const FbxProperty facProp = fbxMaterial->FindProperty(facName);
FbxDouble3 colorVal(1, 1, 1);
FbxDouble factorVal(1);
FbxFileTexture* colTex = colProp.GetSrcObject<FbxFileTexture>();
if (colTex != nullptr && textureLocations.find(colTex) == textureLocations.end()) {
colTex = nullptr;
}
if (colTex == nullptr && colProp.IsValid()) {
colorVal = colProp.Get<FbxDouble3>();
}
FbxFileTexture* facTex = facProp.GetSrcObject<FbxFileTexture>();
if (facTex != nullptr && textureLocations.find(facTex) == textureLocations.end()) {
facTex = nullptr;
}
if (facTex == nullptr && facProp.IsValid()) {
factorVal = facProp.Get<FbxDouble>();
}
auto val = FbxVector4(
colorVal[0] * factorVal, colorVal[1] * factorVal, colorVal[2] * factorVal, factorVal);
return std::make_tuple(val, colTex, facTex);
};
std::string name = fbxMaterial->GetName();
std::unique_ptr<FbxTraditionalMaterialInfo> res(new FbxTraditionalMaterialInfo(
fbxMaterial->GetUniqueID(), name.c_str(), fbxMaterial->ShadingModel.Get()));
// four properties are on the same structure and follow the same rules
auto handleBasicProperty = [&](const char* colName,
const char* facName) -> std::tuple<FbxVector4, FbxFileTexture*> {
FbxFileTexture *colTex, *facTex;
FbxVector4 vec;
std::tie(vec, colTex, facTex) = getSurfaceValues(colName, facName);
if (colTex) {
if (facTex) {
fmt::printf(
"Warning: Mat [%s]: Can't handle both %s and %s textures; discarding %s.\n",
name,
colName,
facName,
facName);
}
return std::make_tuple(vec, colTex);
}
return std::make_tuple(vec, facTex);
};
std::tie(res->colAmbient, res->texAmbient) =
handleBasicProperty(FbxSurfaceMaterial::sAmbient, FbxSurfaceMaterial::sAmbientFactor);
std::tie(res->colSpecular, res->texSpecular) =
handleBasicProperty(FbxSurfaceMaterial::sSpecular, FbxSurfaceMaterial::sSpecularFactor);
std::tie(res->colDiffuse, res->texDiffuse) =
handleBasicProperty(FbxSurfaceMaterial::sDiffuse, FbxSurfaceMaterial::sDiffuseFactor);
std::tie(res->colEmissive, res->texEmissive) =
handleBasicProperty(FbxSurfaceMaterial::sEmissive, FbxSurfaceMaterial::sEmissiveFactor);
// the normal map can only ever be a map, ignore everything else
tie(std::ignore, res->texNormal) = getSurfaceVector(FbxSurfaceMaterial::sNormalMap);
// shininess can be a map or a factor; afaict the map is always 'ShininessExponent' and the
// value is always found in 'Shininess' but only sometimes in 'ShininessExponent'.
tie(std::ignore, res->texShininess) = getSurfaceScalar("ShininessExponent");
tie(res->shininess, std::ignore) = getSurfaceScalar("Shininess");
// for transparency we just want a constant vector value;
FbxVector4 transparency;
// extract any existing textures only so we can warn that we're throwing them away
FbxFileTexture *colTex, *facTex;
std::tie(transparency, colTex, facTex) = getSurfaceValues(
FbxSurfaceMaterial::sTransparentColor, FbxSurfaceMaterial::sTransparencyFactor);
if (colTex) {
fmt::printf(
"Warning: Mat [%s]: Can't handle texture for %s; discarding.\n",
name,
FbxSurfaceMaterial::sTransparentColor);
}
if (facTex) {
fmt::printf(
"Warning: Mat [%s]: Can't handle texture for %s; discarding.\n",
name,
FbxSurfaceMaterial::sTransparencyFactor);
}
// FBX color is RGB, so we calculate the A channel as the average of the FBX transparency color
// vector
res->colDiffuse[3] = 1.0 - (transparency[0] + transparency[1] + transparency[2]) / 3.0;
return res;
}

View File

@ -0,0 +1,43 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "FbxMaterials.hpp"
struct FbxTraditionalMaterialInfo : FbxMaterialInfo {
static constexpr const char* FBX_SHADER_LAMBERT = "Lambert";
static constexpr const char* FBX_SHADER_BLINN = "Blinn";
static constexpr const char* FBX_SHADER_PHONG = "Phong";
FbxTraditionalMaterialInfo(
const FbxUInt64 id,
const FbxString& name,
const FbxString& shadingModel)
: FbxMaterialInfo(id, name, shadingModel) {}
FbxFileTexture* texAmbient{};
FbxVector4 colAmbient{};
FbxFileTexture* texSpecular{};
FbxVector4 colSpecular{};
FbxFileTexture* texDiffuse{};
FbxVector4 colDiffuse{};
FbxFileTexture* texEmissive{};
FbxVector4 colEmissive{};
FbxFileTexture* texNormal{};
FbxFileTexture* texShininess{};
FbxDouble shininess{};
};
class FbxTraditionalMaterialResolver : FbxMaterialResolver<FbxTraditionalMaterialInfo> {
public:
FbxTraditionalMaterialResolver(
FbxSurfaceMaterial* fbxMaterial,
const std::map<const FbxTexture*, FbxString>& textureLocations)
: FbxMaterialResolver(fbxMaterial, textureLocations) {}
virtual std::unique_ptr<FbxTraditionalMaterialInfo> resolve() const;
};

View File

@ -1,49 +0,0 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#include "AccessorData.h"
#include "BufferViewData.h"
AccessorData::AccessorData(const BufferViewData &bufferView, GLType type)
: Holdable(),
bufferView(bufferView.ix),
type(std::move(type)),
byteOffset(0),
count(0)
{
}
AccessorData::AccessorData(GLType type)
: Holdable(),
bufferView(-1),
type(std::move(type)),
byteOffset(0),
count(0)
{
}
json AccessorData::serialize() const
{
json result {
{ "componentType", type.componentType.glType },
{ "type", type.dataType },
{ "count", count }
};
if (bufferView >= 0) {
result["bufferView"] = bufferView;
result["byteOffset"] = byteOffset;
}
if (!min.empty()) {
result["min"] = min;
}
if (!max.empty()) {
result["max"] = max;
}
return result;
}

View File

@ -1,48 +0,0 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#ifndef FBX2GLTF_ACCESSORDATA_H
#define FBX2GLTF_ACCESSORDATA_H
#include "Raw2Gltf.h"
struct AccessorData : Holdable
{
AccessorData(const BufferViewData &bufferView, GLType type);
explicit AccessorData(GLType type);
json serialize() const override;
template<class T>
void appendAsBinaryArray(const std::vector<T> &in, std::vector<uint8_t> &out)
{
const unsigned int stride = type.byteStride();
const size_t offset = out.size();
const size_t count = in.size();
this->count = (unsigned int) count;
out.resize(offset + count * stride);
for (int ii = 0; ii < count; ii ++) {
type.write(&out[offset + ii * stride], in[ii]);
}
}
unsigned int byteLength() const { return type.byteStride() * count; }
const int bufferView;
const GLType type;
unsigned int byteOffset;
unsigned int count;
std::vector<float> min;
std::vector<float> max;
};
#endif //FBX2GLTF_ACCESSORDATA_H

View File

@ -1,70 +0,0 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#include "AnimationData.h"
#include <utility>
#include "AccessorData.h"
#include "NodeData.h"
AnimationData::AnimationData(std::string name, const AccessorData &timeAccessor)
: Holdable(),
name(std::move(name)),
timeAccessor(timeAccessor.ix) {}
// assumption: 1-to-1 relationship between channels and samplers; this is a simplification on what
// glTF can express, but it means we can rely on samplerIx == channelIx throughout an animation
void AnimationData::AddNodeChannel(const NodeData &node, const AccessorData &accessor, std::string path)
{
assert(channels.size() == samplers.size());
uint32_t ix = channels.size();
channels.emplace_back(channel_t(ix, node, std::move(path)));
samplers.emplace_back(sampler_t(timeAccessor, accessor.ix));
}
json AnimationData::serialize() const
{
return {
{ "name", name },
{ "channels", channels },
{ "samplers", samplers }
};
}
AnimationData::channel_t::channel_t(uint32_t ix, const NodeData &node, std::string path)
: ix(ix),
node(node.ix),
path(std::move(path))
{
}
AnimationData::sampler_t::sampler_t(uint32_t time, uint32_t output)
: time(time),
output(output)
{
}
void to_json(json &j, const AnimationData::channel_t &data) {
j = json {
{ "sampler", data.ix },
{ "target", {
{ "node", data.node },
{ "path", data.path }},
}
};
}
void to_json(json &j, const AnimationData::sampler_t &data) {
j = json {
{ "input", data.time },
{ "interpolation", "LINEAR" },
{ "output", data.output },
};
}

View File

@ -1,51 +0,0 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#ifndef FBX2GLTF_ANIMATIONDATA_H
#define FBX2GLTF_ANIMATIONDATA_H
#include "Raw2Gltf.h"
struct AnimationData : Holdable
{
AnimationData(std::string name, const AccessorData &timeAccessor);
// assumption: 1-to-1 relationship between channels and samplers; this is a simplification on what
// glTF can express, but it means we can rely on samplerIx == channelIx throughout an animation
void AddNodeChannel(const NodeData &node, const AccessorData &accessor, std::string path);
json serialize() const override;
struct channel_t
{
channel_t(uint32_t _ix, const NodeData &node, std::string path);
const uint32_t ix;
const uint32_t node;
const std::string path;
};
struct sampler_t
{
sampler_t(uint32_t time, uint32_t output);
const uint32_t time;
const uint32_t output;
};
const std::string name;
const uint32_t timeAccessor;
std::vector<channel_t> channels;
std::vector<sampler_t> samplers;
};
void to_json(json &j, const AnimationData::channel_t &data);
void to_json(json &j, const AnimationData::sampler_t &data);
#endif //FBX2GLTF_ANIMATIONDATA_H

View File

@ -1,43 +0,0 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#include <cppcodec/base64_default_rfc4648.hpp>
#include "BufferData.h"
BufferData::BufferData(const std::shared_ptr<const std::vector<uint8_t> > &binData)
: Holdable(),
isGlb(true),
binData(binData)
{
}
BufferData::BufferData(std::string uri, const std::shared_ptr<const std::vector<uint8_t> > &binData, bool isEmbedded)
: Holdable(),
isGlb(false),
uri(isEmbedded ? "" : std::move(uri)),
binData(binData)
{
}
json BufferData::serialize() const
{
json result{
{"byteLength", binData->size()}
};
if (!isGlb) {
if (!uri.empty()) {
result["uri"] = uri;
} else {
std::string encoded = base64::encode(*binData);
result["uri"] = "data:application/octet-stream;base64," + encoded;
}
}
return result;
}

View File

@ -1,29 +0,0 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#ifndef FBX2GLTF_BUFFERDATA_H
#define FBX2GLTF_BUFFERDATA_H
#include "Raw2Gltf.h"
struct BufferData : Holdable
{
explicit BufferData(const std::shared_ptr<const std::vector<uint8_t> > &binData);
BufferData(std::string uri, const std::shared_ptr<const std::vector<uint8_t> > &binData, bool isEmbedded = false);
json serialize() const override;
const bool isGlb;
const std::string uri;
const std::shared_ptr<const std::vector<uint8_t> > binData; // TODO this is just weird
};
#endif //FBX2GLTF_BUFFERDATA_H

View File

@ -1,32 +0,0 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#include "BufferViewData.h"
#include "BufferData.h"
BufferViewData::BufferViewData(const BufferData &_buffer, const size_t _byteOffset, const GL_ArrayType _target)
: Holdable(),
buffer(_buffer.ix),
byteOffset((unsigned int) _byteOffset),
target(_target)
{
}
json BufferViewData::serialize() const
{
json result {
{ "buffer", buffer },
{ "byteLength", byteLength },
{ "byteOffset", byteOffset }
};
if (target != GL_ARRAY_NONE) {
result["target"] = target;
}
return result;
}

View File

@ -1,35 +0,0 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#ifndef FBX2GLTF_BUFFERVIEW_H
#define FBX2GLTF_BUFFERVIEW_H
#include "Raw2Gltf.h"
struct BufferViewData : Holdable
{
enum GL_ArrayType
{
GL_ARRAY_NONE = 0, // no GL buffer is being set
GL_ARRAY_BUFFER = 34962,
GL_ELEMENT_ARRAY_BUFFER = 34963
};
BufferViewData(const BufferData &_buffer, const size_t _byteOffset, const GL_ArrayType _target);
json serialize() const override;
const unsigned int buffer;
const unsigned int byteOffset;
const GL_ArrayType target;
unsigned int byteLength = 0;
};
#endif //FBX2GLTF_BUFFERVIEW_H

View File

@ -1,42 +0,0 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#include "CameraData.h"
CameraData::CameraData()
: Holdable(),
aspectRatio(0.0f),
yfov(0.0f),
xmag(0.0f),
ymag(0.0f),
znear(0.0f),
zfar(0.0f)
{
}
json CameraData::serialize() const
{
json result {
{ "name", name },
{ "type", type },
};
json subResult {
{ "znear", znear },
{ "zfar", zfar }
};
if (type == "perspective") {
subResult["aspectRatio"] = aspectRatio;
subResult["yfov"] = yfov;
} else {
subResult["xmag"] = xmag;
subResult["ymag"] = ymag;
}
result[type] = subResult;
return result;
}

View File

@ -1,31 +0,0 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#ifndef FBX2GLTF_CAMERADATA_H
#define FBX2GLTF_CAMERADATA_H
#include "Raw2Gltf.h"
// TODO: this class needs some work
struct CameraData : Holdable
{
CameraData();
json serialize() const override;
std::string name;
std::string type;
float aspectRatio;
float yfov;
float xmag;
float ymag;
float znear;
float zfar;
};
#endif //FBX2GLTF_CAMERADATA_H

View File

@ -1,44 +0,0 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#include "ImageData.h"
#include <utility>
#include "BufferViewData.h"
ImageData::ImageData(std::string name, std::string uri)
: Holdable(),
name(std::move(name)),
uri(std::move(uri)),
bufferView(-1)
{
}
ImageData::ImageData(std::string name, const BufferViewData &bufferView, std::string mimeType)
: Holdable(),
name(std::move(name)),
bufferView(bufferView.ix),
mimeType(std::move(mimeType))
{
}
json ImageData::serialize() const
{
if (mimeType.empty()) {
return {
{ "uri", uri }
};
}
return {
{ "name", name },
{ "bufferView", bufferView },
{ "mimeType", mimeType }
};
}

View File

@ -1,28 +0,0 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#ifndef FBX2GLTF_IMAGEDATA_H
#define FBX2GLTF_IMAGEDATA_H
#include "Raw2Gltf.h"
struct ImageData : Holdable
{
ImageData(std::string name, std::string uri);
ImageData(std::string name, const BufferViewData &bufferView, std::string mimeType);
json serialize() const override;
const std::string name;
const std::string uri; // non-empty in gltf mode
const int32_t bufferView; // non-negative in glb mode
const std::string mimeType;
};
#endif //FBX2GLTF_IMAGEDATA_H

View File

@ -1,202 +0,0 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#include "MaterialData.h"
#include "TextureData.h"
// TODO: retrieve & pass in correct UV set from FBX
std::unique_ptr<Tex> Tex::ref(const TextureData *tex, uint32_t texCoord)
{
return std::unique_ptr<Tex> { (tex != nullptr) ? new Tex(tex->ix, texCoord) : nullptr };
}
Tex::Tex(uint32_t texRef, uint32_t texCoord)
: texRef(texRef),
texCoord(texCoord) {}
void to_json(json &j, const Tex &data) {
j = json {
{ "index", data.texRef },
{ "texCoord", data.texCoord }
};
}
static inline Vec4f toRGBA(const Vec3f &colour, float alpha = 1) {
return { colour[0], colour[1], colour[2], alpha };
};
KHRCommonMats::KHRCommonMats(
MaterialType type,
const TextureData *shininessTexture, float shininess,
const TextureData *ambientTexture, const Vec3f &ambientFactor,
const TextureData *diffuseTexture, const Vec4f &diffuseFactor,
const TextureData *specularTexture, const Vec3f &specularFactor)
: type(type),
shininessTexture(Tex::ref(shininessTexture)),
shininess(shininess),
ambientTexture(Tex::ref(ambientTexture)),
ambientFactor(ambientFactor),
diffuseTexture(Tex::ref(diffuseTexture)),
diffuseFactor(diffuseFactor),
specularTexture(Tex::ref(specularTexture)),
specularFactor(specularFactor)
{
}
std::string KHRCommonMats::typeDesc(MaterialType type)
{
switch (type) {
case Blinn:
return "commonBlinn";
case Phong:
return "commonPhong";
case Lambert:
return "commonLambert";
case Constant:
return "commonConstant";
}
}
void to_json(json &j, const KHRCommonMats &d)
{
j = {{"type", KHRCommonMats::typeDesc(d.type)}};
if (d.shininessTexture != nullptr) {
j["shininessTexture"] = *d.shininessTexture;
}
if (d.shininess != 0) {
j["shininess"] = d.shininess;
}
if (d.ambientTexture != nullptr) {
j["ambientTexture"] = *d.ambientTexture;
}
if (d.ambientFactor.LengthSquared() > 0) {
j["ambientFactor"] = toStdVec(toRGBA(d.ambientFactor));
}
if (d.diffuseTexture != nullptr) {
j["diffuseTexture"] = *d.diffuseTexture;
}
if (d.diffuseFactor.LengthSquared() > 0) {
j["diffuseFactor"] = toStdVec(d.diffuseFactor);
}
if (d.specularTexture != nullptr) {
j["specularTexture"] = *d.specularTexture;
}
if (d.specularFactor.LengthSquared() > 0) {
j["specularFactor"] = toStdVec(toRGBA(d.specularFactor));
}
}
PBRSpecularGlossiness::PBRSpecularGlossiness(
const TextureData *diffuseTexture, const Vec4f &diffuseFactor,
const TextureData *specularGlossinessTexture, const Vec3f &specularFactor,
float glossinessFactor)
: diffuseTexture(Tex::ref(diffuseTexture)),
diffuseFactor(diffuseFactor),
specularGlossinessTexture(Tex::ref(specularGlossinessTexture)),
specularFactor(specularFactor),
glossinessFactor(glossinessFactor)
{
}
void to_json(json &j, const PBRSpecularGlossiness &d)
{
j = {};
if (d.diffuseTexture != nullptr) {
j["diffuseTexture"] = *d.diffuseTexture;
}
if (d.diffuseFactor.LengthSquared() > 0) {
j["diffuseFactor"] = toStdVec(d.diffuseFactor);
}
if (d.specularGlossinessTexture != nullptr) {
j["specularGlossinessTexture"] = *d.specularGlossinessTexture;
}
if (d.specularFactor.LengthSquared() > 0) {
j["specularFactor"] = toStdVec(d.specularFactor);
}
if (d.glossinessFactor != 0) {
j["glossinessFactor"] = d.glossinessFactor;
}
}
PBRMetallicRoughness::PBRMetallicRoughness(
const TextureData *baseColorTexture, const Vec4f &baseolorFactor,
float metallic, float roughness)
: baseColorTexture(Tex::ref(baseColorTexture)),
baseColorFactor(baseolorFactor),
metallic(metallic),
roughness(roughness)
{
}
void to_json(json &j, const PBRMetallicRoughness &d)
{
j = { };
if (d.baseColorTexture != nullptr) {
j["baseColorTexture"] = *d.baseColorTexture;
}
if (d.baseColorFactor.LengthSquared() > 0) {
j["baseColorFactor"] = toStdVec(d.baseColorFactor);
}
if (d.metallic != 1.0f) {
j["metallicFactor"] = d.metallic;
}
if (d.roughness != 1.0f) {
j["roughnessFactor"] = d.roughness;
}
}
MaterialData::MaterialData(
std::string name, bool isTransparent, const TextureData *normalTexture,
const TextureData *emissiveTexture, const Vec3f & emissiveFactor,
std::shared_ptr<KHRCommonMats> const khrCommonMats,
std::shared_ptr<PBRMetallicRoughness> const pbrMetallicRoughness,
std::shared_ptr<PBRSpecularGlossiness> const pbrSpecularGlossiness)
: Holdable(),
name(std::move(name)),
isTransparent(isTransparent),
normalTexture(Tex::ref(normalTexture)),
emissiveTexture(Tex::ref(emissiveTexture)),
emissiveFactor(std::move(emissiveFactor)),
khrCommonMats(khrCommonMats),
pbrMetallicRoughness(pbrMetallicRoughness),
pbrSpecularGlossiness(pbrSpecularGlossiness) {}
json MaterialData::serialize() const
{
json result = {
{ "name", name },
{ "alphaMode", isTransparent ? "BLEND" : "OPAQUE" }
};
if (normalTexture != nullptr) {
result["normalTexture"] = *normalTexture;
}
if (emissiveTexture != nullptr) {
result["emissiveTexture"] = *emissiveTexture;
}
if (emissiveFactor.LengthSquared() > 0) {
result["emissiveFactor"] = toStdVec(emissiveFactor);
}
if (pbrMetallicRoughness != nullptr) {
result["pbrMetallicRoughness"] = *pbrMetallicRoughness;
}
if (khrCommonMats != nullptr || pbrSpecularGlossiness != nullptr) {
json extensions = { };
if (khrCommonMats != nullptr) {
extensions[KHR_MATERIALS_COMMON] = *khrCommonMats;
}
if (pbrSpecularGlossiness != nullptr) {
extensions[KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS] = *pbrSpecularGlossiness;
}
result["extensions"] = extensions;
}
return result;
}

View File

@ -1,109 +0,0 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#ifndef FBX2GLTF_MATERIALDATA_H
#define FBX2GLTF_MATERIALDATA_H
#include <string>
#include "Raw2Gltf.h"
struct Tex
{
static std::unique_ptr<Tex> ref(const TextureData *tex, uint32_t texCoord = 0);
explicit Tex(uint32_t texRef, uint32_t texCoord);
const uint32_t texRef;
const uint32_t texCoord;
};
struct KHRCommonMats
{
enum MaterialType
{
Blinn,
Phong,
Lambert,
Constant,
};
KHRCommonMats(
MaterialType type,
const TextureData *shininessTexture, float shininess,
const TextureData *ambientTexture, const Vec3f &ambientFactor,
const TextureData *diffuseTexture, const Vec4f &diffuseFactor,
const TextureData *specularTexture, const Vec3f &specularFactor);
static std::string typeDesc(MaterialType type);
const MaterialType type;
const std::unique_ptr<Tex> shininessTexture;
const float shininess;
const std::unique_ptr<Tex> ambientTexture;
const Vec3f ambientFactor;
const std::unique_ptr<Tex> diffuseTexture;
const Vec4f diffuseFactor;
const std::unique_ptr<Tex> specularTexture;
const Vec3f specularFactor;
};
struct PBRSpecularGlossiness
{
PBRSpecularGlossiness(
const TextureData *diffuseTexture, const Vec4f &diffuseFactor,
const TextureData *specularGlossinessTexture,
const Vec3f &specularFactor, float glossinessFactor);
std::unique_ptr<Tex> diffuseTexture;
const Vec4f diffuseFactor;
std::unique_ptr<Tex> specularGlossinessTexture;
const Vec3f specularFactor;
const float glossinessFactor;
};
struct PBRMetallicRoughness
{
PBRMetallicRoughness(
const TextureData *baseColorTexture, const Vec4f &baseolorFactor,
float metallic = 0.1f, float roughness = 0.4f);
std::unique_ptr<Tex> baseColorTexture;
const Vec4f baseColorFactor;
const float metallic;
const float roughness;
};
struct MaterialData : Holdable
{
MaterialData(
std::string name, bool isTransparent, const TextureData *normalTexture,
const TextureData *emissiveTexture, const Vec3f &emissiveFactor,
std::shared_ptr<KHRCommonMats> const khrCommonMats,
std::shared_ptr<PBRMetallicRoughness> const pbrMetallicRoughness,
std::shared_ptr<PBRSpecularGlossiness> const pbrSpecularGlossiness);
json serialize() const override;
const std::string name;
const bool isTransparent;
const std::unique_ptr<const Tex> normalTexture;
const std::unique_ptr<const Tex> emissiveTexture;
const Vec3f emissiveFactor;
const std::shared_ptr<const KHRCommonMats> khrCommonMats;
const std::shared_ptr<const PBRMetallicRoughness> pbrMetallicRoughness;
const std::shared_ptr<const PBRSpecularGlossiness> pbrSpecularGlossiness;
};
void to_json(json &j, const Tex &data);
void to_json(json &j, const KHRCommonMats &d);
void to_json(json &j, const PBRSpecularGlossiness &d);
void to_json(json &j, const PBRMetallicRoughness &d);
#endif //FBX2GLTF_MATERIALDATA_H

View File

@ -1,29 +0,0 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#include "MeshData.h"
#include "PrimitiveData.h"
MeshData::MeshData(std::string name)
: Holdable(),
name(std::move(name))
{
}
json MeshData::serialize() const
{
json jsonPrimitivesArray = json::array();
for (const auto &primitive : primitives) {
jsonPrimitivesArray.push_back(*primitive);
}
return {
{ "name", name },
{ "primitives", jsonPrimitivesArray }
};
}

View File

@ -1,36 +0,0 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#ifndef FBX2GLTF_MESHDATA_H
#define FBX2GLTF_MESHDATA_H
#include <string>
#include <draco/compression/encode.h>
#include "Raw2Gltf.h"
#include "PrimitiveData.h"
struct MeshData : Holdable
{
explicit MeshData(std::string name);
void AddPrimitive(std::shared_ptr<PrimitiveData> primitive)
{
primitives.push_back(std::move(primitive));
}
json serialize() const override;
const std::string name;
std::vector<std::shared_ptr<PrimitiveData>> primitives;
};
#endif //FBX2GLTF_MESHDATA_H

View File

@ -1,83 +0,0 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#include "NodeData.h"
NodeData::NodeData(
std::string name, const Vec3f &translation,
const Quatf &rotation, const Vec3f &scale, bool isJoint)
: Holdable(),
name(std::move(name)),
isJoint(isJoint),
translation(translation),
rotation(rotation),
scale(scale),
children(),
mesh(-1),
cameraName(""),
skin(-1)
{
}
void NodeData::AddChildNode(uint32_t childIx)
{
children.push_back(childIx);
}
void NodeData::SetMesh(uint32_t meshIx)
{
assert(mesh < 0);
assert(!isJoint);
mesh = meshIx;
}
void NodeData::SetSkin(uint32_t skinIx)
{
assert(skin < 0);
assert(!isJoint);
skin = skinIx;
}
void NodeData::AddCamera(std::string camera)
{
assert(!isJoint);
cameraName = std::move(camera);
}
json NodeData::serialize() const
{
json result = {
{ "name", name },
{ "translation", toStdVec(translation) },
{ "rotation", toStdVec(rotation) },
{ "scale", toStdVec(scale) }
};
if (!children.empty()) {
result["children"] = children;
}
if (isJoint) {
// sanity-check joint node
assert(mesh < 0 && skin < 0);
} else {
// non-joint node
if (mesh >= 0) {
result["mesh"] = mesh;
}
if (!skeletons.empty()) {
result["skeletons"] = skeletons;
}
if (skin >= 0) {
result["skin"] = skin;
}
if (!cameraName.empty()) {
result["camera"] = cameraName;
}
}
return result;
}

View File

@ -1,38 +0,0 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#ifndef FBX2GLTF_NODEDATA_H
#define FBX2GLTF_NODEDATA_H
#include "Raw2Gltf.h"
struct NodeData : Holdable
{
NodeData(std::string name, const Vec3f &translation, const Quatf &rotation, const Vec3f &scale, bool isJoint);
void AddChildNode(uint32_t childIx);
void SetMesh(uint32_t meshIx);
void SetSkin(uint32_t skinIx);
void AddCamera(std::string camera);
json serialize() const override;
const std::string name;
const bool isJoint;
Vec3f translation;
Quatf rotation;
Vec3f scale;
std::vector<uint32_t> children;
int32_t mesh;
std::string cameraName;
int32_t skin;
std::vector<std::string> skeletons;
};
#endif //FBX2GLTF_NODEDATA_H

View File

@ -1,60 +0,0 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#include "PrimitiveData.h"
#include "MaterialData.h"
#include "AccessorData.h"
#include "BufferViewData.h"
PrimitiveData::PrimitiveData(const AccessorData &indices, const MaterialData &material, std::shared_ptr<draco::Mesh> dracoMesh)
: indices(indices.ix),
material(material.ix),
mode(TRIANGLES),
dracoMesh(dracoMesh),
dracoBufferView(-1) {}
PrimitiveData::PrimitiveData(const AccessorData &indices, const MaterialData &material)
: indices(indices.ix),
material(material.ix),
mode(TRIANGLES),
dracoMesh(nullptr),
dracoBufferView(-1)
{
}
void PrimitiveData::AddAttrib(std::string name, const AccessorData &accessor)
{
attributes[name] = accessor.ix;
}
void PrimitiveData::NoteDracoBuffer(const BufferViewData &data)
{
dracoBufferView = data.ix;
}
void to_json(json &j, const PrimitiveData &d) {
j = {
{ "material", d.material },
{ "mode", d.mode },
{ "attributes", d.attributes }
};
if (d.indices >= 0) {
j["indices"] = d.indices;
}
if (!d.dracoAttributes.empty()) {
j["extensions"] = {
{ KHR_DRACO_MESH_COMPRESSION, {
{ "bufferView", d.dracoBufferView },
{ "attributes", d.dracoAttributes }
}}
};
}
}

View File

@ -1,71 +0,0 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#ifndef FBX2GLTF_PRIMITIVEDATA_H
#define FBX2GLTF_PRIMITIVEDATA_H
#include "Raw2Gltf.h"
struct PrimitiveData
{
enum MeshMode
{
POINTS = 0,
LINES,
LINE_LOOP,
LINE_STRIP,
TRIANGLES,
TRIANGLE_STRIP,
TRIANGLE_FAN
};
PrimitiveData(const AccessorData &indices, const MaterialData &material, std::shared_ptr<draco::Mesh> dracoMesh);
PrimitiveData(const AccessorData &indices, const MaterialData &material);
void AddAttrib(std::string name, const AccessorData &accessor);
template<class T>
void AddDracoAttrib(const AttributeDefinition<T> attribute, const std::vector<T> &attribArr)
{
draco::PointAttribute att;
int8_t componentCount = attribute.glType.count;
att.Init(
attribute.dracoAttribute, nullptr, componentCount, attribute.dracoComponentType,
false, componentCount * draco::DataTypeLength(attribute.dracoComponentType), 0);
const int dracoAttId = dracoMesh->AddAttribute(att, true, attribArr.size());
draco::PointAttribute *attPtr = dracoMesh->attribute(dracoAttId);
std::vector<uint8_t> buf(sizeof(T));
for (uint32_t ii = 0; ii < attribArr.size(); ii++) {
uint8_t *ptr = &buf[0];
attribute.glType.write(ptr, attribArr[ii]);
attPtr->SetAttributeValue(attPtr->mapped_index(draco::PointIndex(ii)), ptr);
}
dracoAttributes[attribute.gltfName] = dracoAttId;
}
void NoteDracoBuffer(const BufferViewData &data);
const int indices;
const unsigned int material;
const MeshMode mode;
std::map<std::string, int> attributes;
std::map<std::string, int> dracoAttributes;
std::shared_ptr<draco::Mesh> dracoMesh;
int dracoBufferView;
};
void to_json(json &j, const PrimitiveData &d);
#endif //FBX2GLTF_PRIMITIVEDATA_H

View File

@ -1,28 +0,0 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#ifndef FBX2GLTF_SAMPLERDATA_H
#define FBX2GLTF_SAMPLERDATA_H
#include "Raw2Gltf.h"
struct SamplerData : Holdable
{
// this is where magFilter, minFilter, wrapS and wrapT would go, should we want it
SamplerData()
: Holdable()
{
}
json serialize() const {
return json::object();
}
};
#endif //FBX2GLTF_SAMPLERDATA_H

View File

@ -1,28 +0,0 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#include "SceneData.h"
#include "NodeData.h"
SceneData::SceneData(std::string name, const NodeData &rootNode)
: Holdable(),
name(std::move(name)),
nodes({rootNode.ix})
{
}
json SceneData::serialize() const
{
assert(nodes.size() <= 1);
return {
{ "name", name },
{ "nodes", nodes }
};
}

View File

@ -1,25 +0,0 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#ifndef FBX2GLTF_SCENEDATA_H
#define FBX2GLTF_SCENEDATA_H
#include "Raw2Gltf.h"
struct SceneData : Holdable
{
SceneData(std::string name, const NodeData &rootNode);
json serialize() const override;
const std::string name;
std::vector<uint32_t> nodes;
};
#endif //FBX2GLTF_SCENEDATA_H

View File

@ -1,32 +0,0 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#include "SkinData.h"
#include "AccessorData.h"
#include "NodeData.h"
SkinData::SkinData(
const std::vector<uint32_t> joints, const AccessorData &inverseBindMatricesAccessor,
const NodeData &skeletonRootNode)
: Holdable(),
joints(joints),
inverseBindMatrices(inverseBindMatricesAccessor.ix),
skeletonRootNode(skeletonRootNode.ix)
{
}
json SkinData::serialize() const
{
return {
{ "joints", joints },
{ "inverseBindMatrices", inverseBindMatrices },
{ "skeleton", skeletonRootNode }
};
}

View File

@ -1,28 +0,0 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#ifndef FBX2GLTF_SKINDATA_H
#define FBX2GLTF_SKINDATA_H
#include "Raw2Gltf.h"
struct SkinData : Holdable
{
SkinData(
const std::vector<uint32_t> joints, const AccessorData &inverseBindMatricesAccessor,
const NodeData &skeletonRootNode);
json serialize() const override;
const std::vector<uint32_t> joints;
const uint32_t skeletonRootNode;
const uint32_t inverseBindMatrices;
};
#endif //FBX2GLTF_SKINDATA_H

View File

@ -1,30 +0,0 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#include "TextureData.h"
#include "ImageData.h"
#include "SamplerData.h"
TextureData::TextureData(std::string name, const SamplerData &sampler, const ImageData &source)
: Holdable(),
name(std::move(name)),
sampler(sampler.ix),
source(source.ix)
{
}
json TextureData::serialize() const
{
return {
{ "name", name },
{ "sampler", sampler },
{ "source", source }
};
}

View File

@ -1,26 +0,0 @@
/**
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#ifndef FBX2GLTF_TEXTUREDATA_H
#define FBX2GLTF_TEXTUREDATA_H
#include "Raw2Gltf.h"
struct TextureData : Holdable
{
TextureData(std::string name, const SamplerData &sampler, const ImageData &source);
json serialize() const override;
const std::string name;
const uint32_t sampler;
const uint32_t source;
};
#endif //FBX2GLTF_TEXTUREDATA_H

85
src/gltf/GltfModel.cpp Normal file
View File

@ -0,0 +1,85 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "GltfModel.hpp"
std::shared_ptr<BufferViewData> GltfModel::GetAlignedBufferView(
BufferData& buffer,
const BufferViewData::GL_ArrayType target) {
uint32_t bufferSize = to_uint32(this->binary->size());
if ((bufferSize % 4) > 0) {
bufferSize += (4 - (bufferSize % 4));
this->binary->resize(bufferSize);
}
return this->bufferViews.hold(new BufferViewData(buffer, bufferSize, target));
}
// add a bufferview on the fly and copy data into it
std::shared_ptr<BufferViewData>
GltfModel::AddRawBufferView(BufferData& buffer, const char* source, uint32_t bytes) {
auto bufferView = GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_NONE);
bufferView->byteLength = bytes;
// make space for the new bytes (possibly moving the underlying data)
uint32_t bufferSize = to_uint32(this->binary->size());
this->binary->resize(bufferSize + bytes);
// and copy them into place
memcpy(&(*this->binary)[bufferSize], source, bytes);
return bufferView;
}
std::shared_ptr<BufferViewData> GltfModel::AddBufferViewForFile(
BufferData& buffer,
const std::string& filename) {
// see if we've already created a BufferViewData for this precise file
auto iter = filenameToBufferView.find(filename);
if (iter != filenameToBufferView.end()) {
return iter->second;
}
std::shared_ptr<BufferViewData> result;
std::ifstream file(filename, std::ios::binary | std::ios::ate);
if (file) {
std::streamsize size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<char> fileBuffer(size);
if (file.read(fileBuffer.data(), size)) {
result = AddRawBufferView(buffer, fileBuffer.data(), to_uint32(size));
} else {
fmt::printf("Warning: Couldn't read %lu bytes from %s, skipping file.\n", size, filename);
}
} else {
fmt::printf("Warning: Couldn't open file %s, skipping file.\n", filename);
}
// note that we persist here not only success, but also failure, as nullptr
filenameToBufferView[filename] = result;
return result;
}
void GltfModel::serializeHolders(json& glTFJson) {
serializeHolder(glTFJson, "buffers", buffers);
serializeHolder(glTFJson, "bufferViews", bufferViews);
serializeHolder(glTFJson, "scenes", scenes);
serializeHolder(glTFJson, "accessors", accessors);
serializeHolder(glTFJson, "images", images);
serializeHolder(glTFJson, "samplers", samplers);
serializeHolder(glTFJson, "textures", textures);
serializeHolder(glTFJson, "materials", materials);
serializeHolder(glTFJson, "meshes", meshes);
serializeHolder(glTFJson, "skins", skins);
serializeHolder(glTFJson, "animations", animations);
serializeHolder(glTFJson, "cameras", cameras);
serializeHolder(glTFJson, "nodes", nodes);
if (!lights.ptrs.empty()) {
json lightsJson = json::object();
serializeHolder(lightsJson, "lights", lights);
glTFJson["extensions"][KHR_LIGHTS_PUNCTUAL] = lightsJson;
}
}

171
src/gltf/GltfModel.hpp Normal file
View File

@ -0,0 +1,171 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <fstream>
#include "FBX2glTF.h"
#include "gltf/properties/AccessorData.hpp"
#include "gltf/properties/AnimationData.hpp"
#include "gltf/properties/BufferData.hpp"
#include "gltf/properties/BufferViewData.hpp"
#include "gltf/properties/CameraData.hpp"
#include "gltf/properties/ImageData.hpp"
#include "gltf/properties/LightData.hpp"
#include "gltf/properties/MaterialData.hpp"
#include "gltf/properties/MeshData.hpp"
#include "gltf/properties/NodeData.hpp"
#include "gltf/properties/PrimitiveData.hpp"
#include "gltf/properties/SamplerData.hpp"
#include "gltf/properties/SceneData.hpp"
#include "gltf/properties/SkinData.hpp"
#include "gltf/properties/TextureData.hpp"
/**
* glTF 2.0 is based on the idea that data structs within a file are referenced by index; an
* accessor will point to the n:th buffer view, and so on. The Holder class takes a freshly
* instantiated class, and then creates, stored, and returns a shared_ptr<T> for it.
*
* The idea is that every glTF resource in the file will live as long as the Holder does, and the
* Holders are all kept in the GLTFData struct. Clients may certainly cnhoose to perpetuate the full
* shared_ptr<T> reference counting type, but generally speaking we pass around simple T& and T*
* types because the GLTFData struct will, by design, outlive all other activity that takes place
* during in a single conversion run.
*/
template <typename T>
class Holder {
public:
std::shared_ptr<T> hold(T* ptr) {
ptr->ix = to_uint32(ptrs.size());
ptrs.emplace_back(ptr);
return ptrs.back();
}
std::vector<std::shared_ptr<T>> ptrs;
};
class GltfModel {
public:
explicit GltfModel(const GltfOptions& options)
: binary(new std::vector<uint8_t>),
isGlb(options.outputBinary),
defaultSampler(nullptr),
defaultBuffer(buffers.hold(buildDefaultBuffer(options))) {
defaultSampler = samplers.hold(buildDefaultSampler());
}
std::shared_ptr<BufferViewData> GetAlignedBufferView(
BufferData& buffer,
const BufferViewData::GL_ArrayType target);
std::shared_ptr<BufferViewData>
AddRawBufferView(BufferData& buffer, const char* source, uint32_t bytes);
std::shared_ptr<BufferViewData> AddBufferViewForFile(
BufferData& buffer,
const std::string& filename);
template <class T>
std::shared_ptr<AccessorData> AddAccessorWithView(
BufferViewData& bufferView,
const GLType& type,
const std::vector<T>& source,
std::string name) {
auto accessor = accessors.hold(new AccessorData(bufferView, type, name));
accessor->appendAsBinaryArray(source, *binary);
bufferView.byteLength = accessor->byteLength();
return accessor;
}
template <class T>
std::shared_ptr<AccessorData>
AddAccessorAndView(BufferData& buffer, const GLType& type, const std::vector<T>& source) {
auto bufferView = GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_NONE);
return AddAccessorWithView(*bufferView, type, source, std::string(""));
}
template <class T>
std::shared_ptr<AccessorData> AddAccessorAndView(
BufferData& buffer,
const GLType& type,
const std::vector<T>& source,
std::string name) {
auto bufferView = GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_NONE);
return AddAccessorWithView(*bufferView, type, source, name);
}
template <class T>
std::shared_ptr<AccessorData> AddAttributeToPrimitive(
BufferData& buffer,
const RawModel& surfaceModel,
PrimitiveData& primitive,
const AttributeDefinition<T>& attrDef) {
// copy attribute data into vector
std::vector<T> attribArr;
surfaceModel.GetAttributeArray<T>(attribArr, attrDef.rawAttributeIx);
std::shared_ptr<AccessorData> accessor;
if (attrDef.dracoComponentType != draco::DT_INVALID && primitive.dracoMesh != nullptr) {
primitive.AddDracoAttrib(attrDef, attribArr);
accessor = accessors.hold(new AccessorData(attrDef.glType));
accessor->count = to_uint32(attribArr.size());
} else {
auto bufferView = GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_BUFFER);
accessor = AddAccessorWithView(*bufferView, attrDef.glType, attribArr, std::string(""));
}
primitive.AddAttrib(attrDef.gltfName, *accessor);
return accessor;
};
template <class T>
void serializeHolder(json& glTFJson, std::string key, const Holder<T> holder) {
if (!holder.ptrs.empty()) {
std::vector<json> bits;
for (const auto& ptr : holder.ptrs) {
bits.push_back(ptr->serialize());
}
glTFJson[key] = bits;
}
}
void serializeHolders(json& glTFJson);
const bool isGlb;
// cache BufferViewData instances that've already been created from a given filename
std::map<std::string, std::shared_ptr<BufferViewData>> filenameToBufferView;
std::shared_ptr<std::vector<uint8_t>> binary;
Holder<BufferData> buffers;
Holder<BufferViewData> bufferViews;
Holder<AccessorData> accessors;
Holder<ImageData> images;
Holder<SamplerData> samplers;
Holder<TextureData> textures;
Holder<MaterialData> materials;
Holder<MeshData> meshes;
Holder<SkinData> skins;
Holder<AnimationData> animations;
Holder<CameraData> cameras;
Holder<NodeData> nodes;
Holder<SceneData> scenes;
Holder<LightData> lights;
std::shared_ptr<SamplerData> defaultSampler;
std::shared_ptr<BufferData> defaultBuffer;
private:
SamplerData* buildDefaultSampler() {
return new SamplerData();
}
BufferData* buildDefaultBuffer(const GltfOptions& options) {
return options.outputBinary ? new BufferData(binary)
: new BufferData(extBufferFilename, binary, options.embedResources);
}
};

881
src/gltf/Raw2Gltf.cpp Normal file
View File

@ -0,0 +1,881 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "Raw2Gltf.hpp"
#include <cassert>
#include <cstdint>
#include <fstream>
#include <iostream>
#include <stb_image.h>
#include <stb_image_write.h>
#include <utils/File_Utils.hpp>
#include "utils/Image_Utils.hpp"
#include "utils/String_Utils.hpp"
#include "raw/RawModel.hpp"
#include "gltf/properties/AccessorData.hpp"
#include "gltf/properties/AnimationData.hpp"
#include "gltf/properties/BufferData.hpp"
#include "gltf/properties/BufferViewData.hpp"
#include "gltf/properties/CameraData.hpp"
#include "gltf/properties/ImageData.hpp"
#include "gltf/properties/MaterialData.hpp"
#include "gltf/properties/MeshData.hpp"
#include "gltf/properties/NodeData.hpp"
#include "gltf/properties/PrimitiveData.hpp"
#include "gltf/properties/SamplerData.hpp"
#include "gltf/properties/SceneData.hpp"
#include "gltf/properties/SkinData.hpp"
#include "gltf/properties/TextureData.hpp"
#include "GltfModel.hpp"
#include "TextureBuilder.hpp"
typedef uint32_t TriangleIndex;
#define DEFAULT_SCENE_NAME "Root Scene"
/**
* This method sanity-checks existance and then returns a *reference* to the *Data instance
* registered under that name. This is safe in the context of this tool, where all such data
* classes are guaranteed to stick around for the duration of the process.
*/
template <typename T>
T& require(std::map<std::string, std::shared_ptr<T>> map, const std::string& key) {
auto iter = map.find(key);
assert(iter != map.end());
T& result = *iter->second;
return result;
}
template <typename T>
T& require(std::map<long, std::shared_ptr<T>> map, long key) {
auto iter = map.find(key);
assert(iter != map.end());
T& result = *iter->second;
return result;
}
static const std::vector<TriangleIndex> getIndexArray(const RawModel& raw) {
std::vector<TriangleIndex> result;
for (int i = 0; i < raw.GetTriangleCount(); i++) {
result.push_back((TriangleIndex)raw.GetTriangle(i).verts[0]);
result.push_back((TriangleIndex)raw.GetTriangle(i).verts[1]);
result.push_back((TriangleIndex)raw.GetTriangle(i).verts[2]);
}
return result;
}
ModelData* Raw2Gltf(
std::ofstream& gltfOutStream,
const std::string& outputFolder,
const RawModel& raw,
const GltfOptions& options) {
if (verboseOutput) {
fmt::printf("Building render model...\n");
for (int i = 0; i < raw.GetMaterialCount(); i++) {
fmt::printf(
"Material %d: %s [shading: %s]\n",
i,
raw.GetMaterial(i).name.c_str(),
Describe(raw.GetMaterial(i).info->shadingModel));
}
if (raw.GetVertexCount() > 2 * raw.GetTriangleCount()) {
fmt::printf(
"Warning: High vertex count. Make sure there are no unnecessary vertex attributes. (see -keepAttribute cmd-line option)");
}
}
std::vector<RawModel> materialModels;
raw.CreateMaterialModels(
materialModels,
options.useLongIndices == UseLongIndicesOptions::NEVER,
options.keepAttribs,
true);
if (verboseOutput) {
fmt::printf("%7d vertices\n", raw.GetVertexCount());
fmt::printf("%7d triangles\n", raw.GetTriangleCount());
fmt::printf("%7d textures\n", raw.GetTextureCount());
fmt::printf("%7d nodes\n", raw.GetNodeCount());
fmt::printf("%7d surfaces\n", (int)materialModels.size());
fmt::printf("%7d animations\n", raw.GetAnimationCount());
fmt::printf("%7d cameras\n", raw.GetCameraCount());
fmt::printf("%7d lights\n", raw.GetLightCount());
}
std::unique_ptr<GltfModel> gltf(new GltfModel(options));
std::map<long, std::shared_ptr<NodeData>> nodesById;
std::map<long, std::shared_ptr<MaterialData>> materialsById;
std::map<std::string, std::shared_ptr<TextureData>> textureByIndicesKey;
std::map<long, std::shared_ptr<MeshData>> meshBySurfaceId;
// for now, we only have one buffer; data->binary points to the same vector as that BufferData
// does.
BufferData& buffer = *gltf->defaultBuffer;
{
//
// nodes
//
for (int i = 0; i < raw.GetNodeCount(); i++) {
// assumption: RawNode index == NodeData index
const RawNode& node = raw.GetNode(i);
auto nodeData = gltf->nodes.hold(
new NodeData(node.name, node.translation, node.rotation, node.scale, node.isJoint));
if (options.enableUserProperties) {
nodeData->userProperties = node.userProperties;
}
for (const auto& childId : node.childIds) {
int childIx = raw.GetNodeById(childId);
assert(childIx >= 0);
nodeData->AddChildNode(childIx);
}
nodesById.insert(std::make_pair(node.id, nodeData));
}
//
// animations
//
for (int i = 0; i < raw.GetAnimationCount(); i++) {
const RawAnimation& animation = raw.GetAnimation(i);
if (animation.channels.size() == 0) {
fmt::printf(
"Warning: animation '%s' has zero channels. Skipping.\n", animation.name.c_str());
continue;
}
auto accessor = gltf->AddAccessorAndView(buffer, GLT_FLOAT, animation.times);
accessor->min = {*std::min_element(std::begin(animation.times), std::end(animation.times))};
accessor->max = {*std::max_element(std::begin(animation.times), std::end(animation.times))};
AnimationData& aDat = *gltf->animations.hold(new AnimationData(animation.name, *accessor));
if (verboseOutput) {
fmt::printf(
"Animation '%s' has %lu channels:\n",
animation.name.c_str(),
animation.channels.size());
}
for (size_t channelIx = 0; channelIx < animation.channels.size(); channelIx++) {
const RawChannel& channel = animation.channels[channelIx];
const RawNode& node = raw.GetNode(channel.nodeIndex);
if (verboseOutput) {
fmt::printf(
" Channel %lu (%s) has translations/rotations/scales/weights: [%lu, %lu, %lu, %lu]\n",
channelIx,
node.name.c_str(),
channel.translations.size(),
channel.rotations.size(),
channel.scales.size(),
channel.weights.size());
}
NodeData& nDat = require(nodesById, node.id);
if (!channel.translations.empty()) {
aDat.AddNodeChannel(
nDat,
*gltf->AddAccessorAndView(buffer, GLT_VEC3F, channel.translations),
"translation");
}
if (!channel.rotations.empty()) {
aDat.AddNodeChannel(
nDat, *gltf->AddAccessorAndView(buffer, GLT_QUATF, channel.rotations), "rotation");
}
if (!channel.scales.empty()) {
aDat.AddNodeChannel(
nDat, *gltf->AddAccessorAndView(buffer, GLT_VEC3F, channel.scales), "scale");
}
if (!channel.weights.empty()) {
aDat.AddNodeChannel(
nDat,
*gltf->AddAccessorAndView(buffer, {CT_FLOAT, 1, "SCALAR"}, channel.weights),
"weights");
}
}
}
//
// samplers
//
// textures
//
TextureBuilder textureBuilder(raw, options, outputFolder, *gltf);
//
// materials
//
for (int materialIndex = 0; materialIndex < raw.GetMaterialCount(); materialIndex++) {
const RawMaterial& material = raw.GetMaterial(materialIndex);
const bool isTransparent = material.type == RAW_MATERIAL_TYPE_TRANSPARENT ||
material.type == RAW_MATERIAL_TYPE_SKINNED_TRANSPARENT;
Vec3f emissiveFactor;
float emissiveIntensity;
// acquire the texture of a specific RawTextureUsage as *TextData, or nullptr if none exists
auto simpleTex = [&](RawTextureUsage usage) -> std::shared_ptr<TextureData> {
return (material.textures[usage] >= 0)
? textureBuilder.simple(material.textures[usage], "simple")
: nullptr;
};
TextureData* normalTexture = simpleTex(RAW_TEXTURE_USAGE_NORMAL).get();
TextureData* emissiveTexture = simpleTex(RAW_TEXTURE_USAGE_EMISSIVE).get();
TextureData* occlusionTexture = nullptr;
std::shared_ptr<PBRMetallicRoughness> pbrMetRough;
if (options.usePBRMetRough) {
// albedo is a basic texture, no merging needed
std::shared_ptr<TextureData> baseColorTex, aoMetRoughTex;
Vec4f diffuseFactor;
float metallic, roughness;
if (material.info->shadingModel == RAW_SHADING_MODEL_PBR_MET_ROUGH) {
/**
* PBR FBX Material -> PBR Met/Rough glTF.
*
* METALLIC and ROUGHNESS textures are packed in G and B channels of a rough/met texture.
* Other values translate directly.
*/
RawMetRoughMatProps* props = (RawMetRoughMatProps*)material.info.get();
// determine if we need to generate a combined map
bool hasMetallicMap = material.textures[RAW_TEXTURE_USAGE_METALLIC] >= 0;
bool hasRoughnessMap = material.textures[RAW_TEXTURE_USAGE_ROUGHNESS] >= 0;
bool hasOcclusionMap = material.textures[RAW_TEXTURE_USAGE_OCCLUSION] >= 0;
bool atLeastTwoMaps = hasMetallicMap ? (hasRoughnessMap || hasOcclusionMap)
: (hasRoughnessMap && hasMetallicMap);
if (!atLeastTwoMaps) {
// this handles the case of 0 or 1 maps supplied
aoMetRoughTex = hasMetallicMap
? simpleTex(RAW_TEXTURE_USAGE_METALLIC)
: (hasRoughnessMap
? simpleTex(RAW_TEXTURE_USAGE_ROUGHNESS)
: (hasOcclusionMap ? simpleTex(RAW_TEXTURE_USAGE_OCCLUSION) : nullptr));
} else {
// otherwise merge occlusion into the red channel, metallic into blue channel, and
// roughness into the green, of a new combinatory texture
aoMetRoughTex = textureBuilder.combine(
{
material.textures[RAW_TEXTURE_USAGE_OCCLUSION],
material.textures[RAW_TEXTURE_USAGE_METALLIC],
material.textures[RAW_TEXTURE_USAGE_ROUGHNESS],
},
"ao_met_rough",
[&](const std::vector<const TextureBuilder::pixel*> pixels)
-> TextureBuilder::pixel {
const float occlusion = (*pixels[0])[0];
const float metallic = (*pixels[1])[0] * (hasMetallicMap ? 1 : props->metallic);
const float roughness =
(*pixels[2])[0] * (hasRoughnessMap ? 1 : props->roughness);
return {{occlusion,
props->invertRoughnessMap ? 1.0f - roughness : roughness,
metallic,
1}};
},
false);
}
baseColorTex = simpleTex(RAW_TEXTURE_USAGE_ALBEDO);
diffuseFactor = props->diffuseFactor;
metallic = props->metallic;
roughness = props->roughness;
emissiveFactor = props->emissiveFactor;
emissiveIntensity = props->emissiveIntensity;
// this will set occlusionTexture to null, if no actual occlusion map exists
occlusionTexture = aoMetRoughTex.get();
} else {
/**
* Traditional FBX Material -> PBR Met/Rough glTF.
*
* Diffuse channel is used as base colour. Simple constants for metallic and roughness.
*/
const RawTraditionalMatProps* props = ((RawTraditionalMatProps*)material.info.get());
diffuseFactor = props->diffuseFactor;
if (material.info->shadingModel == RAW_SHADING_MODEL_BLINN ||
material.info->shadingModel == RAW_SHADING_MODEL_PHONG) {
// blinn/phong hardcoded to 0.4 metallic
metallic = 0.4f;
// fairly arbitrary conversion equation, with properties:
// shininess 0 -> roughness 1
// shininess 2 -> roughness ~0.7
// shininess 6 -> roughness 0.5
// shininess 16 -> roughness ~0.33
// as shininess ==> oo, roughness ==> 0
auto getRoughness = [&](float shininess) { return sqrtf(2.0f / (2.0f + shininess)); };
aoMetRoughTex = textureBuilder.combine(
{
material.textures[RAW_TEXTURE_USAGE_SHININESS],
},
"ao_met_rough",
[&](const std::vector<const TextureBuilder::pixel*> pixels)
-> TextureBuilder::pixel {
// do not multiply with props->shininess; that doesn't work like the other
// factors.
float shininess = props->shininess * (*pixels[0])[0];
return {{0, getRoughness(shininess), metallic, 1}};
},
false);
if (aoMetRoughTex != nullptr) {
// if we successfully built a texture, factors are just multiplicative identity
metallic = roughness = 1.0f;
} else {
// no shininess texture,
roughness = getRoughness(props->shininess);
}
} else {
metallic = 0.2f;
roughness = 0.8f;
}
baseColorTex = simpleTex(RAW_TEXTURE_USAGE_DIFFUSE);
emissiveFactor = props->emissiveFactor;
emissiveIntensity = 1.0f;
}
pbrMetRough.reset(new PBRMetallicRoughness(
baseColorTex.get(), aoMetRoughTex.get(), diffuseFactor, metallic, roughness));
}
std::shared_ptr<KHRCmnUnlitMaterial> khrCmnUnlitMat;
if (options.useKHRMatUnlit) {
normalTexture = nullptr;
emissiveTexture = nullptr;
emissiveFactor = Vec3f(0.00f, 0.00f, 0.00f);
Vec4f diffuseFactor;
std::shared_ptr<TextureData> baseColorTex;
if (material.info->shadingModel == RAW_SHADING_MODEL_PBR_MET_ROUGH) {
RawMetRoughMatProps* props = (RawMetRoughMatProps*)material.info.get();
diffuseFactor = props->diffuseFactor;
baseColorTex = simpleTex(RAW_TEXTURE_USAGE_ALBEDO);
} else {
RawTraditionalMatProps* props = ((RawTraditionalMatProps*)material.info.get());
diffuseFactor = props->diffuseFactor;
baseColorTex = simpleTex(RAW_TEXTURE_USAGE_DIFFUSE);
}
pbrMetRough.reset(
new PBRMetallicRoughness(baseColorTex.get(), nullptr, diffuseFactor, 0.0f, 1.0f));
khrCmnUnlitMat.reset(new KHRCmnUnlitMaterial());
}
if (!occlusionTexture) {
occlusionTexture = simpleTex(RAW_TEXTURE_USAGE_OCCLUSION).get();
}
std::shared_ptr<MaterialData> mData = gltf->materials.hold(new MaterialData(
material.name,
isTransparent,
material.info->shadingModel,
normalTexture,
occlusionTexture,
emissiveTexture,
emissiveFactor * emissiveIntensity,
khrCmnUnlitMat,
pbrMetRough));
materialsById[material.id] = mData;
if (options.enableUserProperties) {
mData->userProperties = material.userProperties;
}
}
for (const auto& surfaceModel : materialModels) {
assert(surfaceModel.GetSurfaceCount() == 1);
const RawSurface& rawSurface = surfaceModel.GetSurface(0);
const long surfaceId = rawSurface.id;
const RawMaterial& rawMaterial =
surfaceModel.GetMaterial(surfaceModel.GetTriangle(0).materialIndex);
const MaterialData& mData = require(materialsById, rawMaterial.id);
MeshData* mesh = nullptr;
auto meshIter = meshBySurfaceId.find(surfaceId);
if (meshIter != meshBySurfaceId.end()) {
mesh = meshIter->second.get();
} else {
std::vector<float> defaultDeforms;
for (const auto& channel : rawSurface.blendChannels) {
defaultDeforms.push_back(channel.defaultDeform);
}
auto meshPtr = gltf->meshes.hold(new MeshData(rawSurface.name, defaultDeforms));
meshBySurfaceId[surfaceId] = meshPtr;
mesh = meshPtr.get();
}
bool useLongIndices = (options.useLongIndices == UseLongIndicesOptions::ALWAYS) ||
(options.useLongIndices == UseLongIndicesOptions::AUTO &&
surfaceModel.GetVertexCount() > 65535);
std::shared_ptr<PrimitiveData> primitive;
if (options.draco.enabled) {
size_t triangleCount = surfaceModel.GetTriangleCount();
// initialize Draco mesh with vertex index information
auto dracoMesh(std::make_shared<draco::Mesh>());
dracoMesh->SetNumFaces(triangleCount);
dracoMesh->set_num_points(surfaceModel.GetVertexCount());
for (uint32_t ii = 0; ii < triangleCount; ii++) {
draco::Mesh::Face face;
face[0] = surfaceModel.GetTriangle(ii).verts[0];
face[1] = surfaceModel.GetTriangle(ii).verts[1];
face[2] = surfaceModel.GetTriangle(ii).verts[2];
dracoMesh->SetFace(draco::FaceIndex(ii), face);
}
AccessorData& indexes =
*gltf->accessors.hold(new AccessorData(useLongIndices ? GLT_UINT : GLT_USHORT));
indexes.count = to_uint32(3 * triangleCount);
primitive.reset(new PrimitiveData(indexes, mData, dracoMesh));
} else {
const AccessorData& indexes = *gltf->AddAccessorWithView(
*gltf->GetAlignedBufferView(buffer, BufferViewData::GL_ELEMENT_ARRAY_BUFFER),
useLongIndices ? GLT_UINT : GLT_USHORT,
getIndexArray(surfaceModel),
std::string(""));
primitive.reset(new PrimitiveData(indexes, mData));
};
//
// surface vertices
//
{
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_POSITION) != 0) {
const AttributeDefinition<Vec3f> ATTR_POSITION(
"POSITION",
&RawVertex::position,
GLT_VEC3F,
draco::GeometryAttribute::POSITION,
draco::DT_FLOAT32);
auto accessor =
gltf->AddAttributeToPrimitive<Vec3f>(buffer, surfaceModel, *primitive, ATTR_POSITION);
accessor->min = toStdVec(rawSurface.bounds.min);
accessor->max = toStdVec(rawSurface.bounds.max);
}
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_NORMAL) != 0) {
const AttributeDefinition<Vec3f> ATTR_NORMAL(
"NORMAL",
&RawVertex::normal,
GLT_VEC3F,
draco::GeometryAttribute::NORMAL,
draco::DT_FLOAT32);
const auto _ =
gltf->AddAttributeToPrimitive<Vec3f>(buffer, surfaceModel, *primitive, ATTR_NORMAL);
}
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_TANGENT) != 0) {
const AttributeDefinition<Vec4f> ATTR_TANGENT("TANGENT", &RawVertex::tangent, GLT_VEC4F);
const auto _ = gltf->AddAttributeToPrimitive<Vec4f>(
buffer, surfaceModel, *primitive, ATTR_TANGENT);
}
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_COLOR) != 0) {
const AttributeDefinition<Vec4f> ATTR_COLOR(
"COLOR_0",
&RawVertex::color,
GLT_VEC4F,
draco::GeometryAttribute::COLOR,
draco::DT_FLOAT32);
const auto _ =
gltf->AddAttributeToPrimitive<Vec4f>(buffer, surfaceModel, *primitive, ATTR_COLOR);
}
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_UV0) != 0) {
const AttributeDefinition<Vec2f> ATTR_TEXCOORD_0(
"TEXCOORD_0",
&RawVertex::uv0,
GLT_VEC2F,
draco::GeometryAttribute::TEX_COORD,
draco::DT_FLOAT32);
const auto _ = gltf->AddAttributeToPrimitive<Vec2f>(
buffer, surfaceModel, *primitive, ATTR_TEXCOORD_0);
}
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_UV1) != 0) {
const AttributeDefinition<Vec2f> ATTR_TEXCOORD_1(
"TEXCOORD_1",
&RawVertex::uv1,
GLT_VEC2F,
draco::GeometryAttribute::TEX_COORD,
draco::DT_FLOAT32);
const auto _ = gltf->AddAttributeToPrimitive<Vec2f>(
buffer, surfaceModel, *primitive, ATTR_TEXCOORD_1);
}
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_JOINT_INDICES) != 0) {
const AttributeDefinition<Vec4i> ATTR_JOINTS(
"JOINTS_0",
&RawVertex::jointIndices,
GLT_VEC4I,
draco::GeometryAttribute::GENERIC,
draco::DT_UINT16);
const auto _ =
gltf->AddAttributeToPrimitive<Vec4i>(buffer, surfaceModel, *primitive, ATTR_JOINTS);
}
if ((surfaceModel.GetVertexAttributes() & RAW_VERTEX_ATTRIBUTE_JOINT_WEIGHTS) != 0) {
const AttributeDefinition<Vec4f> ATTR_WEIGHTS(
"WEIGHTS_0",
&RawVertex::jointWeights,
GLT_VEC4F,
draco::GeometryAttribute::GENERIC,
draco::DT_FLOAT32);
const auto _ =
gltf->AddAttributeToPrimitive<Vec4f>(buffer, surfaceModel, *primitive, ATTR_WEIGHTS);
}
// each channel present in the mesh always ends up a target in the primitive
for (int channelIx = 0; channelIx < rawSurface.blendChannels.size(); channelIx++) {
const auto& channel = rawSurface.blendChannels[channelIx];
// track the bounds of each shape channel
Bounds<float, 3> shapeBounds;
std::vector<Vec3f> positions, normals;
std::vector<Vec4f> tangents;
for (int jj = 0; jj < surfaceModel.GetVertexCount(); jj++) {
auto blendVertex = surfaceModel.GetVertex(jj).blends[channelIx];
shapeBounds.AddPoint(blendVertex.position);
positions.push_back(blendVertex.position);
if (options.useBlendShapeTangents && channel.hasNormals) {
normals.push_back(blendVertex.normal);
}
if (options.useBlendShapeTangents && channel.hasTangents) {
tangents.push_back(blendVertex.tangent);
}
}
std::shared_ptr<AccessorData> pAcc = gltf->AddAccessorWithView(
*gltf->GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_BUFFER),
GLT_VEC3F,
positions,
channel.name);
pAcc->min = toStdVec(shapeBounds.min);
pAcc->max = toStdVec(shapeBounds.max);
std::shared_ptr<AccessorData> nAcc;
if (!normals.empty()) {
nAcc = gltf->AddAccessorWithView(
*gltf->GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_BUFFER),
GLT_VEC3F,
normals,
channel.name);
}
std::shared_ptr<AccessorData> tAcc;
if (!tangents.empty()) {
nAcc = gltf->AddAccessorWithView(
*gltf->GetAlignedBufferView(buffer, BufferViewData::GL_ARRAY_BUFFER),
GLT_VEC4F,
tangents,
channel.name);
}
primitive->AddTarget(pAcc.get(), nAcc.get(), tAcc.get());
}
}
if (options.draco.enabled) {
// Set up the encoder.
draco::Encoder encoder;
if (options.draco.compressionLevel != -1) {
int dracoSpeed = 10 - options.draco.compressionLevel;
encoder.SetSpeedOptions(dracoSpeed, dracoSpeed);
}
if (options.draco.quantBitsPosition != -1) {
encoder.SetAttributeQuantization(
draco::GeometryAttribute::POSITION, options.draco.quantBitsPosition);
}
if (options.draco.quantBitsTexCoord != -1) {
encoder.SetAttributeQuantization(
draco::GeometryAttribute::TEX_COORD, options.draco.quantBitsTexCoord);
}
if (options.draco.quantBitsNormal != -1) {
encoder.SetAttributeQuantization(
draco::GeometryAttribute::NORMAL, options.draco.quantBitsNormal);
}
if (options.draco.quantBitsColor != -1) {
encoder.SetAttributeQuantization(
draco::GeometryAttribute::COLOR, options.draco.quantBitsColor);
}
if (options.draco.quantBitsGeneric != -1) {
encoder.SetAttributeQuantization(
draco::GeometryAttribute::GENERIC, options.draco.quantBitsGeneric);
}
draco::EncoderBuffer dracoBuffer;
draco::Status status = encoder.EncodeMeshToBuffer(*primitive->dracoMesh, &dracoBuffer);
assert(status.code() == draco::Status::OK);
auto view = gltf->AddRawBufferView(buffer, dracoBuffer.data(), to_uint32(dracoBuffer.size()));
primitive->NoteDracoBuffer(*view);
}
mesh->AddPrimitive(primitive);
}
//
// Assign meshes to node
//
for (int i = 0; i < raw.GetNodeCount(); i++) {
const RawNode& node = raw.GetNode(i);
auto nodeData = gltf->nodes.ptrs[i];
//
// Assign mesh to node
//
if (node.surfaceId > 0) {
int surfaceIndex = raw.GetSurfaceById(node.surfaceId);
const RawSurface& rawSurface = raw.GetSurface(surfaceIndex);
MeshData& meshData = require(meshBySurfaceId, rawSurface.id);
nodeData->SetMesh(meshData.ix);
//
// surface skin
//
if (!rawSurface.jointIds.empty()) {
if (nodeData->skin == -1) {
// glTF uses column-major matrices
std::vector<Mat4f> inverseBindMatrices;
for (const auto& inverseBindMatrice : rawSurface.inverseBindMatrices) {
inverseBindMatrices.push_back(inverseBindMatrice.Transpose());
}
std::vector<uint32_t> jointIndexes;
for (const auto& jointId : rawSurface.jointIds) {
jointIndexes.push_back(require(nodesById, jointId).ix);
}
// Write out inverseBindMatrices
auto accIBM = gltf->AddAccessorAndView(buffer, GLT_MAT4F, inverseBindMatrices);
auto skeletonRoot = require(nodesById, rawSurface.skeletonRootId);
auto skin = *gltf->skins.hold(new SkinData(jointIndexes, *accIBM, skeletonRoot));
nodeData->SetSkin(skin.ix);
}
}
}
}
//
// cameras
//
for (int i = 0; i < raw.GetCameraCount(); i++) {
const RawCamera& cam = raw.GetCamera(i);
CameraData& camera = *gltf->cameras.hold(new CameraData());
camera.name = cam.name;
if (cam.mode == RawCamera::CAMERA_MODE_PERSPECTIVE) {
camera.type = "perspective";
camera.aspectRatio = cam.perspective.aspectRatio;
camera.yfov = cam.perspective.fovDegreesY * ((float)M_PI / 180.0f);
camera.znear = cam.perspective.nearZ;
camera.zfar = cam.perspective.farZ;
} else {
camera.type = "orthographic";
camera.xmag = cam.orthographic.magX;
camera.ymag = cam.orthographic.magY;
camera.znear = cam.orthographic.nearZ;
camera.zfar = cam.orthographic.farZ;
}
// Add the camera to the node hierarchy.
auto iter = nodesById.find(cam.nodeId);
if (iter == nodesById.end()) {
fmt::printf("Warning: Camera node id %lu does not exist.\n", cam.nodeId);
continue;
}
iter->second->SetCamera(camera.ix);
}
//
// lights
//
std::vector<json> khrPunctualLights;
if (options.useKHRLightsPunctual) {
for (int i = 0; i < raw.GetLightCount(); i++) {
const RawLight& light = raw.GetLight(i);
LightData::Type type;
switch (light.type) {
case RAW_LIGHT_TYPE_DIRECTIONAL:
type = LightData::Type::Directional;
break;
case RAW_LIGHT_TYPE_POINT:
type = LightData::Type::Point;
break;
case RAW_LIGHT_TYPE_SPOT:
type = LightData::Type::Spot;
break;
}
const auto _ = gltf->lights.hold(new LightData(
light.name,
type,
light.color,
// FBX intensity defaults to 100, so let's call that 1.0;
// but caveat: I find nothing in the documentation to suggest
// what unit the FBX value is meant to be measured in...
light.intensity / 100,
light.innerConeAngle,
light.outerConeAngle));
}
for (int i = 0; i < raw.GetNodeCount(); i++) {
const RawNode& node = raw.GetNode(i);
const auto nodeData = gltf->nodes.ptrs[i];
if (node.lightIx >= 0) {
// we lean on the fact that in this simple case, raw and gltf indexing are aligned
nodeData->SetLight(node.lightIx);
}
}
}
}
NodeData& rootNode = require(nodesById, raw.GetRootNode());
const SceneData& rootScene = *gltf->scenes.hold(new SceneData(DEFAULT_SCENE_NAME, rootNode));
if (options.outputBinary) {
// note: glTF binary is little-endian
const char glbHeader[] = {
'g',
'l',
'T',
'F', // magic
0x02,
0x00,
0x00,
0x00, // version
0x00,
0x00,
0x00,
0x00, // total length: written in later
};
gltfOutStream.write(glbHeader, 12);
// binary glTF 2.0 has a sub-header for each of the JSON and BIN chunks
const char glb2JsonHeader[] = {
0x00,
0x00,
0x00,
0x00, // chunk length: written in later
'J',
'S',
'O',
'N', // chunk type: 0x4E4F534A aka JSON
};
gltfOutStream.write(glb2JsonHeader, 8);
}
{
std::vector<std::string> extensionsUsed, extensionsRequired;
if (options.useKHRMatUnlit) {
extensionsUsed.push_back(KHR_MATERIALS_CMN_UNLIT);
}
if (!gltf->lights.ptrs.empty()) {
extensionsUsed.push_back(KHR_LIGHTS_PUNCTUAL);
}
if (options.draco.enabled) {
extensionsUsed.push_back(KHR_DRACO_MESH_COMPRESSION);
extensionsRequired.push_back(KHR_DRACO_MESH_COMPRESSION);
}
json glTFJson{{"asset", {{"generator", "FBX2glTF v" + FBX2GLTF_VERSION}, {"version", "2.0"}}},
{"scene", rootScene.ix}};
if (!extensionsUsed.empty()) {
glTFJson["extensionsUsed"] = extensionsUsed;
}
if (!extensionsRequired.empty()) {
glTFJson["extensionsRequired"] = extensionsRequired;
}
gltf->serializeHolders(glTFJson);
gltfOutStream << glTFJson.dump(options.outputBinary ? 0 : 4);
}
if (options.outputBinary) {
uint32_t jsonLength = (uint32_t)gltfOutStream.tellp() - 20;
// the binary body must begin on a 4-aligned address, so pad json with spaces if necessary
while ((jsonLength % 4) != 0) {
gltfOutStream.put(' ');
jsonLength++;
}
uint32_t binHeader = (uint32_t)gltfOutStream.tellp();
// binary glTF 2.0 has a sub-header for each of the JSON and BIN chunks
const char glb2BinaryHeader[] = {
0x00,
0x00,
0x00,
0x00, // chunk length: written in later
'B',
'I',
'N',
0x00, // chunk type: 0x004E4942 aka BIN
};
gltfOutStream.write(glb2BinaryHeader, 8);
// append binary buffer directly to .glb file
size_t binaryLength = gltf->binary->size();
gltfOutStream.write((const char*)&(*gltf->binary)[0], binaryLength);
while ((binaryLength % 4) != 0) {
gltfOutStream.put('\0');
binaryLength++;
}
uint32_t totalLength = to_uint32(gltfOutStream.tellp());
// seek back to sub-header for json chunk
gltfOutStream.seekp(8);
// write total length, little-endian
gltfOutStream.put((totalLength >> 0) & 0xFF);
gltfOutStream.put((totalLength >> 8) & 0xFF);
gltfOutStream.put((totalLength >> 16) & 0xFF);
gltfOutStream.put((totalLength >> 24) & 0xFF);
// write JSON length, little-endian
gltfOutStream.put((jsonLength >> 0) & 0xFF);
gltfOutStream.put((jsonLength >> 8) & 0xFF);
gltfOutStream.put((jsonLength >> 16) & 0xFF);
gltfOutStream.put((jsonLength >> 24) & 0xFF);
// seek back to the gltf 2.0 binary chunk header
gltfOutStream.seekp(binHeader);
// write total length, little-endian
gltfOutStream.put((binaryLength >> 0) & 0xFF);
gltfOutStream.put((binaryLength >> 8) & 0xFF);
gltfOutStream.put((binaryLength >> 16) & 0xFF);
gltfOutStream.put((binaryLength >> 24) & 0xFF);
// be tidy and return write pointer to end-of-file
gltfOutStream.seekp(0, std::ios::end);
}
return new ModelData(gltf->binary);
}

185
src/gltf/Raw2Gltf.hpp Normal file
View File

@ -0,0 +1,185 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <memory>
#include <string>
// This can be a macro under Windows, confusing Draco
#undef ERROR
#include <draco/compression/encode.h>
#include "FBX2glTF.h"
#include "raw/RawModel.hpp"
const std::string KHR_DRACO_MESH_COMPRESSION = "KHR_draco_mesh_compression";
const std::string KHR_MATERIALS_CMN_UNLIT = "KHR_materials_unlit";
const std::string KHR_LIGHTS_PUNCTUAL = "KHR_lights_punctual";
const std::string extBufferFilename = "buffer.bin";
struct ComponentType {
// OpenGL Datatype enums
enum GL_DataType {
GL_BYTE = 5120,
GL_UNSIGNED_BYTE,
GL_SHORT,
GL_UNSIGNED_SHORT,
GL_INT,
GL_UNSIGNED_INT,
GL_FLOAT
};
const GL_DataType glType;
const unsigned int size;
};
const ComponentType CT_USHORT = {ComponentType::GL_UNSIGNED_SHORT, 2};
const ComponentType CT_UINT = {ComponentType::GL_UNSIGNED_INT, 4};
const ComponentType CT_FLOAT = {ComponentType::GL_FLOAT, 4};
// Map our low-level data types for glTF output
struct GLType {
GLType(const ComponentType& componentType, unsigned int count, const std::string dataType)
: componentType(componentType), count(count), dataType(dataType) {}
unsigned int byteStride() const {
return componentType.size * count;
}
void write(uint8_t* buf, const float scalar) const {
*((float*)buf) = scalar;
}
void write(uint8_t* buf, const uint32_t scalar) const {
switch (componentType.size) {
case 1:
*buf = (uint8_t)scalar;
break;
case 2:
*((uint16_t*)buf) = (uint16_t)scalar;
break;
case 4:
*((uint32_t*)buf) = scalar;
break;
}
}
template <class T, int d>
void write(uint8_t* buf, const mathfu::Vector<T, d>& vector) const {
for (int ii = 0; ii < d; ii++) {
((T*)buf)[ii] = vector(ii);
}
}
template <class T, int d>
void write(uint8_t* buf, const mathfu::Matrix<T, d>& matrix) const {
// three matrix types require special alignment considerations that we don't handle
// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#data-alignment
assert(!(sizeof(T) == 1 && d == 2));
assert(!(sizeof(T) == 1 && d == 3));
assert(!(sizeof(T) == 2 && d == 2));
for (int col = 0; col < d; col++) {
for (int row = 0; row < d; row++) {
// glTF matrices are column-major
((T*)buf)[col * d + row] = matrix(row, col);
}
}
}
template <class T>
void write(uint8_t* buf, const mathfu::Quaternion<T>& quaternion) const {
for (int ii = 0; ii < 3; ii++) {
((T*)buf)[ii] = quaternion.vector()(ii);
}
((T*)buf)[3] = quaternion.scalar();
}
const ComponentType componentType;
const uint8_t count;
const std::string dataType;
};
const GLType GLT_FLOAT = {CT_FLOAT, 1, "SCALAR"};
const GLType GLT_USHORT = {CT_USHORT, 1, "SCALAR"};
const GLType GLT_UINT = {CT_UINT, 1, "SCALAR"};
const GLType GLT_VEC2F = {CT_FLOAT, 2, "VEC2"};
const GLType GLT_VEC3F = {CT_FLOAT, 3, "VEC3"};
const GLType GLT_VEC4F = {CT_FLOAT, 4, "VEC4"};
const GLType GLT_VEC4I = {CT_USHORT, 4, "VEC4"};
const GLType GLT_MAT2F = {CT_USHORT, 4, "MAT2"};
const GLType GLT_MAT3F = {CT_USHORT, 9, "MAT3"};
const GLType GLT_MAT4F = {CT_FLOAT, 16, "MAT4"};
const GLType GLT_QUATF = {CT_FLOAT, 4, "VEC4"};
/**
* The base of any indexed glTF entity.
*/
struct Holdable {
uint32_t ix = UINT_MAX;
virtual json serialize() const = 0;
};
template <class T>
struct AttributeDefinition {
const std::string gltfName;
const T RawVertex::*rawAttributeIx;
const GLType glType;
const draco::GeometryAttribute::Type dracoAttribute;
const draco::DataType dracoComponentType;
AttributeDefinition(
const std::string gltfName,
const T RawVertex::*rawAttributeIx,
const GLType& _glType,
const draco::GeometryAttribute::Type dracoAttribute,
const draco::DataType dracoComponentType)
: gltfName(gltfName),
rawAttributeIx(rawAttributeIx),
glType(_glType),
dracoAttribute(dracoAttribute),
dracoComponentType(dracoComponentType) {}
AttributeDefinition(
const std::string gltfName,
const T RawVertex::*rawAttributeIx,
const GLType& _glType)
: gltfName(gltfName),
rawAttributeIx(rawAttributeIx),
glType(_glType),
dracoAttribute(draco::GeometryAttribute::INVALID),
dracoComponentType(draco::DataType::DT_INVALID) {}
};
struct AccessorData;
struct AnimationData;
struct BufferData;
struct BufferViewData;
struct CameraData;
struct GLTFData;
struct ImageData;
struct MaterialData;
struct MeshData;
struct NodeData;
struct PrimitiveData;
struct SamplerData;
struct SceneData;
struct SkinData;
struct TextureData;
struct ModelData {
explicit ModelData(std::shared_ptr<const std::vector<uint8_t>> const& _binary)
: binary(_binary) {}
std::shared_ptr<const std::vector<uint8_t>> const binary;
};
ModelData* Raw2Gltf(
std::ofstream& gltfOutStream,
const std::string& outputFolder,
const RawModel& raw,
const GltfOptions& options);

226
src/gltf/TextureBuilder.cpp Normal file
View File

@ -0,0 +1,226 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "TextureBuilder.hpp"
#include <stb_image.h>
#include <stb_image_write.h>
#include <utils/File_Utils.hpp>
#include <utils/Image_Utils.hpp>
#include <utils/String_Utils.hpp>
#include <gltf/properties/ImageData.hpp>
#include <gltf/properties/TextureData.hpp>
// keep track of some texture data as we load them
struct TexInfo {
explicit TexInfo(int rawTexIx) : rawTexIx(rawTexIx) {}
const int rawTexIx;
int width{};
int height{};
int channels{};
uint8_t* pixels{};
};
std::shared_ptr<TextureData> TextureBuilder::combine(
const std::vector<int>& ixVec,
const std::string& tag,
const pixel_merger& computePixel,
bool includeAlphaChannel) {
const std::string key = texIndicesKey(ixVec, tag);
auto iter = textureByIndicesKey.find(key);
if (iter != textureByIndicesKey.end()) {
return iter->second;
}
int width = -1, height = -1;
std::string mergedFilename = tag;
std::vector<TexInfo> texes{};
for (const int rawTexIx : ixVec) {
TexInfo info(rawTexIx);
if (rawTexIx >= 0) {
const RawTexture& rawTex = raw.GetTexture(rawTexIx);
const std::string& fileLoc = rawTex.fileLocation;
const std::string& name = FileUtils::GetFileBase(FileUtils::GetFileName(fileLoc));
if (!fileLoc.empty()) {
info.pixels = stbi_load(fileLoc.c_str(), &info.width, &info.height, &info.channels, 0);
if (!info.pixels) {
fmt::printf("Warning: merge texture [%d](%s) could not be loaded.\n", rawTexIx, name);
} else {
if (width < 0) {
width = info.width;
height = info.height;
} else if (width != info.width || height != info.height) {
fmt::printf(
"Warning: texture %s (%d, %d) can't be merged with previous texture(s) of dimension (%d, %d)\n",
name,
info.width,
info.height,
width,
height);
// this is bad enough that we abort the whole merge
return nullptr;
}
mergedFilename += "_" + name;
}
}
}
texes.push_back(info);
}
// at the moment, the best choice of filename is also the best choice of name
const std::string mergedName = mergedFilename;
if (width < 0) {
// no textures to merge; bail
return nullptr;
}
// TODO: which channel combinations make sense in input files?
// write 3 or 4 channels depending on whether or not we need transparency
int channels = includeAlphaChannel ? 4 : 3;
std::vector<uint8_t> mergedPixels(static_cast<size_t>(channels * width * height));
for (int xx = 0; xx < width; xx++) {
for (int yy = 0; yy < height; yy++) {
std::vector<pixel> pixels(texes.size());
std::vector<const pixel*> pixelPointers(texes.size(), nullptr);
for (int jj = 0; jj < texes.size(); jj++) {
const TexInfo& tex = texes[jj];
// each texture's structure will depend on its channel count
int ii = tex.channels * (xx + yy * width);
int kk = 0;
if (tex.pixels != nullptr) {
for (; kk < tex.channels; kk++) {
pixels[jj][kk] = tex.pixels[ii++] / 255.0f;
}
}
for (; kk < pixels[jj].size(); kk++) {
pixels[jj][kk] = 1.0f;
}
pixelPointers[jj] = &pixels[jj];
}
const pixel merged = computePixel(pixelPointers);
int ii = channels * (xx + yy * width);
for (int jj = 0; jj < channels; jj++) {
mergedPixels[ii + jj] = static_cast<uint8_t>(fmax(0, fmin(255.0f, merged[jj] * 255.0f)));
}
}
}
// write a .png iff we need transparency in the destination texture
bool png = includeAlphaChannel;
std::vector<char> imgBuffer;
int res;
if (png) {
res = stbi_write_png_to_func(
WriteToVectorContext,
&imgBuffer,
width,
height,
channels,
mergedPixels.data(),
width * channels);
} else {
res = stbi_write_jpg_to_func(
WriteToVectorContext, &imgBuffer, width, height, channels, mergedPixels.data(), 80);
}
if (!res) {
fmt::printf("Warning: failed to generate merge texture '%s'.\n", mergedFilename);
return nullptr;
}
ImageData* image;
if (options.outputBinary) {
const auto bufferView =
gltf.AddRawBufferView(*gltf.defaultBuffer, imgBuffer.data(), to_uint32(imgBuffer.size()));
image = new ImageData(mergedName, *bufferView, png ? "image/png" : "image/jpeg");
} else {
const std::string imageFilename = mergedFilename + (png ? ".png" : ".jpg");
const std::string imagePath = outputFolder + imageFilename;
FILE* fp = fopen(imagePath.c_str(), "wb");
if (fp == nullptr) {
fmt::printf("Warning:: Couldn't write file '%s' for writing.\n", imagePath);
return nullptr;
}
if (fwrite(imgBuffer.data(), imgBuffer.size(), 1, fp) != 1) {
fmt::printf(
"Warning: Failed to write %lu bytes to file '%s'.\n", imgBuffer.size(), imagePath);
fclose(fp);
return nullptr;
}
fclose(fp);
if (verboseOutput) {
fmt::printf("Wrote %lu bytes to texture '%s'.\n", imgBuffer.size(), imagePath);
}
image = new ImageData(mergedName, imageFilename);
}
std::shared_ptr<TextureData> texDat = gltf.textures.hold(
new TextureData(mergedName, *gltf.defaultSampler, *gltf.images.hold(image)));
textureByIndicesKey.insert(std::make_pair(key, texDat));
return texDat;
}
/** Create a new TextureData for the given RawTexture index, or return a previously created one. */
std::shared_ptr<TextureData> TextureBuilder::simple(int rawTexIndex, const std::string& tag) {
const std::string key = texIndicesKey({rawTexIndex}, tag);
auto iter = textureByIndicesKey.find(key);
if (iter != textureByIndicesKey.end()) {
return iter->second;
}
const RawTexture& rawTexture = raw.GetTexture(rawTexIndex);
const std::string textureName = FileUtils::GetFileBase(rawTexture.name);
const std::string relativeFilename = FileUtils::GetFileName(rawTexture.fileLocation);
ImageData* image = nullptr;
if (options.outputBinary) {
auto bufferView = gltf.AddBufferViewForFile(*gltf.defaultBuffer, rawTexture.fileLocation);
if (bufferView) {
const auto& suffix = FileUtils::GetFileSuffix(rawTexture.fileLocation);
std::string mimeType;
if (suffix) {
mimeType = ImageUtils::suffixToMimeType(suffix.value());
} else {
mimeType = "image/jpeg";
fmt::printf(
"Warning: Can't deduce mime type of texture '%s'; using %s.\n",
rawTexture.fileLocation,
mimeType);
}
image = new ImageData(relativeFilename, *bufferView, mimeType);
}
} else if (!relativeFilename.empty()) {
image = new ImageData(relativeFilename, relativeFilename);
std::string outputPath = outputFolder + "/" + relativeFilename;
if (FileUtils::CopyFile(rawTexture.fileLocation, outputPath, true)) {
if (verboseOutput) {
fmt::printf("Copied texture '%s' to output folder: %s\n", textureName, outputPath);
}
} else {
// no point commenting further on read/write error; CopyFile() does enough of that, and we
// certainly want to to add an image struct to the glTF JSON, with the correct relative path
// reference, even if the copy failed.
}
}
if (!image) {
// fallback is tiny transparent PNG
image = new ImageData(
textureName,
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==");
}
std::shared_ptr<TextureData> texDat = gltf.textures.hold(
new TextureData(textureName, *gltf.defaultSampler, *gltf.images.hold(image)));
textureByIndicesKey.insert(std::make_pair(key, texDat));
return texDat;
}

View File

@ -0,0 +1,77 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <functional>
#include "FBX2glTF.h"
#include <gltf/properties/ImageData.hpp>
#include "GltfModel.hpp"
class TextureBuilder {
public:
using pixel = std::array<float, 4>; // pixel components are floats in [0, 1]
using pixel_merger = std::function<pixel(const std::vector<const pixel*>)>;
TextureBuilder(
const RawModel& raw,
const GltfOptions& options,
const std::string& outputFolder,
GltfModel& gltf)
: raw(raw), options(options), outputFolder(outputFolder), gltf(gltf) {}
~TextureBuilder() {}
std::shared_ptr<TextureData> combine(
const std::vector<int>& ixVec,
const std::string& tag,
const pixel_merger& mergeFunction,
bool transparency);
std::shared_ptr<TextureData> simple(int rawTexIndex, const std::string& tag);
static std::string texIndicesKey(const std::vector<int>& ixVec, const std::string& tag) {
std::string result = tag;
for (int ix : ixVec) {
result += "_" + std::to_string(ix);
}
return result;
};
static std::string describeChannel(int channels) {
switch (channels) {
case 1:
return "G";
case 2:
return "GA";
case 3:
return "RGB";
case 4:
return "RGBA";
default:
return fmt::format("?%d?", channels);
}
};
static void WriteToVectorContext(void* context, void* data, int size) {
auto* vec = static_cast<std::vector<uint8_t>*>(context);
for (int ii = 0; ii < size; ii++) {
vec->push_back(((uint8_t*)data)[ii]);
}
}
private:
const RawModel& raw;
const GltfOptions& options;
const std::string outputFolder;
GltfModel& gltf;
std::map<std::string, std::shared_ptr<TextureData>> textureByIndicesKey;
};

View File

@ -0,0 +1,40 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "AccessorData.hpp"
#include "BufferViewData.hpp"
AccessorData::AccessorData(const BufferViewData& bufferView, GLType type, std::string name)
: Holdable(),
bufferView(bufferView.ix),
type(std::move(type)),
byteOffset(0),
count(0),
name(name) {}
AccessorData::AccessorData(GLType type)
: Holdable(), bufferView(-1), type(std::move(type)), byteOffset(0), count(0) {}
json AccessorData::serialize() const {
json result{
{"componentType", type.componentType.glType}, {"type", type.dataType}, {"count", count}};
if (bufferView >= 0) {
result["bufferView"] = bufferView;
result["byteOffset"] = byteOffset;
}
if (!min.empty()) {
result["min"] = min;
}
if (!max.empty()) {
result["max"] = max;
}
if (name.length() > 0) {
result["name"] = name;
}
return result;
}

View File

@ -0,0 +1,45 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include "gltf/Raw2Gltf.hpp"
struct AccessorData : Holdable {
AccessorData(const BufferViewData& bufferView, GLType type, std::string name);
explicit AccessorData(GLType type);
json serialize() const override;
template <class T>
void appendAsBinaryArray(const std::vector<T>& in, std::vector<uint8_t>& out) {
const unsigned int stride = type.byteStride();
const size_t offset = out.size();
const size_t count = in.size();
this->count = (unsigned int)count;
out.resize(offset + count * stride);
for (int ii = 0; ii < count; ii++) {
type.write(&out[offset + ii * stride], in[ii]);
}
}
unsigned int byteLength() const {
return type.byteStride() * count;
}
const int bufferView;
const GLType type;
unsigned int byteOffset;
unsigned int count;
std::vector<float> min;
std::vector<float> max;
std::string name;
};

View File

@ -0,0 +1,54 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "AnimationData.hpp"
#include <utility>
#include "AccessorData.hpp"
#include "NodeData.hpp"
AnimationData::AnimationData(std::string name, const AccessorData& timeAccessor)
: Holdable(), name(std::move(name)), timeAccessor(timeAccessor.ix) {}
// assumption: 1-to-1 relationship between channels and samplers; this is a simplification on what
// glTF can express, but it means we can rely on samplerIx == channelIx throughout an animation
void AnimationData::AddNodeChannel(
const NodeData& node,
const AccessorData& accessor,
std::string path) {
assert(channels.size() == samplers.size());
uint32_t ix = to_uint32(channels.size());
channels.emplace_back(channel_t(ix, node, std::move(path)));
samplers.emplace_back(sampler_t(timeAccessor, accessor.ix));
}
json AnimationData::serialize() const {
return {{"name", name}, {"channels", channels}, {"samplers", samplers}};
}
AnimationData::channel_t::channel_t(uint32_t ix, const NodeData& node, std::string path)
: ix(ix), node(node.ix), path(std::move(path)) {}
AnimationData::sampler_t::sampler_t(uint32_t time, uint32_t output) : time(time), output(output) {}
void to_json(json& j, const AnimationData::channel_t& data) {
j = json{{"sampler", data.ix},
{
"target",
{{"node", data.node}, {"path", data.path}},
}};
}
void to_json(json& j, const AnimationData::sampler_t& data) {
j = json{
{"input", data.time},
{"interpolation", "LINEAR"},
{"output", data.output},
};
}

View File

@ -0,0 +1,44 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include "gltf/Raw2Gltf.hpp"
struct AnimationData : Holdable {
AnimationData(std::string name, const AccessorData& timeAccessor);
// assumption: 1-to-1 relationship between channels and samplers; this is a simplification on what
// glTF can express, but it means we can rely on samplerIx == channelIx throughout an animation
void AddNodeChannel(const NodeData& node, const AccessorData& accessor, std::string path);
json serialize() const override;
struct channel_t {
channel_t(uint32_t _ix, const NodeData& node, std::string path);
const uint32_t ix;
const uint32_t node;
const std::string path;
};
struct sampler_t {
sampler_t(uint32_t time, uint32_t output);
const uint32_t time;
const uint32_t output;
};
const std::string name;
const uint32_t timeAccessor;
std::vector<channel_t> channels;
std::vector<sampler_t> samplers;
};
void to_json(json& j, const AnimationData::channel_t& data);
void to_json(json& j, const AnimationData::sampler_t& data);

View File

@ -0,0 +1,33 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#include <cppcodec/base64_default_rfc4648.hpp>
#include "BufferData.hpp"
BufferData::BufferData(const std::shared_ptr<const std::vector<uint8_t>>& binData)
: Holdable(), isGlb(true), binData(binData) {}
BufferData::BufferData(
std::string uri,
const std::shared_ptr<const std::vector<uint8_t>>& binData,
bool isEmbedded)
: Holdable(), isGlb(false), uri(isEmbedded ? "" : std::move(uri)), binData(binData) {}
json BufferData::serialize() const {
json result{{"byteLength", binData->size()}};
if (!isGlb) {
if (!uri.empty()) {
result["uri"] = uri;
} else {
std::string encoded = base64::encode(*binData);
result["uri"] = "data:application/octet-stream;base64," + encoded;
}
}
return result;
}

View File

@ -0,0 +1,26 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include "gltf/Raw2Gltf.hpp"
struct BufferData : Holdable {
explicit BufferData(const std::shared_ptr<const std::vector<uint8_t>>& binData);
BufferData(
std::string uri,
const std::shared_ptr<const std::vector<uint8_t>>& binData,
bool isEmbedded = false);
json serialize() const override;
const bool isGlb;
const std::string uri;
const std::shared_ptr<const std::vector<uint8_t>> binData; // TODO this is just weird
};

View File

@ -0,0 +1,24 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "BufferViewData.hpp"
#include "BufferData.hpp"
BufferViewData::BufferViewData(
const BufferData& _buffer,
const size_t _byteOffset,
const GL_ArrayType _target)
: Holdable(), buffer(_buffer.ix), byteOffset((unsigned int)_byteOffset), target(_target) {}
json BufferViewData::serialize() const {
json result{{"buffer", buffer}, {"byteLength", byteLength}, {"byteOffset", byteOffset}};
if (target != GL_ARRAY_NONE) {
result["target"] = target;
}
return result;
}

View File

@ -0,0 +1,29 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include "gltf/Raw2Gltf.hpp"
struct BufferViewData : Holdable {
enum GL_ArrayType {
GL_ARRAY_NONE = 0, // no GL buffer is being set
GL_ARRAY_BUFFER = 34962,
GL_ELEMENT_ARRAY_BUFFER = 34963
};
BufferViewData(const BufferData& _buffer, const size_t _byteOffset, const GL_ArrayType _target);
json serialize() const override;
const unsigned int buffer;
const unsigned int byteOffset;
const GL_ArrayType target;
unsigned int byteLength = 0;
};

View File

@ -0,0 +1,29 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "CameraData.hpp"
CameraData::CameraData()
: Holdable(), aspectRatio(0.0f), yfov(0.0f), xmag(0.0f), ymag(0.0f), znear(0.0f), zfar(0.0f) {}
json CameraData::serialize() const {
json result{
{"name", name},
{"type", type},
};
json subResult{{"znear", znear}, {"zfar", zfar}};
if (type == "perspective") {
subResult["aspectRatio"] = aspectRatio;
subResult["yfov"] = yfov;
} else {
subResult["xmag"] = xmag;
subResult["ymag"] = ymag;
}
result[type] = subResult;
return result;
}

View File

@ -0,0 +1,26 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include "gltf/Raw2Gltf.hpp"
// TODO: this class needs some work
struct CameraData : Holdable {
CameraData();
json serialize() const override;
std::string name;
std::string type;
float aspectRatio;
float yfov;
float xmag;
float ymag;
float znear;
float zfar;
};

View File

@ -0,0 +1,26 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "ImageData.hpp"
#include <utility>
#include "BufferViewData.hpp"
ImageData::ImageData(std::string name, std::string uri)
: Holdable(), name(std::move(name)), uri(std::move(uri)), bufferView(-1) {}
ImageData::ImageData(std::string name, const BufferViewData& bufferView, std::string mimeType)
: Holdable(), name(std::move(name)), bufferView(bufferView.ix), mimeType(std::move(mimeType)) {}
json ImageData::serialize() const {
if (bufferView < 0) {
return {{"name", name}, {"uri", uri}};
}
return {{"name", name}, {"bufferView", bufferView}, {"mimeType", mimeType}};
}

View File

@ -0,0 +1,23 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include "gltf/Raw2Gltf.hpp"
struct ImageData : Holdable {
ImageData(std::string name, std::string uri);
ImageData(std::string name, const BufferViewData& bufferView, std::string mimeType);
json serialize() const override;
const std::string name;
const std::string uri; // non-empty in gltf mode
const int32_t bufferView; // non-negative in glb mode
const std::string mimeType;
};

View File

@ -0,0 +1,47 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "LightData.hpp"
LightData::LightData(
std::string name,
Type type,
Vec3f color,
float intensity,
float innerConeAngle,
float outerConeAngle)
: Holdable(),
type(type),
color(color),
intensity(intensity),
innerConeAngle(innerConeAngle),
outerConeAngle(outerConeAngle) {}
json LightData::serialize() const {
json result{{"name", name}, {"color", toStdVec(color)}, {"intensity", intensity}};
switch (type) {
case Directional:
result["type"] = "directional";
break;
case Point:
result["type"] = "point";
break;
case Spot:
result["type"] = "spot";
json spotJson;
if (innerConeAngle != 0) {
spotJson["innerConeAngle"] = innerConeAngle;
}
if (outerConeAngle != M_PI_4) {
spotJson["outerConeAngle"] = outerConeAngle;
}
result["spot"] = spotJson;
break;
}
return result;
}

View File

@ -0,0 +1,36 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include "gltf/Raw2Gltf.hpp"
struct LightData : Holdable {
enum Type {
Directional,
Point,
Spot,
};
LightData(
std::string name,
Type type,
Vec3f color,
float intensity,
float innerConeAngle,
float outerConeAngle);
json serialize() const override;
const std::string name;
const Type type;
const Vec3f color;
const float intensity;
const float innerConeAngle;
const float outerConeAngle;
};

View File

@ -0,0 +1,133 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#include "MaterialData.hpp"
#include "TextureData.hpp"
// TODO: retrieve & pass in correct UV set from FBX
std::unique_ptr<Tex> Tex::ref(const TextureData* tex, uint32_t texCoord) {
return std::unique_ptr<Tex>{(tex != nullptr) ? new Tex(tex->ix, texCoord) : nullptr};
}
Tex::Tex(uint32_t texRef, uint32_t texCoord) : texRef(texRef), texCoord(texCoord) {}
void to_json(json& j, const Tex& data) {
j = json{{"index", data.texRef}, {"texCoord", data.texCoord}};
}
KHRCmnUnlitMaterial::KHRCmnUnlitMaterial() {}
void to_json(json& j, const KHRCmnUnlitMaterial& d) {
j = json({});
}
inline float clamp(float d, float bottom = 0, float top = 1) {
return std::max(bottom, std::min(top, d));
}
inline Vec3f
clamp(const Vec3f& vec, const Vec3f& bottom = VEC3F_ZERO, const Vec3f& top = VEC3F_ONE) {
return Vec3f::Max(bottom, Vec3f::Min(top, vec));
}
inline Vec4f
clamp(const Vec4f& vec, const Vec4f& bottom = VEC4F_ZERO, const Vec4f& top = VEC4F_ONE) {
return Vec4f::Max(bottom, Vec4f::Min(top, vec));
}
PBRMetallicRoughness::PBRMetallicRoughness(
const TextureData* baseColorTexture,
const TextureData* metRoughTexture,
const Vec4f& baseColorFactor,
float metallic,
float roughness)
: baseColorTexture(Tex::ref(baseColorTexture)),
metRoughTexture(Tex::ref(metRoughTexture)),
baseColorFactor(clamp(baseColorFactor)),
metallic(clamp(metallic)),
roughness(clamp(roughness)) {}
void to_json(json& j, const PBRMetallicRoughness& d) {
j = {};
if (d.baseColorTexture != nullptr) {
j["baseColorTexture"] = *d.baseColorTexture;
}
if (d.baseColorFactor.LengthSquared() > 0) {
j["baseColorFactor"] = toStdVec(d.baseColorFactor);
}
if (d.metRoughTexture != nullptr) {
j["metallicRoughnessTexture"] = *d.metRoughTexture;
// if a texture is provided, throw away metallic/roughness values
j["roughnessFactor"] = 1.0f;
j["metallicFactor"] = 1.0f;
} else {
// without a texture, however, use metallic/roughness as constants
j["metallicFactor"] = d.metallic;
j["roughnessFactor"] = d.roughness;
}
}
MaterialData::MaterialData(
std::string name,
bool isTransparent,
const RawShadingModel shadingModel,
const TextureData* normalTexture,
const TextureData* occlusionTexture,
const TextureData* emissiveTexture,
const Vec3f& emissiveFactor,
std::shared_ptr<KHRCmnUnlitMaterial> const khrCmnConstantMaterial,
std::shared_ptr<PBRMetallicRoughness> const pbrMetallicRoughness)
: Holdable(),
name(std::move(name)),
shadingModel(shadingModel),
isTransparent(isTransparent),
normalTexture(Tex::ref(normalTexture)),
occlusionTexture(Tex::ref(occlusionTexture)),
emissiveTexture(Tex::ref(emissiveTexture)),
emissiveFactor(clamp(emissiveFactor)),
khrCmnConstantMaterial(khrCmnConstantMaterial),
pbrMetallicRoughness(pbrMetallicRoughness) {}
json MaterialData::serialize() const {
json result = {{"name", name},
{"alphaMode", isTransparent ? "BLEND" : "OPAQUE"},
{"extras",
{{"fromFBX",
{{"shadingModel", Describe(shadingModel)},
{"isTruePBR", shadingModel == RAW_SHADING_MODEL_PBR_MET_ROUGH}}}}}};
if (normalTexture != nullptr) {
result["normalTexture"] = *normalTexture;
}
if (occlusionTexture != nullptr) {
result["occlusionTexture"] = *occlusionTexture;
}
if (emissiveTexture != nullptr) {
result["emissiveTexture"] = *emissiveTexture;
}
if (emissiveFactor.LengthSquared() > 0) {
result["emissiveFactor"] = toStdVec(emissiveFactor);
}
if (pbrMetallicRoughness != nullptr) {
result["pbrMetallicRoughness"] = *pbrMetallicRoughness;
}
if (khrCmnConstantMaterial != nullptr) {
json extensions = {};
extensions[KHR_MATERIALS_CMN_UNLIT] = *khrCmnConstantMaterial;
result["extensions"] = extensions;
}
for (const auto& i : userProperties) {
auto& prop_map = result["extras"]["fromFBX"]["userProperties"];
json j = json::parse(i);
for (const auto& k : json::iterator_wrapper(j)) {
prop_map[k.key()] = k.value();
}
}
return result;
}

View File

@ -0,0 +1,72 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <string>
#include "gltf/Raw2Gltf.hpp"
struct Tex {
static std::unique_ptr<Tex> ref(const TextureData* tex, uint32_t texCoord = 0);
explicit Tex(uint32_t texRef, uint32_t texCoord);
const uint32_t texRef;
const uint32_t texCoord;
};
struct KHRCmnUnlitMaterial {
KHRCmnUnlitMaterial();
};
struct PBRMetallicRoughness {
PBRMetallicRoughness(
const TextureData* baseColorTexture,
const TextureData* metRoughTexture,
const Vec4f& baseColorFactor,
float metallic = 0.1f,
float roughness = 0.6f);
std::unique_ptr<Tex> baseColorTexture;
std::unique_ptr<Tex> metRoughTexture;
const Vec4f baseColorFactor;
const float metallic;
const float roughness;
};
struct MaterialData : Holdable {
MaterialData(
std::string name,
bool isTransparent,
RawShadingModel shadingModel,
const TextureData* normalTexture,
const TextureData* occlusionTexture,
const TextureData* emissiveTexture,
const Vec3f& emissiveFactor,
std::shared_ptr<KHRCmnUnlitMaterial> const khrCmnConstantMaterial,
std::shared_ptr<PBRMetallicRoughness> const pbrMetallicRoughness);
json serialize() const override;
const std::string name;
const RawShadingModel shadingModel;
const bool isTransparent;
const std::unique_ptr<const Tex> normalTexture;
const std::unique_ptr<const Tex> occlusionTexture;
const std::unique_ptr<const Tex> emissiveTexture;
const Vec3f emissiveFactor;
const std::shared_ptr<const KHRCmnUnlitMaterial> khrCmnConstantMaterial;
const std::shared_ptr<const PBRMetallicRoughness> pbrMetallicRoughness;
std::vector<std::string> userProperties;
};
void to_json(json& j, const Tex& data);
void to_json(json& j, const KHRCmnUnlitMaterial& d);
void to_json(json& j, const PBRMetallicRoughness& d);

Some files were not shown because too many files have changed in this diff Show More