FBX Importer Generation 3

Basic skin support

Various fixes

- Fixes bind mount id and mesh index
- Fixed duplicate nodes being created
- Prevented leak when instances being freed during re-import.
- Improved camera and light transform import
- skeleton handling and technical debt removal
- ASSIMP: bone nodes were unlinked from bones by this code
- bone_add working can distinguish between armatutes
- Updated transform to be the correct offset
- Added safety for state.root node errors
- Fixed memory leak with leaf bones
- Implemented children re-parenting for mesh template
- import_animation fixes to basic skeleton data
- Adds some more debug messages
- Fixed Godot import segfault
- Fix build failing on mono
- Clear resources we use which are no longer required after import
- Fixed bone duplication issue
- Working skeleton_bone_map which can lookup armatures properly now.
- Fixed stack being used up when mesh swapped & Fixed bone ID

Additional notes:
We use a mesh template which is a fake node to instance the initial
mesh nodes . This is to ensure the entire tree can be built.

We replace mesh node templates with the real mesh after the
skeleton is available, since this makes it ensure that the fully
built skeleton exists with all bones, all nodes, etc.

The bone stack is a stack which pops when it finds bones,
this overcomes duplicate bones with the same names.

FBX has lots of these because animation armature has bone names like bone001
and another armature will also have bone001
Fixed errors in node path assignment

Simple explanation:
- Every mesh uses a node from the stack
- Node stack was empties before completed
- Every time node not found, stack must be rebuilt to maintain correct armature order :)

Additional fixes:
- Fixes destructor in assimp
- Implements aiNode* mArmature in bone data
- Implements aiNode* mParent in bone data
- Fixes parent ID on bones.

Implemented skeleton assignment in generate_mesh_indicies

This is the only place we can safely do a lookup for the skeleton for the mesh.h
I used a pointer reference so we can pass this back out, since the skeleton assignment happens inside the function.

Added mesh re-parenting to the armature node this is a permanent feature and must be enforced, just like GLTF2 specification.

Fixed import_animation spawning tracks per skin
This commit is contained in:
RevoluPowered 2019-11-05 17:54:34 +00:00
parent 245c99175c
commit 0bd877780f
22 changed files with 1385 additions and 908 deletions

File diff suppressed because it is too large Load diff

View file

@ -50,6 +50,7 @@
#include <assimp/DefaultLogger.hpp>
#include <assimp/LogStream.hpp>
#include <assimp/Logger.hpp>
#include <map>
#include "import_state.h"
#include "import_utils.h"
@ -72,7 +73,6 @@ public:
class EditorSceneImporterAssimp : public EditorSceneImporter {
private:
GDCLASS(EditorSceneImporterAssimp, EditorSceneImporter);
const String ASSIMP_FBX_KEY = "_$AssimpFbx$";
struct AssetImportAnimation {
enum Interpolation {
@ -88,40 +88,32 @@ private:
float weight;
};
struct SkeletonHole { //nodes may be part of the skeleton by used by vertex
String name;
String parent;
Transform pose;
const aiNode *node;
};
Ref<Mesh> _generate_mesh_from_surface_indices(ImportState &state, const Vector<int> &p_surface_indices,
const aiNode *assimp_node, Ref<Skin> &skin,
Skeleton *&skeleton_assigned);
void _calc_tangent_from_mesh(const aiMesh *ai_mesh, int i, int tri_index, int index, PoolColorArray::Write &w);
void _set_texture_mapping_mode(aiTextureMapMode *map_mode, Ref<Texture> texture);
Ref<Mesh> _generate_mesh_from_surface_indices(ImportState &state, const Vector<int> &p_surface_indices, const aiNode *assimp_node, Skeleton *p_skeleton = NULL);
// utility for node creation
void attach_new_node(ImportState &state, Spatial *new_node, const aiNode *node, Node *parent_node, String Name, Transform &transform);
// simple object creation functions
void create_light(ImportState &state, RecursiveState &recursive_state);
void create_camera(ImportState &state, RecursiveState &recursive_state);
void create_bone(ImportState &state, RecursiveState &recursive_state);
Spatial *create_light(ImportState &state,
const String &node_name,
Transform &look_at_transform);
Spatial *create_camera(
ImportState &state,
const String &node_name,
Transform &look_at_transform);
// non recursive - linear so must not use recursive arguments
void create_mesh(ImportState &state, const aiNode *assimp_node, const String &node_name, Node *current_node, Node *parent_node, Transform node_transform);
MeshInstance *create_mesh(ImportState &state, const aiNode *assimp_node, const String &node_name, Node *active_node, Transform node_transform);
// recursive node generator
void _generate_node(ImportState &state, Skeleton *skeleton, const aiNode *assimp_node, Node *parent_node);
// runs after _generate_node as it must then use pre-created godot skeleton.
void generate_mesh_phase_from_skeletal_mesh(ImportState &state);
void _insert_animation_track(ImportState &scene, const aiAnimation *assimp_anim, int p_track, int p_bake_fps, Ref<Animation> animation, float ticks_per_second, Skeleton *p_skeleton, const NodePath &p_path, const String &p_name);
void _generate_node(ImportState &state, const aiNode *assimp_node);
void _insert_animation_track(ImportState &scene, const aiAnimation *assimp_anim, int track_id,
int anim_fps, Ref<Animation> animation, float ticks_per_second,
Skeleton *skeleton, const NodePath &node_path,
const String &node_name, aiBone *track_bone);
void _import_animation(ImportState &state, int p_animation_index, int p_bake_fps);
Node *get_node_by_name(ImportState &state, String name);
aiBone *get_bone_from_stack(ImportState &state, aiString name);
Spatial *_generate_scene(const String &p_path, aiScene *scene, const uint32_t p_flags, int p_bake_fps, const int32_t p_max_bone_weights);
String _assimp_anim_string_to_string(const aiString &p_string) const;
String _assimp_raw_string_to_string(const aiString &p_string) const;
float _get_fbx_fps(int32_t time_mode, const aiScene *p_scene);
template <class T>
T _interpolate_track(const Vector<float> &p_times, const Vector<T> &p_values, float p_time, AssetImportAnimation::Interpolation p_interp);
void _register_project_setting_import(const String generic, const String import_setting_string, const Vector<String> &exts, List<String> *r_extensions, const bool p_enabled) const;
@ -148,6 +140,10 @@ public:
virtual uint32_t get_import_flags() const;
virtual Node *import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, List<String> *r_missing_deps, Error *r_err = NULL);
Ref<Image> load_image(ImportState &state, const aiScene *p_scene, String p_path);
static void RegenerateBoneStack(ImportState &state);
void RegenerateBoneStack(ImportState &state, aiMesh *mesh);
};
#endif
#endif

View file

@ -52,28 +52,42 @@
namespace AssimpImporter {
/** Import state is for global scene import data
* This makes the code simpler and contains useful lookups.
*/
* This makes the code simpler and contains useful lookups.
*/
struct ImportState {
String path;
Spatial *root;
const aiScene *assimp_scene;
uint32_t max_bone_weights;
Spatial *root;
Map<String, Ref<Mesh> > mesh_cache;
Map<int, Ref<Material> > material_cache;
Map<String, int> light_cache;
Map<String, int> camera_cache;
//Vector<Skeleton *> skeletons;
Map<Skeleton *, const Spatial *> armature_skeletons; // maps skeletons based on their armature nodes.
Map<const aiBone *, Skeleton *> bone_to_skeleton_lookup; // maps bones back into their skeleton
// very useful for when you need to ask assimp for the bone mesh
Map<String, Node *> node_map;
Map<const aiNode *, const Node *> assimp_node_map;
Map<const aiNode *, Node *> assimp_node_map;
Map<String, Ref<Image> > path_to_image_cache;
bool fbx; //for some reason assimp does some things different for FBX
// Generation 3 - determinisitic iteration
// to lower potential recursion errors
List<const aiNode *> nodes;
Map<const aiNode *, Spatial *> flat_node_map;
AnimationPlayer *animation_player;
// Generation 3 - deterministic armatures
// list of armature nodes - flat and simple to parse
// assimp node, node in godot
List<aiNode *> armature_nodes;
Map<const aiNode *, Skeleton *> armature_skeletons;
Map<aiBone *, Skeleton *> skeleton_bone_map;
// Generation 3 - deterministic bone handling
// bones from the stack are popped when found
// this means we can detect
// what bones are for other armatures
List<aiBone *> bone_stack;
};
struct AssimpImageData {
@ -86,14 +100,15 @@ struct AssimpImageData {
* This makes the code easier to handle too and add extra arguments without breaking things
*/
struct RecursiveState {
RecursiveState() {} // do not construct :)
RecursiveState(
Transform &_node_transform,
Skeleton *_skeleton,
Spatial *_new_node,
const String &_node_name,
const aiNode *_assimp_node,
String &_node_name,
aiNode *_assimp_node,
Node *_parent_node,
const aiBone *_bone) :
aiBone *_bone) :
node_transform(_node_transform),
skeleton(_skeleton),
new_node(_new_node),
@ -102,13 +117,13 @@ struct RecursiveState {
parent_node(_parent_node),
bone(_bone) {}
Transform &node_transform;
Skeleton *skeleton;
Spatial *new_node;
const String &node_name;
const aiNode *assimp_node;
Node *parent_node;
const aiBone *bone;
Transform node_transform;
Skeleton *skeleton = NULL;
Spatial *new_node = NULL;
String node_name;
aiNode *assimp_node = NULL;
Node *parent_node = NULL;
aiBone *bone = NULL;
};
} // namespace AssimpImporter

View file

@ -85,7 +85,7 @@ void BaseImporter::UpdateImporterScale( Importer* pImp )
double activeScale = importerScale * fileScale;
// Set active scaling
pImp->SetPropertyFloat( AI_CONFIG_APP_SCALE_KEY, activeScale);
pImp->SetPropertyFloat( AI_CONFIG_APP_SCALE_KEY, static_cast<float>( activeScale) );
ASSIMP_LOG_DEBUG_F("UpdateImporterScale scale set: %f", activeScale );
}

View file

@ -61,83 +61,66 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
using namespace Assimp;
// maximum path length
// XXX http://insanecoding.blogspot.com/2007/11/pathmax-simply-isnt.html
#ifdef PATH_MAX
# define PATHLIMIT PATH_MAX
#else
# define PATHLIMIT 4096
#ifdef _WIN32
static std::wstring Utf8ToWide(const char* in)
{
int size = MultiByteToWideChar(CP_UTF8, 0, in, -1, nullptr, 0);
// size includes terminating null; std::wstring adds null automatically
std::wstring out(static_cast<size_t>(size) - 1, L'\0');
MultiByteToWideChar(CP_UTF8, 0, in, -1, &out[0], size);
return out;
}
static std::string WideToUtf8(const wchar_t* in)
{
int size = WideCharToMultiByte(CP_UTF8, 0, in, -1, nullptr, 0, nullptr, nullptr);
// size includes terminating null; std::string adds null automatically
std::string out(static_cast<size_t>(size) - 1, '\0');
WideCharToMultiByte(CP_UTF8, 0, in, -1, &out[0], size, nullptr, nullptr);
return out;
}
#endif
// ------------------------------------------------------------------------------------------------
// Tests for the existence of a file at the given path.
bool DefaultIOSystem::Exists( const char* pFile) const
bool DefaultIOSystem::Exists(const char* pFile) const
{
#ifdef _WIN32
wchar_t fileName16[PATHLIMIT];
#ifndef WindowsStore
bool isUnicode = IsTextUnicode(pFile, static_cast<int>(strlen(pFile)), NULL) != 0;
if (isUnicode) {
MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED, pFile, -1, fileName16, PATHLIMIT);
struct __stat64 filestat;
if (0 != _wstat64(fileName16, &filestat)) {
return false;
}
} else {
#endif
FILE* file = ::fopen(pFile, "rb");
if (!file)
return false;
::fclose(file);
#ifndef WindowsStore
struct __stat64 filestat;
if (_wstat64(Utf8ToWide(pFile).c_str(), &filestat) != 0) {
return false;
}
#endif
#else
FILE* file = ::fopen( pFile, "rb");
if( !file)
FILE* file = ::fopen(pFile, "rb");
if (!file)
return false;
::fclose( file);
::fclose(file);
#endif
return true;
}
// ------------------------------------------------------------------------------------------------
// Open a new file with a given path.
IOStream* DefaultIOSystem::Open( const char* strFile, const char* strMode)
IOStream* DefaultIOSystem::Open(const char* strFile, const char* strMode)
{
ai_assert(NULL != strFile);
ai_assert(NULL != strMode);
ai_assert(strFile != nullptr);
ai_assert(strMode != nullptr);
FILE* file;
#ifdef _WIN32
wchar_t fileName16[PATHLIMIT];
#ifndef WindowsStore
bool isUnicode = IsTextUnicode(strFile, static_cast<int>(strlen(strFile)), NULL) != 0;
if (isUnicode) {
MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED, strFile, -1, fileName16, PATHLIMIT);
std::string mode8(strMode);
file = ::_wfopen(fileName16, std::wstring(mode8.begin(), mode8.end()).c_str());
} else {
#endif
file = ::fopen(strFile, strMode);
#ifndef WindowsStore
}
#endif
file = ::_wfopen(Utf8ToWide(strFile).c_str(), Utf8ToWide(strMode).c_str());
#else
file = ::fopen(strFile, strMode);
#endif
if (nullptr == file)
if (!file)
return nullptr;
return new DefaultIOStream(file, (std::string) strFile);
return new DefaultIOStream(file, strFile);
}
// ------------------------------------------------------------------------------------------------
// Closes the given file and releases all resources associated with it.
void DefaultIOSystem::Close( IOStream* pFile)
void DefaultIOSystem::Close(IOStream* pFile)
{
delete pFile;
}
@ -155,78 +138,56 @@ char DefaultIOSystem::getOsSeparator() const
// ------------------------------------------------------------------------------------------------
// IOSystem default implementation (ComparePaths isn't a pure virtual function)
bool IOSystem::ComparePaths (const char* one, const char* second) const
bool IOSystem::ComparePaths(const char* one, const char* second) const
{
return !ASSIMP_stricmp(one,second);
return !ASSIMP_stricmp(one, second);
}
// ------------------------------------------------------------------------------------------------
// Convert a relative path into an absolute path
inline static void MakeAbsolutePath (const char* in, char* _out)
inline static std::string MakeAbsolutePath(const char* in)
{
ai_assert(in && _out);
#if defined( _MSC_VER ) || defined( __MINGW32__ )
#ifndef WindowsStore
bool isUnicode = IsTextUnicode(in, static_cast<int>(strlen(in)), NULL) != 0;
if (isUnicode) {
wchar_t out16[PATHLIMIT];
wchar_t in16[PATHLIMIT];
MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED, in, -1, out16, PATHLIMIT);
wchar_t* ret = ::_wfullpath(out16, in16, PATHLIMIT);
if (ret) {
WideCharToMultiByte(CP_UTF8, MB_PRECOMPOSED, out16, -1, _out, PATHLIMIT, nullptr, nullptr);
}
if (!ret) {
// preserve the input path, maybe someone else is able to fix
// the path before it is accessed (e.g. our file system filter)
ASSIMP_LOG_WARN_F("Invalid path: ", std::string(in));
strcpy(_out, in);
}
} else {
#endif
char* ret = :: _fullpath(_out, in, PATHLIMIT);
if (!ret) {
// preserve the input path, maybe someone else is able to fix
// the path before it is accessed (e.g. our file system filter)
ASSIMP_LOG_WARN_F("Invalid path: ", std::string(in));
strcpy(_out, in);
}
#ifndef WindowsStore
ai_assert(in);
std::string out;
#ifdef _WIN32
wchar_t* ret = ::_wfullpath(nullptr, Utf8ToWide(in).c_str(), 0);
if (ret) {
out = WideToUtf8(ret);
free(ret);
}
#else
char* ret = realpath(in, nullptr);
if (ret) {
out = ret;
free(ret);
}
#endif
#else
// use realpath
char* ret = realpath(in, _out);
if(!ret) {
if (!ret) {
// preserve the input path, maybe someone else is able to fix
// the path before it is accessed (e.g. our file system filter)
ASSIMP_LOG_WARN_F("Invalid path: ", std::string(in));
strcpy(_out,in);
out = in;
}
#endif
return out;
}
// ------------------------------------------------------------------------------------------------
// DefaultIOSystem's more specialized implementation
bool DefaultIOSystem::ComparePaths (const char* one, const char* second) const
bool DefaultIOSystem::ComparePaths(const char* one, const char* second) const
{
// chances are quite good both paths are formatted identically,
// so we can hopefully return here already
if( !ASSIMP_stricmp(one,second) )
if (!ASSIMP_stricmp(one, second))
return true;
char temp1[PATHLIMIT];
char temp2[PATHLIMIT];
std::string temp1 = MakeAbsolutePath(one);
std::string temp2 = MakeAbsolutePath(second);
MakeAbsolutePath (one, temp1);
MakeAbsolutePath (second, temp2);
return !ASSIMP_stricmp(temp1,temp2);
return !ASSIMP_stricmp(temp1, temp2);
}
// ------------------------------------------------------------------------------------------------
std::string DefaultIOSystem::fileName( const std::string &path )
std::string DefaultIOSystem::fileName(const std::string& path)
{
std::string ret = path;
std::size_t last = ret.find_last_of("\\/");
@ -235,16 +196,16 @@ std::string DefaultIOSystem::fileName( const std::string &path )
}
// ------------------------------------------------------------------------------------------------
std::string DefaultIOSystem::completeBaseName( const std::string &path )
std::string DefaultIOSystem::completeBaseName(const std::string& path)
{
std::string ret = fileName(path);
std::size_t pos = ret.find_last_of('.');
if(pos != ret.npos) ret = ret.substr(0, pos);
if (pos != std::string::npos) ret = ret.substr(0, pos);
return ret;
}
// ------------------------------------------------------------------------------------------------
std::string DefaultIOSystem::absolutePath( const std::string &path )
std::string DefaultIOSystem::absolutePath(const std::string& path)
{
std::string ret = path;
std::size_t last = ret.find_last_of("\\/");
@ -253,5 +214,3 @@ std::string DefaultIOSystem::absolutePath( const std::string &path )
}
// ------------------------------------------------------------------------------------------------
#undef PATHLIMIT

View file

@ -315,34 +315,6 @@ const aiExportDataBlob* Exporter::ExportToBlob( const aiScene* pScene, const cha
return pimpl->blob;
}
// ------------------------------------------------------------------------------------------------
bool IsVerboseFormat(const aiMesh* mesh) {
// avoid slow vector<bool> specialization
std::vector<unsigned int> seen(mesh->mNumVertices,0);
for(unsigned int i = 0; i < mesh->mNumFaces; ++i) {
const aiFace& f = mesh->mFaces[i];
for(unsigned int j = 0; j < f.mNumIndices; ++j) {
if(++seen[f.mIndices[j]] == 2) {
// found a duplicate index
return false;
}
}
}
return true;
}
// ------------------------------------------------------------------------------------------------
bool IsVerboseFormat(const aiScene* pScene) {
for(unsigned int i = 0; i < pScene->mNumMeshes; ++i) {
if(!IsVerboseFormat(pScene->mMeshes[i])) {
return false;
}
}
return true;
}
// ------------------------------------------------------------------------------------------------
aiReturn Exporter::Export( const aiScene* pScene, const char* pFormatId, const char* pPath,
unsigned int pPreprocessing, const ExportProperties* pProperties) {
@ -352,7 +324,7 @@ aiReturn Exporter::Export( const aiScene* pScene, const char* pFormatId, const c
// format. They will likely not be aware that there is a flag in the scene to indicate
// this, however. To avoid surprises and bug reports, we check for duplicates in
// meshes upfront.
const bool is_verbose_format = !(pScene->mFlags & AI_SCENE_FLAGS_NON_VERBOSE_FORMAT) || IsVerboseFormat(pScene);
const bool is_verbose_format = !(pScene->mFlags & AI_SCENE_FLAGS_NON_VERBOSE_FORMAT) || MakeVerboseFormatProcess::IsVerboseFormat(pScene);
pimpl->mProgressHandler->UpdateFileWrite(0, 4);
@ -472,7 +444,10 @@ aiReturn Exporter::Export( const aiScene* pScene, const char* pFormatId, const c
}
ExportProperties emptyProperties; // Never pass NULL ExportProperties so Exporters don't have to worry.
exp.mExportFunction(pPath,pimpl->mIOSystem.get(),scenecopy.get(), pProperties ? pProperties : &emptyProperties);
ExportProperties* pProp = pProperties ? (ExportProperties*)pProperties : &emptyProperties;
pProp->SetPropertyBool("bJoinIdenticalVertices", must_join_again);
exp.mExportFunction(pPath,pimpl->mIOSystem.get(),scenecopy.get(), pProp);
exp.mExportFunction(pPath,pimpl->mIOSystem.get(),scenecopy.get(), pProp);
pimpl->mProgressHandler->UpdateFileWrite(4, 4);
} catch (DeadlyExportError& err) {

View file

@ -1091,6 +1091,35 @@ void SceneCombiner::Copy( aiMesh** _dest, const aiMesh* src ) {
aiFace& f = dest->mFaces[i];
GetArrayCopy(f.mIndices,f.mNumIndices);
}
// make a deep copy of all blend shapes
CopyPtrArray(dest->mAnimMeshes, dest->mAnimMeshes, dest->mNumAnimMeshes);
}
// ------------------------------------------------------------------------------------------------
void SceneCombiner::Copy(aiAnimMesh** _dest, const aiAnimMesh* src) {
if (nullptr == _dest || nullptr == src) {
return;
}
aiAnimMesh* dest = *_dest = new aiAnimMesh();
// get a flat copy
::memcpy(dest, src, sizeof(aiAnimMesh));
// and reallocate all arrays
GetArrayCopy(dest->mVertices, dest->mNumVertices);
GetArrayCopy(dest->mNormals, dest->mNumVertices);
GetArrayCopy(dest->mTangents, dest->mNumVertices);
GetArrayCopy(dest->mBitangents, dest->mNumVertices);
unsigned int n = 0;
while (dest->HasTextureCoords(n))
GetArrayCopy(dest->mTextureCoords[n++], dest->mNumVertices);
n = 0;
while (dest->HasVertexColors(n))
GetArrayCopy(dest->mColors[n++], dest->mNumVertices);
}
// ------------------------------------------------------------------------------------------------

View file

@ -55,6 +55,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "FBXImporter.h"
#include <assimp/StringComparison.h>
#include <assimp/MathFunctions.h>
#include <assimp/scene.h>
@ -67,7 +68,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <sstream>
#include <iomanip>
#include <cstdint>
#include <iostream>
#include <stdlib.h>
namespace Assimp {
namespace FBX {
@ -119,6 +121,46 @@ namespace Assimp {
ConvertGlobalSettings();
TransferDataToScene();
// Now convert all bone positions to the correct mOffsetMatrix
std::vector<aiBone*> bones;
std::vector<aiNode*> nodes;
std::map<aiBone*, aiNode*> bone_stack;
BuildBoneList(out->mRootNode, out->mRootNode, out, bones);
BuildNodeList(out->mRootNode, nodes );
BuildBoneStack(out->mRootNode, out->mRootNode, out, bones, bone_stack, nodes);
std::cout << "Bone stack size: " << bone_stack.size() << std::endl;
for( std::pair<aiBone*, aiNode*> kvp : bone_stack )
{
aiBone *bone = kvp.first;
aiNode *bone_node = kvp.second;
std::cout << "active node lookup: " << bone->mName.C_Str() << std::endl;
// lcl transform grab - done in generate_nodes :)
//bone->mOffsetMatrix = bone_node->mTransformation;
aiNode * armature = GetArmatureRoot(bone_node, bones);
ai_assert(armature);
// set up bone armature id
bone->mArmature = armature;
// set this bone node to be referenced properly
ai_assert(bone_node);
bone->mNode = bone_node;
// apply full hierarchy to transform for basic offset
while( bone_node->mParent )
{
bone->mRestMatrix = bone_node->mTransformation * bone->mRestMatrix;
bone_node = bone_node->mParent;
}
}
// if we didn't read any meshes set the AI_SCENE_FLAGS_INCOMPLETE
// to make sure the scene passes assimp's validation. FBX files
// need not contain geometry (i.e. camera animations, raw armatures).
@ -137,6 +179,167 @@ namespace Assimp {
std::for_each(textures.begin(), textures.end(), Util::delete_fun<aiTexture>());
}
/* Returns the armature root node */
/* This is required to be detected for a bone initially, it will recurse up until it cannot find another
* bone and return the node
* No known failure points. (yet)
*/
aiNode * FBXConverter::GetArmatureRoot(aiNode *bone_node, std::vector<aiBone*> &bone_list)
{
while(bone_node)
{
if(!IsBoneNode(bone_node->mName, bone_list))
{
std::cout << "Found valid armature: " << bone_node->mName.C_Str() << std::endl;
return bone_node;
}
bone_node = bone_node->mParent;
}
std::cout << "can't find armature! node: " << bone_node << std::endl;
return NULL;
}
/* Simple IsBoneNode check if this could be a bone */
bool FBXConverter::IsBoneNode(const aiString &bone_name, std::vector<aiBone*>& bones )
{
for( aiBone *bone : bones)
{
if(bone->mName == bone_name)
{
return true;
}
}
return false;
}
/* Pop this node by name from the stack if found */
/* Used in multiple armature situations with duplicate node / bone names */
/* Known flaw: cannot have nodes with bone names, will be fixed in later release */
/* (serious to be fixed) Known flaw: nodes which have more than one bone could be prematurely dropped from stack */
aiNode* FBXConverter::GetNodeFromStack(const aiString &node_name, std::vector<aiNode*> &nodes)
{
std::vector<aiNode*>::iterator iter;
aiNode *found = NULL;
for( iter = nodes.begin(); iter < nodes.end(); ++iter )
{
aiNode *element = *iter;
ai_assert(element);
// node valid and node name matches
if(element->mName == node_name)
{
found = element;
break;
}
}
if(found != NULL) {
// now pop the element from the node list
nodes.erase(iter);
return found;
}
return NULL;
}
/* Prepare flat node list which can be used for non recursive lookups later */
void FBXConverter::BuildNodeList(aiNode *current_node, std::vector<aiNode *> &nodes)
{
assert(current_node);
for( unsigned int nodeId = 0; nodeId < current_node->mNumChildren; ++nodeId)
{
aiNode *child = current_node->mChildren[nodeId];
assert(child);
nodes.push_back(child);
BuildNodeList(child, nodes);
}
}
/* Reprocess all nodes to calculate bone transforms properly based on the REAL mOffsetMatrix not the local. */
/* Before this would use mesh transforms which is wrong for bone transforms */
/* Before this would work for simple character skeletons but not complex meshes with multiple origins */
/* Source: sketch fab log cutter fbx */
void FBXConverter::BuildBoneList(aiNode *current_node, const aiNode * root_node, const aiScene *scene, std::vector<aiBone*> &bones )
{
assert(scene);
for( unsigned int nodeId = 0; nodeId < current_node->mNumChildren; ++nodeId)
{
aiNode *child = current_node->mChildren[nodeId];
assert(child);
// check for bones
for( unsigned int meshId = 0; meshId < child->mNumMeshes; ++meshId)
{
assert(child->mMeshes);
unsigned int mesh_index = child->mMeshes[meshId];
aiMesh *mesh = scene->mMeshes[ mesh_index ];
assert(mesh);
for( unsigned int boneId = 0; boneId < mesh->mNumBones; ++boneId)
{
aiBone *bone = mesh->mBones[boneId];
ai_assert(bone);
// duplicate meshes exist with the same bones sometimes :)
// so this must be detected
if( std::find(bones.begin(), bones.end(), bone) == bones.end() )
{
// add the element once
bones.push_back(bone);
}
}
// find mesh and get bones
// then do recursive lookup for bones in root node hierarchy
}
BuildBoneList(child, root_node, scene, bones);
}
}
/* A bone stack allows us to have multiple armatures, with the same bone names
* A bone stack allows us also to retrieve bones true transform even with duplicate names :)
*/
void FBXConverter::BuildBoneStack(aiNode *current_node, const aiNode *root_node, const aiScene *scene,
const std::vector<aiBone *> &bones,
std::map<aiBone *, aiNode *> &bone_stack,
std::vector<aiNode*> &node_stack )
{
ai_assert(scene);
ai_assert(root_node);
ai_assert(!node_stack.empty());
for( aiBone * bone : bones)
{
ai_assert(bone);
aiNode* node = GetNodeFromStack(bone->mName, node_stack);
if(node == NULL)
{
node_stack.clear();
BuildNodeList(out->mRootNode, node_stack );
std::cout << "Resetting bone stack: null element " << bone->mName.C_Str() << std::endl;
node = GetNodeFromStack(bone->mName, node_stack);
if(!node) {
std::cout << "serious import issue armature failed to be detected?" << std::endl;
continue;
}
}
std::cout << "Successfully added bone to stack and have valid armature: " << bone->mName.C_Str() << std::endl;
bone_stack.insert(std::pair<aiBone*, aiNode*>(bone, node));
}
}
void FBXConverter::ConvertRootNode() {
out->mRootNode = new aiNode();
std::string unique_name;
@ -144,7 +347,7 @@ namespace Assimp {
out->mRootNode->mName.Set(unique_name);
// root has ID 0
ConvertNodes(0L, *out->mRootNode);
ConvertNodes(0L, out->mRootNode, out->mRootNode);
}
static std::string getAncestorBaseName(const aiNode* node)
@ -178,8 +381,11 @@ namespace Assimp {
GetUniqueName(original_name, unique_name);
return unique_name;
}
void FBXConverter::ConvertNodes(uint64_t id, aiNode& parent, const aiMatrix4x4& parent_transform) {
/// todo: pre-build node hierarchy
/// todo: get bone from stack
/// todo: make map of aiBone* to aiNode*
/// then update convert clusters to the new format
void FBXConverter::ConvertNodes(uint64_t id, aiNode *parent, aiNode *root_node) {
const std::vector<const Connection*>& conns = doc.GetConnectionsByDestinationSequenced(id, "Model");
std::vector<aiNode*> nodes;
@ -190,62 +396,69 @@ namespace Assimp {
try {
for (const Connection* con : conns) {
// ignore object-property links
if (con->PropertyName().length()) {
continue;
// really important we document why this is ignored.
FBXImporter::LogInfo("ignoring property link - no docs on why this is ignored");
continue; //?
}
// convert connection source object into Object base class
const Object* const object = con->SourceObject();
if (nullptr == object) {
FBXImporter::LogWarn("failed to convert source object for Model link");
FBXImporter::LogError("failed to convert source object for Model link");
continue;
}
// FBX Model::Cube, Model::Bone001, etc elements
// This detects if we can cast the object into this model structure.
const Model* const model = dynamic_cast<const Model*>(object);
if (nullptr != model) {
nodes_chain.clear();
post_nodes_chain.clear();
aiMatrix4x4 new_abs_transform = parent_transform;
std::string unique_name = MakeUniqueNodeName(model, parent);
aiMatrix4x4 new_abs_transform = parent->mTransformation;
std::string node_name = FixNodeName(model->Name());
// even though there is only a single input node, the design of
// assimp (or rather: the complicated transformation chain that
// is employed by fbx) means that we may need multiple aiNode's
// to represent a fbx node's transformation.
const bool need_additional_node = GenerateTransformationNodeChain(*model, unique_name, nodes_chain, post_nodes_chain);
// generate node transforms - this includes pivot data
// if need_additional_node is true then you t
const bool need_additional_node = GenerateTransformationNodeChain(*model, node_name, nodes_chain, post_nodes_chain);
// assert that for the current node we must have at least a single transform
ai_assert(nodes_chain.size());
if (need_additional_node) {
nodes_chain.push_back(new aiNode(unique_name));
nodes_chain.push_back(new aiNode(node_name));
}
//setup metadata on newest node
SetupNodeMetadata(*model, *nodes_chain.back());
// link all nodes in a row
aiNode* last_parent = &parent;
for (aiNode* prenode : nodes_chain) {
ai_assert(prenode);
aiNode* last_parent = parent;
for (aiNode* child : nodes_chain) {
ai_assert(child);
if (last_parent != &parent) {
if (last_parent != parent) {
last_parent->mNumChildren = 1;
last_parent->mChildren = new aiNode*[1];
last_parent->mChildren[0] = prenode;
last_parent->mChildren[0] = child;
}
prenode->mParent = last_parent;
last_parent = prenode;
child->mParent = last_parent;
last_parent = child;
new_abs_transform *= prenode->mTransformation;
new_abs_transform *= child->mTransformation;
}
// attach geometry
ConvertModel(*model, *nodes_chain.back(), new_abs_transform);
ConvertModel(*model, nodes_chain.back(), root_node, new_abs_transform);
// check if there will be any child nodes
const std::vector<const Connection*>& child_conns
@ -257,7 +470,7 @@ namespace Assimp {
for (aiNode* postnode : post_nodes_chain) {
ai_assert(postnode);
if (last_parent != &parent) {
if (last_parent != parent) {
last_parent->mNumChildren = 1;
last_parent->mChildren = new aiNode*[1];
last_parent->mChildren[0] = postnode;
@ -279,15 +492,15 @@ namespace Assimp {
);
}
// attach sub-nodes (if any)
ConvertNodes(model->ID(), *last_parent, new_abs_transform);
// recursion call - child nodes
ConvertNodes(model->ID(), last_parent, root_node);
if (doc.Settings().readLights) {
ConvertLights(*model, unique_name);
ConvertLights(*model, node_name);
}
if (doc.Settings().readCameras) {
ConvertCameras(*model, unique_name);
ConvertCameras(*model, node_name);
}
nodes.push_back(nodes_chain.front());
@ -296,10 +509,10 @@ namespace Assimp {
}
if (nodes.size()) {
parent.mChildren = new aiNode*[nodes.size()]();
parent.mNumChildren = static_cast<unsigned int>(nodes.size());
parent->mChildren = new aiNode*[nodes.size()]();
parent->mNumChildren = static_cast<unsigned int>(nodes.size());
std::swap_ranges(nodes.begin(), nodes.end(), parent.mChildren);
std::swap_ranges(nodes.begin(), nodes.end(), parent->mChildren);
}
}
catch (std::exception&) {
@ -553,7 +766,7 @@ namespace Assimp {
return;
}
const float angle_epsilon = 1e-6f;
const float angle_epsilon = Math::getEpsilon<float>();
out = aiMatrix4x4();
@ -694,7 +907,7 @@ namespace Assimp {
std::fill_n(chain, static_cast<unsigned int>(TransformationComp_MAXIMUM), aiMatrix4x4());
// generate transformation matrices for all the different transformation components
const float zero_epsilon = 1e-6f;
const float zero_epsilon = Math::getEpsilon<float>();
const aiVector3D all_ones(1.0f, 1.0f, 1.0f);
const aiVector3D& PreRotation = PropertyGet<aiVector3D>(props, "PreRotation", ok);
@ -802,7 +1015,7 @@ namespace Assimp {
// is_complex needs to be consistent with NeedsComplexTransformationChain()
// or the interplay between this code and the animation converter would
// not be guaranteed.
ai_assert(NeedsComplexTransformationChain(model) == ((chainBits & chainMaskComplex) != 0));
//ai_assert(NeedsComplexTransformationChain(model) == ((chainBits & chainMaskComplex) != 0));
// now, if we have more than just Translation, Scaling and Rotation,
// we need to generate a full node chain to accommodate for assimp's
@ -904,7 +1117,8 @@ namespace Assimp {
}
}
void FBXConverter::ConvertModel(const Model& model, aiNode& nd, const aiMatrix4x4& node_global_transform)
void FBXConverter::ConvertModel(const Model &model, aiNode *parent, aiNode *root_node,
const aiMatrix4x4 &absolute_transform)
{
const std::vector<const Geometry*>& geos = model.GetGeometry();
@ -916,11 +1130,12 @@ namespace Assimp {
const MeshGeometry* const mesh = dynamic_cast<const MeshGeometry*>(geo);
const LineGeometry* const line = dynamic_cast<const LineGeometry*>(geo);
if (mesh) {
const std::vector<unsigned int>& indices = ConvertMesh(*mesh, model, node_global_transform, nd);
const std::vector<unsigned int>& indices = ConvertMesh(*mesh, model, parent, root_node,
absolute_transform);
std::copy(indices.begin(), indices.end(), std::back_inserter(meshes));
}
else if (line) {
const std::vector<unsigned int>& indices = ConvertLine(*line, model, node_global_transform, nd);
const std::vector<unsigned int>& indices = ConvertLine(*line, model, parent, root_node);
std::copy(indices.begin(), indices.end(), std::back_inserter(meshes));
}
else {
@ -929,15 +1144,16 @@ namespace Assimp {
}
if (meshes.size()) {
nd.mMeshes = new unsigned int[meshes.size()]();
nd.mNumMeshes = static_cast<unsigned int>(meshes.size());
parent->mMeshes = new unsigned int[meshes.size()]();
parent->mNumMeshes = static_cast<unsigned int>(meshes.size());
std::swap_ranges(meshes.begin(), meshes.end(), nd.mMeshes);
std::swap_ranges(meshes.begin(), meshes.end(), parent->mMeshes);
}
}
std::vector<unsigned int> FBXConverter::ConvertMesh(const MeshGeometry& mesh, const Model& model,
const aiMatrix4x4& node_global_transform, aiNode& nd)
std::vector<unsigned int>
FBXConverter::ConvertMesh(const MeshGeometry &mesh, const Model &model, aiNode *parent, aiNode *root_node,
const aiMatrix4x4 &absolute_transform)
{
std::vector<unsigned int> temp;
@ -961,18 +1177,18 @@ namespace Assimp {
const MatIndexArray::value_type base = mindices[0];
for (MatIndexArray::value_type index : mindices) {
if (index != base) {
return ConvertMeshMultiMaterial(mesh, model, node_global_transform, nd);
return ConvertMeshMultiMaterial(mesh, model, parent, root_node, absolute_transform);
}
}
}
// faster code-path, just copy the data
temp.push_back(ConvertMeshSingleMaterial(mesh, model, node_global_transform, nd));
temp.push_back(ConvertMeshSingleMaterial(mesh, model, absolute_transform, parent, root_node));
return temp;
}
std::vector<unsigned int> FBXConverter::ConvertLine(const LineGeometry& line, const Model& model,
const aiMatrix4x4& node_global_transform, aiNode& nd)
aiNode *parent, aiNode *root_node)
{
std::vector<unsigned int> temp;
@ -983,7 +1199,7 @@ namespace Assimp {
return temp;
}
aiMesh* const out_mesh = SetupEmptyMesh(line, nd);
aiMesh* const out_mesh = SetupEmptyMesh(line, root_node);
out_mesh->mPrimitiveTypes |= aiPrimitiveType_LINE;
// copy vertices
@ -1018,7 +1234,7 @@ namespace Assimp {
return temp;
}
aiMesh* FBXConverter::SetupEmptyMesh(const Geometry& mesh, aiNode& nd)
aiMesh* FBXConverter::SetupEmptyMesh(const Geometry& mesh, aiNode *parent)
{
aiMesh* const out_mesh = new aiMesh();
meshes.push_back(out_mesh);
@ -1035,17 +1251,18 @@ namespace Assimp {
}
else
{
out_mesh->mName = nd.mName;
out_mesh->mName = parent->mName;
}
return out_mesh;
}
unsigned int FBXConverter::ConvertMeshSingleMaterial(const MeshGeometry& mesh, const Model& model,
const aiMatrix4x4& node_global_transform, aiNode& nd)
unsigned int FBXConverter::ConvertMeshSingleMaterial(const MeshGeometry &mesh, const Model &model,
const aiMatrix4x4 &absolute_transform, aiNode *parent,
aiNode *root_node)
{
const MatIndexArray& mindices = mesh.GetMaterialIndices();
aiMesh* const out_mesh = SetupEmptyMesh(mesh, nd);
aiMesh* const out_mesh = SetupEmptyMesh(mesh, parent);
const std::vector<aiVector3D>& vertices = mesh.GetVertices();
const std::vector<unsigned int>& faces = mesh.GetFaceIndexCounts();
@ -1163,7 +1380,8 @@ namespace Assimp {
}
if (doc.Settings().readWeights && mesh.DeformerSkin() != NULL) {
ConvertWeights(out_mesh, model, mesh, node_global_transform, NO_MATERIAL_SEPARATION);
ConvertWeights(out_mesh, model, mesh, absolute_transform, parent, root_node, NO_MATERIAL_SEPARATION,
nullptr);
}
std::vector<aiAnimMesh*> animMeshes;
@ -1208,8 +1426,10 @@ namespace Assimp {
return static_cast<unsigned int>(meshes.size() - 1);
}
std::vector<unsigned int> FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry& mesh, const Model& model,
const aiMatrix4x4& node_global_transform, aiNode& nd)
std::vector<unsigned int>
FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry &mesh, const Model &model, aiNode *parent,
aiNode *root_node,
const aiMatrix4x4 &absolute_transform)
{
const MatIndexArray& mindices = mesh.GetMaterialIndices();
ai_assert(mindices.size());
@ -1220,7 +1440,7 @@ namespace Assimp {
for (MatIndexArray::value_type index : mindices) {
if (had.find(index) == had.end()) {
indices.push_back(ConvertMeshMultiMaterial(mesh, model, index, node_global_transform, nd));
indices.push_back(ConvertMeshMultiMaterial(mesh, model, index, parent, root_node, absolute_transform));
had.insert(index);
}
}
@ -1228,12 +1448,12 @@ namespace Assimp {
return indices;
}
unsigned int FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry& mesh, const Model& model,
MatIndexArray::value_type index,
const aiMatrix4x4& node_global_transform,
aiNode& nd)
unsigned int FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry &mesh, const Model &model,
MatIndexArray::value_type index,
aiNode *parent, aiNode *root_node,
const aiMatrix4x4 &absolute_transform)
{
aiMesh* const out_mesh = SetupEmptyMesh(mesh, nd);
aiMesh* const out_mesh = SetupEmptyMesh(mesh, parent);
const MatIndexArray& mindices = mesh.GetMaterialIndices();
const std::vector<aiVector3D>& vertices = mesh.GetVertices();
@ -1398,7 +1618,7 @@ namespace Assimp {
ConvertMaterialForMesh(out_mesh, model, mesh, index);
if (process_weights) {
ConvertWeights(out_mesh, model, mesh, node_global_transform, index, &reverseMapping);
ConvertWeights(out_mesh, model, mesh, absolute_transform, parent, root_node, index, &reverseMapping);
}
std::vector<aiAnimMesh*> animMeshes;
@ -1448,10 +1668,10 @@ namespace Assimp {
return static_cast<unsigned int>(meshes.size() - 1);
}
void FBXConverter::ConvertWeights(aiMesh* out, const Model& model, const MeshGeometry& geo,
const aiMatrix4x4& node_global_transform,
unsigned int materialIndex,
std::vector<unsigned int>* outputVertStartIndices)
void FBXConverter::ConvertWeights(aiMesh *out, const Model &model, const MeshGeometry &geo,
const aiMatrix4x4 &absolute_transform,
aiNode *parent, aiNode *root_node, unsigned int materialIndex,
std::vector<unsigned int> *outputVertStartIndices)
{
ai_assert(geo.DeformerSkin());
@ -1462,13 +1682,12 @@ namespace Assimp {
const Skin& sk = *geo.DeformerSkin();
std::vector<aiBone*> bones;
bones.reserve(sk.Clusters().size());
const bool no_mat_check = materialIndex == NO_MATERIAL_SEPARATION;
ai_assert(no_mat_check || outputVertStartIndices);
try {
// iterate over the sub deformers
for (const Cluster* cluster : sk.Clusters()) {
ai_assert(cluster);
@ -1482,6 +1701,7 @@ namespace Assimp {
index_out_indices.clear();
out_indices.clear();
// now check if *any* of these weights is contained in the output mesh,
// taking notes so we don't need to do it twice.
for (WeightIndexArray::value_type index : indices) {
@ -1519,68 +1739,107 @@ namespace Assimp {
}
}
}
// if we found at least one, generate the output bones
// XXX this could be heavily simplified by collecting the bone
// data in a single step.
ConvertCluster(bones, model, *cluster, out_indices, index_out_indices,
count_out_indices, node_global_transform);
ConvertCluster(bones, cluster, out_indices, index_out_indices,
count_out_indices, absolute_transform, parent, root_node);
}
bone_map.clear();
}
catch (std::exception&) {
catch (std::exception&e) {
std::for_each(bones.begin(), bones.end(), Util::delete_fun<aiBone>());
throw;
}
if (bones.empty()) {
out->mBones = nullptr;
out->mNumBones = 0;
return;
} else {
out->mBones = new aiBone *[bones.size()]();
out->mNumBones = static_cast<unsigned int>(bones.size());
std::swap_ranges(bones.begin(), bones.end(), out->mBones);
}
out->mBones = new aiBone*[bones.size()]();
out->mNumBones = static_cast<unsigned int>(bones.size());
std::swap_ranges(bones.begin(), bones.end(), out->mBones);
}
void FBXConverter::ConvertCluster(std::vector<aiBone*>& bones, const Model& /*model*/, const Cluster& cl,
std::vector<size_t>& out_indices,
std::vector<size_t>& index_out_indices,
std::vector<size_t>& count_out_indices,
const aiMatrix4x4& node_global_transform)
const aiNode* FBXConverter::GetNodeByName( const aiString& name, aiNode *current_node )
{
aiNode * iter = current_node;
//printf("Child count: %d", iter->mNumChildren);
return iter;
}
aiBone* const bone = new aiBone();
bones.push_back(bone);
void FBXConverter::ConvertCluster(std::vector<aiBone *> &local_mesh_bones, const Cluster *cl,
std::vector<size_t> &out_indices, std::vector<size_t> &index_out_indices,
std::vector<size_t> &count_out_indices, const aiMatrix4x4 &absolute_transform,
aiNode *parent, aiNode *root_node) {
assert(cl); // make sure cluster valid
std::string deformer_name = cl->TargetNode()->Name();
aiString bone_name = aiString(FixNodeName(deformer_name));
bone->mName = FixNodeName(cl.TargetNode()->Name());
aiBone *bone = NULL;
bone->mOffsetMatrix = cl.TransformLink();
bone->mOffsetMatrix.Inverse();
if (bone_map.count(deformer_name)) {
std::cout << "retrieved bone from lookup " << bone_name.C_Str() << ". Deformer: " << deformer_name
<< std::endl;
bone = bone_map[deformer_name];
} else {
std::cout << "created new bone " << bone_name.C_Str() << ". Deformer: " << deformer_name << std::endl;
bone = new aiBone();
bone->mName = bone_name;
bone->mOffsetMatrix = bone->mOffsetMatrix * node_global_transform;
// store local transform link for post processing
bone->mOffsetMatrix = cl->TransformLink();
bone->mOffsetMatrix.Inverse();
bone->mNumWeights = static_cast<unsigned int>(out_indices.size());
aiVertexWeight* cursor = bone->mWeights = new aiVertexWeight[out_indices.size()];
aiMatrix4x4 matrix = (aiMatrix4x4)absolute_transform;
const size_t no_index_sentinel = std::numeric_limits<size_t>::max();
const WeightArray& weights = cl.GetWeights();
bone->mOffsetMatrix = bone->mOffsetMatrix * matrix; // * mesh_offset
const size_t c = index_out_indices.size();
for (size_t i = 0; i < c; ++i) {
const size_t index_index = index_out_indices[i];
if (index_index == no_index_sentinel) {
continue;
//
// Now calculate the aiVertexWeights
//
aiVertexWeight *cursor = nullptr;
bone->mNumWeights = static_cast<unsigned int>(out_indices.size());
cursor = bone->mWeights = new aiVertexWeight[out_indices.size()];
const size_t no_index_sentinel = std::numeric_limits<size_t>::max();
const WeightArray& weights = cl->GetWeights();
const size_t c = index_out_indices.size();
for (size_t i = 0; i < c; ++i) {
const size_t index_index = index_out_indices[i];
if (index_index == no_index_sentinel) {
continue;
}
const size_t cc = count_out_indices[i];
for (size_t j = 0; j < cc; ++j) {
// cursor runs from first element relative to the start
// or relative to the start of the next indexes.
aiVertexWeight& out_weight = *cursor++;
out_weight.mVertexId = static_cast<unsigned int>(out_indices[index_index + j]);
out_weight.mWeight = weights[i];
}
}
const size_t cc = count_out_indices[i];
for (size_t j = 0; j < cc; ++j) {
aiVertexWeight& out_weight = *cursor++;
out_weight.mVertexId = static_cast<unsigned int>(out_indices[index_index + j]);
out_weight.mWeight = weights[i];
}
bone_map.insert(std::pair<const std::string, aiBone *>(deformer_name, bone));
}
std::cout << "bone research: Indicies size: " << out_indices.size() << std::endl;
// lookup must be populated in case something goes wrong
// this also allocates bones to mesh instance outside
local_mesh_bones.push_back(bone);
}
void FBXConverter::ConvertMaterialForMesh(aiMesh* out, const Model& model, const MeshGeometry& geo,
@ -2967,7 +3226,7 @@ void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTa
TransformationCompDefaultValue(comp)
);
const float epsilon = 1e-6f;
const float epsilon = Math::getEpsilon<float>();
return (dyn_val - static_val).SquareLength() < epsilon;
}

View file

@ -76,16 +76,6 @@ namespace Assimp {
namespace FBX {
class Document;
enum class FbxUnit {
cm = 0,
m,
km,
NumUnits,
Undefined
};
/**
* Convert a FBX #Document to #aiScene
* @param out Empty scene to be populated
@ -133,7 +123,7 @@ private:
// ------------------------------------------------------------------------------------------------
// collect and assign child nodes
void ConvertNodes(uint64_t id, aiNode& parent, const aiMatrix4x4& parent_transform = aiMatrix4x4());
void ConvertNodes(uint64_t id, aiNode *parent, aiNode *root_node);
// ------------------------------------------------------------------------------------------------
void ConvertLights(const Model& model, const std::string &orig_name );
@ -189,32 +179,35 @@ private:
void SetupNodeMetadata(const Model& model, aiNode& nd);
// ------------------------------------------------------------------------------------------------
void ConvertModel(const Model& model, aiNode& nd, const aiMatrix4x4& node_global_transform);
void ConvertModel(const Model &model, aiNode *parent, aiNode *root_node,
const aiMatrix4x4 &absolute_transform);
// ------------------------------------------------------------------------------------------------
// MeshGeometry -> aiMesh, return mesh index + 1 or 0 if the conversion failed
std::vector<unsigned int> ConvertMesh(const MeshGeometry& mesh, const Model& model,
const aiMatrix4x4& node_global_transform, aiNode& nd);
std::vector<unsigned int>
ConvertMesh(const MeshGeometry &mesh, const Model &model, aiNode *parent, aiNode *root_node,
const aiMatrix4x4 &absolute_transform);
// ------------------------------------------------------------------------------------------------
std::vector<unsigned int> ConvertLine(const LineGeometry& line, const Model& model,
const aiMatrix4x4& node_global_transform, aiNode& nd);
aiNode *parent, aiNode *root_node);
// ------------------------------------------------------------------------------------------------
aiMesh* SetupEmptyMesh(const Geometry& mesh, aiNode& nd);
aiMesh* SetupEmptyMesh(const Geometry& mesh, aiNode *parent);
// ------------------------------------------------------------------------------------------------
unsigned int ConvertMeshSingleMaterial(const MeshGeometry& mesh, const Model& model,
const aiMatrix4x4& node_global_transform, aiNode& nd);
unsigned int ConvertMeshSingleMaterial(const MeshGeometry &mesh, const Model &model,
const aiMatrix4x4 &absolute_transform, aiNode *parent,
aiNode *root_node);
// ------------------------------------------------------------------------------------------------
std::vector<unsigned int> ConvertMeshMultiMaterial(const MeshGeometry& mesh, const Model& model,
const aiMatrix4x4& node_global_transform, aiNode& nd);
std::vector<unsigned int>
ConvertMeshMultiMaterial(const MeshGeometry &mesh, const Model &model, aiNode *parent, aiNode *root_node,
const aiMatrix4x4 &absolute_transform);
// ------------------------------------------------------------------------------------------------
unsigned int ConvertMeshMultiMaterial(const MeshGeometry& mesh, const Model& model,
MatIndexArray::value_type index,
const aiMatrix4x4& node_global_transform, aiNode& nd);
unsigned int ConvertMeshMultiMaterial(const MeshGeometry &mesh, const Model &model, MatIndexArray::value_type index,
aiNode *parent, aiNode *root_node, const aiMatrix4x4 &absolute_transform);
// ------------------------------------------------------------------------------------------------
static const unsigned int NO_MATERIAL_SEPARATION = /* std::numeric_limits<unsigned int>::max() */
@ -227,17 +220,17 @@ private:
* - outputVertStartIndices is only used when a material index is specified, it gives for
* each output vertex the DOM index it maps to.
*/
void ConvertWeights(aiMesh* out, const Model& model, const MeshGeometry& geo,
const aiMatrix4x4& node_global_transform = aiMatrix4x4(),
unsigned int materialIndex = NO_MATERIAL_SEPARATION,
std::vector<unsigned int>* outputVertStartIndices = NULL);
void ConvertWeights(aiMesh *out, const Model &model, const MeshGeometry &geo, const aiMatrix4x4 &absolute_transform,
aiNode *parent = NULL, aiNode *root_node = NULL,
unsigned int materialIndex = NO_MATERIAL_SEPARATION,
std::vector<unsigned int> *outputVertStartIndices = NULL);
// lookup
static const aiNode* GetNodeByName( const aiString& name, aiNode *current_node );
// ------------------------------------------------------------------------------------------------
void ConvertCluster(std::vector<aiBone*>& bones, const Model& /*model*/, const Cluster& cl,
std::vector<size_t>& out_indices,
std::vector<size_t>& index_out_indices,
std::vector<size_t>& count_out_indices,
const aiMatrix4x4& node_global_transform);
void ConvertCluster(std::vector<aiBone *> &local_mesh_bones, const Cluster *cl,
std::vector<size_t> &out_indices, std::vector<size_t> &index_out_indices,
std::vector<size_t> &count_out_indices, const aiMatrix4x4 &absolute_transform,
aiNode *parent, aiNode *root_node);
// ------------------------------------------------------------------------------------------------
void ConvertMaterialForMesh(aiMesh* out, const Model& model, const MeshGeometry& geo,
@ -462,11 +455,30 @@ private:
using NodeNameCache = std::unordered_map<std::string, unsigned int>;
NodeNameCache mNodeNames;
// Deformer name is not the same as a bone name - it does contain the bone name though :)
// Deformer names in FBX are always unique in an FBX file.
std::map<const std::string, aiBone *> bone_map;
double anim_fps;
aiScene* const out;
const FBX::Document& doc;
FbxUnit mCurrentUnit;
static void BuildBoneList(aiNode *current_node, const aiNode *root_node, const aiScene *scene,
std::vector<aiBone*>& bones);
void BuildBoneStack(aiNode *current_node, const aiNode *root_node, const aiScene *scene,
const std::vector<aiBone *> &bones,
std::map<aiBone *, aiNode *> &bone_stack,
std::vector<aiNode*> &node_stack );
static void BuildNodeList(aiNode *current_node, std::vector<aiNode *> &nodes);
static aiNode *GetNodeFromStack(const aiString &node_name, std::vector<aiNode *> &nodes);
static aiNode *GetArmatureRoot(aiNode *bone_node, std::vector<aiBone*> &bone_list);
static bool IsBoneNode(const aiString &bone_name, std::vector<aiBone *> &bones);
};
}

View file

@ -59,11 +59,7 @@ namespace FBX {
FBXExportProperty::FBXExportProperty(bool v)
: type('C')
, data(1) {
data = {
uint8_t(v)
};
}
, data(1, uint8_t(v)) {}
FBXExportProperty::FBXExportProperty(int16_t v)
: type('Y')

View file

@ -67,6 +67,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <vector>
#include <array>
#include <unordered_set>
#include <numeric>
// RESOURCES:
// https://code.blender.org/2013/08/fbx-binary-file-format-specification/
@ -1005,6 +1006,9 @@ void FBXExporter::WriteObjects ()
object_node.EndProperties(outstream, binary, indent);
object_node.BeginChildren(outstream, binary, indent);
bool bJoinIdenticalVertices = mProperties->GetPropertyBool("bJoinIdenticalVertices", true);
std::vector<std::vector<int32_t>> vVertexIndice;//save vertex_indices as it is needed later
// geometry (aiMesh)
mesh_uids.clear();
indent = 1;
@ -1031,21 +1035,35 @@ void FBXExporter::WriteObjects ()
std::vector<int32_t> vertex_indices;
// map of vertex value to its index in the data vector
std::map<aiVector3D,size_t> index_by_vertex_value;
int32_t index = 0;
for (size_t vi = 0; vi < m->mNumVertices; ++vi) {
aiVector3D vtx = m->mVertices[vi];
auto elem = index_by_vertex_value.find(vtx);
if (elem == index_by_vertex_value.end()) {
vertex_indices.push_back(index);
index_by_vertex_value[vtx] = index;
flattened_vertices.push_back(vtx[0]);
flattened_vertices.push_back(vtx[1]);
flattened_vertices.push_back(vtx[2]);
++index;
} else {
vertex_indices.push_back(int32_t(elem->second));
if(bJoinIdenticalVertices){
int32_t index = 0;
for (size_t vi = 0; vi < m->mNumVertices; ++vi) {
aiVector3D vtx = m->mVertices[vi];
auto elem = index_by_vertex_value.find(vtx);
if (elem == index_by_vertex_value.end()) {
vertex_indices.push_back(index);
index_by_vertex_value[vtx] = index;
flattened_vertices.push_back(vtx[0]);
flattened_vertices.push_back(vtx[1]);
flattened_vertices.push_back(vtx[2]);
++index;
} else {
vertex_indices.push_back(int32_t(elem->second));
}
}
}
else { // do not join vertex, respect the export flag
vertex_indices.resize(m->mNumVertices);
std::iota(vertex_indices.begin(), vertex_indices.end(), 0);
for(unsigned int v = 0; v < m->mNumVertices; ++ v) {
aiVector3D vtx = m->mVertices[v];
flattened_vertices.push_back(vtx.x);
flattened_vertices.push_back(vtx.y);
flattened_vertices.push_back(vtx.z);
}
}
vVertexIndice.push_back(vertex_indices);
FBX::Node::WritePropertyNode(
"Vertices", flattened_vertices, outstream, binary, indent
);
@ -1116,6 +1134,51 @@ void FBXExporter::WriteObjects ()
normals.End(outstream, binary, indent, true);
}
// colors, if any
// TODO only one color channel currently
const int32_t colorChannelIndex = 0;
if (m->HasVertexColors(colorChannelIndex)) {
FBX::Node vertexcolors("LayerElementColor", int32_t(colorChannelIndex));
vertexcolors.Begin(outstream, binary, indent);
vertexcolors.DumpProperties(outstream, binary, indent);
vertexcolors.EndProperties(outstream, binary, indent);
vertexcolors.BeginChildren(outstream, binary, indent);
indent = 3;
FBX::Node::WritePropertyNode(
"Version", int32_t(101), outstream, binary, indent
);
char layerName[8];
sprintf(layerName, "COLOR_%d", colorChannelIndex);
FBX::Node::WritePropertyNode(
"Name", (const char*)layerName, outstream, binary, indent
);
FBX::Node::WritePropertyNode(
"MappingInformationType", "ByPolygonVertex",
outstream, binary, indent
);
FBX::Node::WritePropertyNode(
"ReferenceInformationType", "Direct",
outstream, binary, indent
);
std::vector<double> color_data;
color_data.reserve(4 * polygon_data.size());
for (size_t fi = 0; fi < m->mNumFaces; ++fi) {
const aiFace &f = m->mFaces[fi];
for (size_t pvi = 0; pvi < f.mNumIndices; ++pvi) {
const aiColor4D &c = m->mColors[colorChannelIndex][f.mIndices[pvi]];
color_data.push_back(c.r);
color_data.push_back(c.g);
color_data.push_back(c.b);
color_data.push_back(c.a);
}
}
FBX::Node::WritePropertyNode(
"Colors", color_data, outstream, binary, indent
);
indent = 2;
vertexcolors.End(outstream, binary, indent, true);
}
// uvs, if any
for (size_t uvi = 0; uvi < m->GetNumUVChannels(); ++uvi) {
if (m->mNumUVComponents[uvi] > 2) {
@ -1209,6 +1272,11 @@ void FBXExporter::WriteObjects ()
le.AddChild("Type", "LayerElementNormal");
le.AddChild("TypedIndex", int32_t(0));
layer.AddChild(le);
// TODO only 1 color channel currently
le = FBX::Node("LayerElement");
le.AddChild("Type", "LayerElementColor");
le.AddChild("TypedIndex", int32_t(0));
layer.AddChild(le);
le = FBX::Node("LayerElement");
le.AddChild("Type", "LayerElementMaterial");
le.AddChild("TypedIndex", int32_t(0));
@ -1748,28 +1816,8 @@ void FBXExporter::WriteObjects ()
// connect it
connections.emplace_back("C", "OO", deformer_uid, mesh_uids[mi]);
// we will be indexing by vertex...
// but there might be a different number of "vertices"
// between assimp and our output FBX.
// this code is cut-and-pasted from the geometry section above...
// ideally this should not be so.
// ---
// index of original vertex in vertex data vector
std::vector<int32_t> vertex_indices;
// map of vertex value to its index in the data vector
std::map<aiVector3D,size_t> index_by_vertex_value;
int32_t index = 0;
for (size_t vi = 0; vi < m->mNumVertices; ++vi) {
aiVector3D vtx = m->mVertices[vi];
auto elem = index_by_vertex_value.find(vtx);
if (elem == index_by_vertex_value.end()) {
vertex_indices.push_back(index);
index_by_vertex_value[vtx] = index;
++index;
} else {
vertex_indices.push_back(int32_t(elem->second));
}
}
//computed before
std::vector<int32_t>& vertex_indices = vVertexIndice[mi];
// TODO, FIXME: this won't work if anything is not in the bind pose.
// for now if such a situation is detected, we throw an exception.
@ -2435,7 +2483,7 @@ void FBXExporter::WriteModelNodes(
void FBXExporter::WriteAnimationCurveNode(
StreamWriterLE& outstream,
int64_t uid,
std::string name, // "T", "R", or "S"
const std::string& name, // "T", "R", or "S"
aiVector3D default_value,
std::string property_name, // "Lcl Translation" etc
int64_t layer_uid,

View file

@ -156,7 +156,7 @@ namespace Assimp
void WriteAnimationCurveNode(
StreamWriterLE& outstream,
int64_t uid,
std::string name, // "T", "R", or "S"
const std::string& name, // "T", "R", or "S"
aiVector3D default_value,
std::string property_name, // "Lcl Translation" etc
int64_t animation_layer_uid,

View file

@ -48,26 +48,26 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "FBXImporter.h"
#include "FBXTokenizer.h"
#include "FBXParser.h"
#include "FBXUtil.h"
#include "FBXDocument.h"
#include "FBXConverter.h"
#include "FBXDocument.h"
#include "FBXParser.h"
#include "FBXTokenizer.h"
#include "FBXUtil.h"
#include <assimp/StreamReader.h>
#include <assimp/MemoryIOWrapper.h>
#include <assimp/Importer.hpp>
#include <assimp/StreamReader.h>
#include <assimp/importerdesc.h>
#include <assimp/Importer.hpp>
namespace Assimp {
template<>
const char* LogFunctions<FBXImporter>::Prefix() {
static auto prefix = "FBX: ";
return prefix;
template <>
const char *LogFunctions<FBXImporter>::Prefix() {
static auto prefix = "FBX: ";
return prefix;
}
}
} // namespace Assimp
using namespace Assimp;
using namespace Assimp::Formatter;
@ -76,136 +76,123 @@ using namespace Assimp::FBX;
namespace {
static const aiImporterDesc desc = {
"Autodesk FBX Importer",
"",
"",
"",
aiImporterFlags_SupportTextFlavour,
0,
0,
0,
0,
"fbx"
"Autodesk FBX Importer",
"",
"",
"",
aiImporterFlags_SupportTextFlavour,
0,
0,
0,
0,
"fbx"
};
}
// ------------------------------------------------------------------------------------------------
// Constructor to be privately used by #Importer
FBXImporter::FBXImporter()
{
FBXImporter::FBXImporter() {
}
// ------------------------------------------------------------------------------------------------
// Destructor, private as well
FBXImporter::~FBXImporter()
{
FBXImporter::~FBXImporter() {
}
// ------------------------------------------------------------------------------------------------
// Returns whether the class can handle the format of the given file.
bool FBXImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
{
const std::string& extension = GetExtension(pFile);
if (extension == std::string( desc.mFileExtensions ) ) {
return true;
}
bool FBXImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler, bool checkSig) const {
const std::string &extension = GetExtension(pFile);
if (extension == std::string(desc.mFileExtensions)) {
return true;
}
else if ((!extension.length() || checkSig) && pIOHandler) {
// at least ASCII-FBX files usually have a 'FBX' somewhere in their head
const char* tokens[] = {"fbx"};
return SearchFileHeaderForToken(pIOHandler,pFile,tokens,1);
}
return false;
else if ((!extension.length() || checkSig) && pIOHandler) {
// at least ASCII-FBX files usually have a 'FBX' somewhere in their head
const char *tokens[] = { "fbx" };
return SearchFileHeaderForToken(pIOHandler, pFile, tokens, 1);
}
return false;
}
// ------------------------------------------------------------------------------------------------
// List all extensions handled by this loader
const aiImporterDesc* FBXImporter::GetInfo () const
{
return &desc;
const aiImporterDesc *FBXImporter::GetInfo() const {
return &desc;
}
// ------------------------------------------------------------------------------------------------
// Setup configuration properties for the loader
void FBXImporter::SetupProperties(const Importer* pImp)
{
settings.readAllLayers = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ALL_GEOMETRY_LAYERS, true);
settings.readAllMaterials = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ALL_MATERIALS, false);
settings.readMaterials = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_MATERIALS, true);
settings.readTextures = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_TEXTURES, true);
settings.readCameras = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_CAMERAS, true);
settings.readLights = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_LIGHTS, true);
settings.readAnimations = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ANIMATIONS, true);
settings.strictMode = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_STRICT_MODE, false);
settings.preservePivots = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_PRESERVE_PIVOTS, true);
settings.optimizeEmptyAnimationCurves = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_OPTIMIZE_EMPTY_ANIMATION_CURVES, true);
settings.useLegacyEmbeddedTextureNaming = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_EMBEDDED_TEXTURES_LEGACY_NAMING, false);
settings.removeEmptyBones = pImp->GetPropertyBool(AI_CONFIG_IMPORT_REMOVE_EMPTY_BONES, true);
settings.convertToMeters = pImp->GetPropertyBool(AI_CONFIG_FBX_CONVERT_TO_M, false);
void FBXImporter::SetupProperties(const Importer *pImp) {
settings.readAllLayers = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ALL_GEOMETRY_LAYERS, true);
settings.readAllMaterials = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ALL_MATERIALS, false);
settings.readMaterials = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_MATERIALS, true);
settings.readTextures = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_TEXTURES, true);
settings.readCameras = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_CAMERAS, true);
settings.readLights = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_LIGHTS, true);
settings.readAnimations = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ANIMATIONS, true);
settings.strictMode = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_STRICT_MODE, false);
settings.preservePivots = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_PRESERVE_PIVOTS, true);
settings.optimizeEmptyAnimationCurves = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_OPTIMIZE_EMPTY_ANIMATION_CURVES, true);
settings.useLegacyEmbeddedTextureNaming = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_EMBEDDED_TEXTURES_LEGACY_NAMING, false);
settings.removeEmptyBones = pImp->GetPropertyBool(AI_CONFIG_IMPORT_REMOVE_EMPTY_BONES, true);
settings.convertToMeters = pImp->GetPropertyBool(AI_CONFIG_FBX_CONVERT_TO_M, false);
}
// ------------------------------------------------------------------------------------------------
// Imports the given file into the given scene structure.
void FBXImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler)
{
std::unique_ptr<IOStream> stream(pIOHandler->Open(pFile,"rb"));
if (!stream) {
ThrowException("Could not open file for reading");
}
void FBXImporter::InternReadFile(const std::string &pFile, aiScene *pScene, IOSystem *pIOHandler) {
std::unique_ptr<IOStream> stream(pIOHandler->Open(pFile, "rb"));
if (!stream) {
ThrowException("Could not open file for reading");
}
// read entire file into memory - no streaming for this, fbx
// files can grow large, but the assimp output data structure
// then becomes very large, too. Assimp doesn't support
// streaming for its output data structures so the net win with
// streaming input data would be very low.
std::vector<char> contents;
contents.resize(stream->FileSize()+1);
stream->Read( &*contents.begin(), 1, contents.size()-1 );
contents[ contents.size() - 1 ] = 0;
const char* const begin = &*contents.begin();
// read entire file into memory - no streaming for this, fbx
// files can grow large, but the assimp output data structure
// then becomes very large, too. Assimp doesn't support
// streaming for its output data structures so the net win with
// streaming input data would be very low.
std::vector<char> contents;
contents.resize(stream->FileSize() + 1);
stream->Read(&*contents.begin(), 1, contents.size() - 1);
contents[contents.size() - 1] = 0;
const char *const begin = &*contents.begin();
// broadphase tokenizing pass in which we identify the core
// syntax elements of FBX (brackets, commas, key:value mappings)
TokenList tokens;
try {
// broadphase tokenizing pass in which we identify the core
// syntax elements of FBX (brackets, commas, key:value mappings)
TokenList tokens;
try {
bool is_binary = false;
if (!strncmp(begin,"Kaydara FBX Binary",18)) {
is_binary = true;
TokenizeBinary(tokens,begin,contents.size());
}
else {
Tokenize(tokens,begin);
}
bool is_binary = false;
if (!strncmp(begin, "Kaydara FBX Binary", 18)) {
is_binary = true;
TokenizeBinary(tokens, begin, contents.size());
} else {
Tokenize(tokens, begin);
}
// use this information to construct a very rudimentary
// parse-tree representing the FBX scope structure
Parser parser(tokens, is_binary);
// use this information to construct a very rudimentary
// parse-tree representing the FBX scope structure
Parser parser(tokens, is_binary);
// take the raw parse-tree and convert it to a FBX DOM
Document doc(parser,settings);
// take the raw parse-tree and convert it to a FBX DOM
Document doc(parser, settings);
FbxUnit unit(FbxUnit::cm);
if (settings.convertToMeters) {
unit = FbxUnit::m;
}
// convert the FBX DOM to aiScene
ConvertToAssimpScene(pScene, doc, settings.removeEmptyBones);
// convert the FBX DOM to aiScene
ConvertToAssimpScene(pScene, doc, settings.removeEmptyBones);
// size relative to cm
float size_relative_to_cm = doc.GlobalSettings().UnitScaleFactor();
// size relative to cm
float size_relative_to_cm = doc.GlobalSettings().UnitScaleFactor();
// Set FBX file scale is relative to CM must be converted to M for
// assimp universal format (M)
SetFileScale(size_relative_to_cm * 0.01f);
// Set FBX file scale is relative to CM must be converted to M for
// assimp universal format (M)
SetFileScale( size_relative_to_cm * 0.01f);
std::for_each(tokens.begin(),tokens.end(),Util::delete_fun<Token>());
}
catch(std::exception&) {
std::for_each(tokens.begin(),tokens.end(),Util::delete_fun<Token>());
throw;
}
std::for_each(tokens.begin(), tokens.end(), Util::delete_fun<Token>());
} catch (std::exception &) {
std::for_each(tokens.begin(), tokens.end(), Util::delete_fun<Token>());
throw;
}
}
#endif // !ASSIMP_BUILD_NO_FBX_IMPORTER

View file

@ -610,11 +610,11 @@ void MeshGeometry::ReadVertexDataMaterials(std::vector<int>& materials_out, cons
const std::string& ReferenceInformationType)
{
const size_t face_count = m_faces.size();
if(face_count <= 0)
if( 0 == face_count )
{
return;
}
// materials are handled separately. First of all, they are assigned per-face
// and not per polyvert. Secondly, ReferenceInformationType=IndexToDirect
// has a slightly different meaning for materials.
@ -625,16 +625,14 @@ void MeshGeometry::ReadVertexDataMaterials(std::vector<int>& materials_out, cons
if (materials_out.empty()) {
FBXImporter::LogError(Formatter::format("expected material index, ignoring"));
return;
}
else if (materials_out.size() > 1) {
} else if (materials_out.size() > 1) {
FBXImporter::LogWarn(Formatter::format("expected only a single material index, ignoring all except the first one"));
materials_out.clear();
}
materials_out.resize(m_vertices.size());
std::fill(materials_out.begin(), materials_out.end(), materials_out.at(0));
}
else if (MappingInformationType == "ByPolygon" && ReferenceInformationType == "IndexToDirect") {
} else if (MappingInformationType == "ByPolygon" && ReferenceInformationType == "IndexToDirect") {
materials_out.resize(face_count);
if(materials_out.size() != face_count) {
@ -643,18 +641,16 @@ void MeshGeometry::ReadVertexDataMaterials(std::vector<int>& materials_out, cons
);
return;
}
}
else {
} else {
FBXImporter::LogError(Formatter::format("ignoring material assignments, access type not implemented: ")
<< MappingInformationType << "," << ReferenceInformationType);
}
}
// ------------------------------------------------------------------------------------------------
ShapeGeometry::ShapeGeometry(uint64_t id, const Element& element, const std::string& name, const Document& doc)
: Geometry(id, element, name, doc)
{
const Scope* sc = element.Compound();
if (!sc) {
: Geometry(id, element, name, doc) {
const Scope *sc = element.Compound();
if (nullptr == sc) {
DOMError("failed to read Geometry object (class: Shape), no data scope found");
}
const Element& Indexes = GetRequiredElement(*sc, "Indexes", &element);

View file

@ -431,31 +431,6 @@ int JoinVerticesProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshIndex)
bone->mWeights = new aiVertexWeight[bone->mNumWeights];
memcpy( bone->mWeights, &newWeights[0], bone->mNumWeights * sizeof( aiVertexWeight));
}
else {
/* NOTE:
*
* In the algorithm above we're assuming that there are no vertices
* with a different bone weight setup at the same position. That wouldn't
* make sense, but it is not absolutely impossible. SkeletonMeshBuilder
* for example generates such input data if two skeleton points
* share the same position. Again this doesn't make sense but is
* reality for some model formats (MD5 for example uses these special
* nodes as attachment tags for its weapons).
*
* Then it is possible that a bone has no weights anymore .... as a quick
* workaround, we're just removing these bones. If they're animated,
* model geometry might be modified but at least there's no risk of a crash.
*/
delete bone;
--pMesh->mNumBones;
for (unsigned int n = a; n < pMesh->mNumBones; ++n) {
pMesh->mBones[n] = pMesh->mBones[n+1];
}
--a;
ASSIMP_LOG_WARN("Removing bone -> no weights remaining");
}
}
return pMesh->mNumVertices;
}

View file

@ -224,3 +224,32 @@ bool MakeVerboseFormatProcess::MakeVerboseFormat(aiMesh* pcMesh)
}
return (pcMesh->mNumVertices != iOldNumVertices);
}
// ------------------------------------------------------------------------------------------------
bool IsMeshInVerboseFormat(const aiMesh* mesh) {
// avoid slow vector<bool> specialization
std::vector<unsigned int> seen(mesh->mNumVertices,0);
for(unsigned int i = 0; i < mesh->mNumFaces; ++i) {
const aiFace& f = mesh->mFaces[i];
for(unsigned int j = 0; j < f.mNumIndices; ++j) {
if(++seen[f.mIndices[j]] == 2) {
// found a duplicate index
return false;
}
}
}
return true;
}
// ------------------------------------------------------------------------------------------------
bool MakeVerboseFormatProcess::IsVerboseFormat(const aiScene* pScene) {
for(unsigned int i = 0; i < pScene->mNumMeshes; ++i) {
if(!IsMeshInVerboseFormat(pScene->mMeshes[i])) {
return false;
}
}
return true;
}

View file

@ -94,6 +94,13 @@ public:
* @param pScene The imported data to work at. */
void Execute( aiScene* pScene);
public:
// -------------------------------------------------------------------
/** Checks whether the scene is already in verbose format.
* @param pScene The data to check.
* @return true if the scene is already in verbose format. */
static bool IsVerboseFormat(const aiScene* pScene);
private:

View file

@ -39,22 +39,24 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---------------------------------------------------------------------------
*/
#pragma once
/** @file MathFunctions.h
* @brief Implementation of the math functions (gcd and lcm)
* @brief Implementation of math utility functions.
*
* Copied from BoostWorkaround/math
*/
*/
#include <limits>
namespace Assimp {
namespace Math {
// TODO: use binary GCD for unsigned integers ....
template < typename IntegerType >
IntegerType gcd( IntegerType a, IntegerType b )
{
inline
IntegerType gcd( IntegerType a, IntegerType b ) {
const IntegerType zero = (IntegerType)0;
while ( true )
{
while ( true ) {
if ( a == zero )
return b;
b %= a;
@ -66,12 +68,19 @@ IntegerType gcd( IntegerType a, IntegerType b )
}
template < typename IntegerType >
IntegerType lcm( IntegerType a, IntegerType b )
{
inline
IntegerType lcm( IntegerType a, IntegerType b ) {
const IntegerType t = gcd (a,b);
if (!t)return t;
if (!t)
return t;
return a / t * b;
}
template<class T>
inline
T getEpsilon() {
return std::numeric_limits<T>::epsilon();
}
}
}

View file

@ -65,6 +65,7 @@ struct aiLight;
struct aiMetadata;
struct aiBone;
struct aiMesh;
struct aiAnimMesh;
struct aiAnimation;
struct aiNodeAnim;
@ -363,6 +364,7 @@ public:
static void Copy (aiMesh** dest, const aiMesh* src);
// similar to Copy():
static void Copy (aiAnimMesh** dest, const aiAnimMesh* src);
static void Copy (aiMaterial** dest, const aiMaterial* src);
static void Copy (aiTexture** dest, const aiTexture* src);
static void Copy (aiAnimation** dest, const aiAnimation* src);

View file

@ -113,7 +113,6 @@ struct aiCamera
*/
C_STRUCT aiVector3D mPosition;
/** 'Up' - vector of the camera coordinate system relative to
* the coordinate space defined by the corresponding node.
*
@ -134,7 +133,6 @@ struct aiCamera
*/
C_STRUCT aiVector3D mLookAt;
/** Half horizontal field of view angle, in radians.
*
* The field of view angle is the angle between the center

View file

@ -5,8 +5,6 @@ Open Asset Import Library (assimp)
Copyright (c) 2006-2019, assimp team
All rights reserved.
Redistribution and use of this software in source and binary forms,
@ -53,6 +51,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "matrix4x4.h"
#include "matrix3x3.h"
#include "quaternion.h"
#include "MathFunctions.h"
#include <algorithm>
#include <limits>
@ -420,8 +419,8 @@ inline void aiMatrix4x4t<TReal>::Decompose (aiVector3t<TReal>& pScaling, aiQuate
}
template <typename TReal>
inline void aiMatrix4x4t<TReal>::Decompose(aiVector3t<TReal>& pScaling, aiVector3t<TReal>& pRotation, aiVector3t<TReal>& pPosition) const
{
inline
void aiMatrix4x4t<TReal>::Decompose(aiVector3t<TReal>& pScaling, aiVector3t<TReal>& pRotation, aiVector3t<TReal>& pPosition) const {
ASSIMP_MATRIX4_4_DECOMPOSE_PART;
/*
@ -442,7 +441,7 @@ inline void aiMatrix4x4t<TReal>::Decompose(aiVector3t<TReal>& pScaling, aiVector
*/
// Use a small epsilon to solve floating-point inaccuracies
const TReal epsilon = 10e-3f;
const TReal epsilon = Assimp::Math::getEpsilon<TReal>();
pRotation.y = std::asin(-vCols[0].z);// D. Angle around oY.

View file

@ -51,6 +51,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <assimp/types.h>
#include <assimp/aabb.h>
struct aiNode;
#ifdef __cplusplus
extern "C" {
#endif
@ -264,6 +266,12 @@ struct aiBone {
//! The maximum value for this member is #AI_MAX_BONE_WEIGHTS.
unsigned int mNumWeights;
// The bone armature node - used for skeleton conversion
aiNode* mArmature;
// The bone node in the scene - used for skeleton conversion
aiNode* mNode;
//! The influence weights of this bone, by vertex index.
C_STRUCT aiVertexWeight* mWeights;
@ -280,6 +288,11 @@ struct aiBone {
*/
C_STRUCT aiMatrix4x4 mOffsetMatrix;
/** Matrix used for the global rest transform
* This tells you directly the rest without extending as required in most game engine implementations
* */
C_STRUCT aiMatrix4x4 mRestMatrix;
#ifdef __cplusplus
//! Default constructor
@ -769,7 +782,10 @@ struct aiMesh
// DO NOT REMOVE THIS ADDITIONAL CHECK
if (mNumBones && mBones) {
for( unsigned int a = 0; a < mNumBones; a++) {
delete mBones[a];
if(mBones[a])
{
delete mBones[a];
}
}
delete [] mBones;
}