| /* |
| Open Asset Import Library (assimp) |
| ---------------------------------------------------------------------- |
| |
| Copyright (c) 2006-2017, assimp team |
| |
| All rights reserved. |
| |
| Redistribution and use of this software in source and binary forms, |
| with or without modification, are permitted provided that the |
| following conditions are met: |
| |
| * Redistributions of source code must retain the above |
| copyright notice, this list of conditions and the |
| following disclaimer. |
| |
| * Redistributions in binary form must reproduce the above |
| copyright notice, this list of conditions and the |
| following disclaimer in the documentation and/or other |
| materials provided with the distribution. |
| |
| * Neither the name of the assimp team, nor the names of its |
| contributors may be used to endorse or promote products |
| derived from this software without specific prior |
| written permission of the assimp team. |
| |
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| ---------------------------------------------------------------------- |
| */ |
| |
| /** @file FBXMeshGeometry.cpp |
| * @brief Assimp::FBX::MeshGeometry implementation |
| */ |
| |
| #ifndef ASSIMP_BUILD_NO_FBX_IMPORTER |
| |
| #include <functional> |
| |
| #include "FBXMeshGeometry.h" |
| #include "FBXDocument.h" |
| #include "FBXImporter.h" |
| #include "FBXImportSettings.h" |
| #include "FBXDocumentUtil.h" |
| |
| |
| namespace Assimp { |
| namespace FBX { |
| |
| using namespace Util; |
| |
| // ------------------------------------------------------------------------------------------------ |
| Geometry::Geometry(uint64_t id, const Element& element, const std::string& name, const Document& doc) |
| : Object(id, element,name) |
| , skin() |
| { |
| const std::vector<const Connection*>& conns = doc.GetConnectionsByDestinationSequenced(ID(),"Deformer"); |
| for(const Connection* con : conns) { |
| const Skin* const sk = ProcessSimpleConnection<Skin>(*con, false, "Skin -> Geometry", element); |
| if(sk) { |
| skin = sk; |
| break; |
| } |
| } |
| } |
| |
| |
| // ------------------------------------------------------------------------------------------------ |
| Geometry::~Geometry() |
| { |
| |
| } |
| |
| const Skin* Geometry::DeformerSkin() const { |
| return skin; |
| } |
| |
| |
| // ------------------------------------------------------------------------------------------------ |
| MeshGeometry::MeshGeometry(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) { |
| DOMError("failed to read Geometry object (class: Mesh), no data scope found"); |
| } |
| |
| // must have Mesh elements: |
| const Element& Vertices = GetRequiredElement(*sc,"Vertices",&element); |
| const Element& PolygonVertexIndex = GetRequiredElement(*sc,"PolygonVertexIndex",&element); |
| |
| // optional Mesh elements: |
| const ElementCollection& Layer = sc->GetCollection("Layer"); |
| |
| std::vector<aiVector3D> tempVerts; |
| ParseVectorDataArray(tempVerts,Vertices); |
| |
| if(tempVerts.empty()) { |
| FBXImporter::LogWarn("encountered mesh with no vertices"); |
| return; |
| } |
| |
| std::vector<int> tempFaces; |
| ParseVectorDataArray(tempFaces,PolygonVertexIndex); |
| |
| if(tempFaces.empty()) { |
| FBXImporter::LogWarn("encountered mesh with no faces"); |
| return; |
| } |
| |
| m_vertices.reserve(tempFaces.size()); |
| m_faces.reserve(tempFaces.size() / 3); |
| |
| m_mapping_offsets.resize(tempVerts.size()); |
| m_mapping_counts.resize(tempVerts.size(),0); |
| m_mappings.resize(tempFaces.size()); |
| |
| const size_t vertex_count = tempVerts.size(); |
| |
| // generate output vertices, computing an adjacency table to |
| // preserve the mapping from fbx indices to *this* indexing. |
| unsigned int count = 0; |
| for(int index : tempFaces) { |
| const int absi = index < 0 ? (-index - 1) : index; |
| if(static_cast<size_t>(absi) >= vertex_count) { |
| DOMError("polygon vertex index out of range",&PolygonVertexIndex); |
| } |
| |
| m_vertices.push_back(tempVerts[absi]); |
| ++count; |
| |
| ++m_mapping_counts[absi]; |
| |
| if (index < 0) { |
| m_faces.push_back(count); |
| count = 0; |
| } |
| } |
| |
| unsigned int cursor = 0; |
| for (size_t i = 0, e = tempVerts.size(); i < e; ++i) { |
| m_mapping_offsets[i] = cursor; |
| cursor += m_mapping_counts[i]; |
| |
| m_mapping_counts[i] = 0; |
| } |
| |
| cursor = 0; |
| for(int index : tempFaces) { |
| const int absi = index < 0 ? (-index - 1) : index; |
| m_mappings[m_mapping_offsets[absi] + m_mapping_counts[absi]++] = cursor++; |
| } |
| |
| // if settings.readAllLayers is true: |
| // * read all layers, try to load as many vertex channels as possible |
| // if settings.readAllLayers is false: |
| // * read only the layer with index 0, but warn about any further layers |
| for (ElementMap::const_iterator it = Layer.first; it != Layer.second; ++it) { |
| const TokenList& tokens = (*it).second->Tokens(); |
| |
| const char* err; |
| const int index = ParseTokenAsInt(*tokens[0], err); |
| if(err) { |
| DOMError(err,&element); |
| } |
| |
| if(doc.Settings().readAllLayers || index == 0) { |
| const Scope& layer = GetRequiredScope(*(*it).second); |
| ReadLayer(layer); |
| } |
| else { |
| FBXImporter::LogWarn("ignoring additional geometry layers"); |
| } |
| } |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| MeshGeometry::~MeshGeometry() |
| { |
| |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| const std::vector<aiVector3D>& MeshGeometry::GetVertices() const { |
| return m_vertices; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| const std::vector<aiVector3D>& MeshGeometry::GetNormals() const { |
| return m_normals; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| const std::vector<aiVector3D>& MeshGeometry::GetTangents() const { |
| return m_tangents; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| const std::vector<aiVector3D>& MeshGeometry::GetBinormals() const { |
| return m_binormals; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| const std::vector<unsigned int>& MeshGeometry::GetFaceIndexCounts() const { |
| return m_faces; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| const std::vector<aiVector2D>& MeshGeometry::GetTextureCoords( unsigned int index ) const { |
| static const std::vector<aiVector2D> empty; |
| return index >= AI_MAX_NUMBER_OF_TEXTURECOORDS ? empty : m_uvs[ index ]; |
| } |
| |
| std::string MeshGeometry::GetTextureCoordChannelName( unsigned int index ) const { |
| return index >= AI_MAX_NUMBER_OF_TEXTURECOORDS ? "" : m_uvNames[ index ]; |
| } |
| |
| const std::vector<aiColor4D>& MeshGeometry::GetVertexColors( unsigned int index ) const { |
| static const std::vector<aiColor4D> empty; |
| return index >= AI_MAX_NUMBER_OF_COLOR_SETS ? empty : m_colors[ index ]; |
| } |
| |
| const MatIndexArray& MeshGeometry::GetMaterialIndices() const { |
| return m_materials; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| const unsigned int* MeshGeometry::ToOutputVertexIndex( unsigned int in_index, unsigned int& count ) const { |
| if ( in_index >= m_mapping_counts.size() ) { |
| return NULL; |
| } |
| |
| ai_assert( m_mapping_counts.size() == m_mapping_offsets.size() ); |
| count = m_mapping_counts[ in_index ]; |
| |
| ai_assert( m_mapping_offsets[ in_index ] + count <= m_mappings.size() ); |
| |
| return &m_mappings[ m_mapping_offsets[ in_index ] ]; |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| unsigned int MeshGeometry::FaceForVertexIndex( unsigned int in_index ) const { |
| ai_assert( in_index < m_vertices.size() ); |
| |
| // in the current conversion pattern this will only be needed if |
| // weights are present, so no need to always pre-compute this table |
| if ( m_facesVertexStartIndices.empty() ) { |
| m_facesVertexStartIndices.resize( m_faces.size() + 1, 0 ); |
| |
| std::partial_sum( m_faces.begin(), m_faces.end(), m_facesVertexStartIndices.begin() + 1 ); |
| m_facesVertexStartIndices.pop_back(); |
| } |
| |
| ai_assert( m_facesVertexStartIndices.size() == m_faces.size() ); |
| const std::vector<unsigned int>::iterator it = std::upper_bound( |
| m_facesVertexStartIndices.begin(), |
| m_facesVertexStartIndices.end(), |
| in_index |
| ); |
| |
| return static_cast< unsigned int >( std::distance( m_facesVertexStartIndices.begin(), it - 1 ) ); |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| void MeshGeometry::ReadLayer(const Scope& layer) |
| { |
| const ElementCollection& LayerElement = layer.GetCollection("LayerElement"); |
| for (ElementMap::const_iterator eit = LayerElement.first; eit != LayerElement.second; ++eit) { |
| const Scope& elayer = GetRequiredScope(*(*eit).second); |
| |
| ReadLayerElement(elayer); |
| } |
| } |
| |
| |
| // ------------------------------------------------------------------------------------------------ |
| void MeshGeometry::ReadLayerElement(const Scope& layerElement) |
| { |
| const Element& Type = GetRequiredElement(layerElement,"Type"); |
| const Element& TypedIndex = GetRequiredElement(layerElement,"TypedIndex"); |
| |
| const std::string& type = ParseTokenAsString(GetRequiredToken(Type,0)); |
| const int typedIndex = ParseTokenAsInt(GetRequiredToken(TypedIndex,0)); |
| |
| const Scope& top = GetRequiredScope(element); |
| const ElementCollection candidates = top.GetCollection(type); |
| |
| for (ElementMap::const_iterator it = candidates.first; it != candidates.second; ++it) { |
| const int index = ParseTokenAsInt(GetRequiredToken(*(*it).second,0)); |
| if(index == typedIndex) { |
| ReadVertexData(type,typedIndex,GetRequiredScope(*(*it).second)); |
| return; |
| } |
| } |
| |
| FBXImporter::LogError(Formatter::format("failed to resolve vertex layer element: ") |
| << type << ", index: " << typedIndex); |
| } |
| |
| |
| // ------------------------------------------------------------------------------------------------ |
| void MeshGeometry::ReadVertexData(const std::string& type, int index, const Scope& source) |
| { |
| const std::string& MappingInformationType = ParseTokenAsString(GetRequiredToken( |
| GetRequiredElement(source,"MappingInformationType"),0) |
| ); |
| |
| const std::string& ReferenceInformationType = ParseTokenAsString(GetRequiredToken( |
| GetRequiredElement(source,"ReferenceInformationType"),0) |
| ); |
| |
| if (type == "LayerElementUV") { |
| if(index >= AI_MAX_NUMBER_OF_TEXTURECOORDS) { |
| FBXImporter::LogError(Formatter::format("ignoring UV layer, maximum number of UV channels exceeded: ") |
| << index << " (limit is " << AI_MAX_NUMBER_OF_TEXTURECOORDS << ")" ); |
| return; |
| } |
| |
| const Element* Name = source["Name"]; |
| m_uvNames[index] = ""; |
| if(Name) { |
| m_uvNames[index] = ParseTokenAsString(GetRequiredToken(*Name,0)); |
| } |
| |
| ReadVertexDataUV(m_uvs[index],source, |
| MappingInformationType, |
| ReferenceInformationType |
| ); |
| } |
| else if (type == "LayerElementMaterial") { |
| if (m_materials.size() > 0) { |
| FBXImporter::LogError("ignoring additional material layer"); |
| return; |
| } |
| |
| std::vector<int> temp_materials; |
| |
| ReadVertexDataMaterials(temp_materials,source, |
| MappingInformationType, |
| ReferenceInformationType |
| ); |
| |
| // sometimes, there will be only negative entries. Drop the material |
| // layer in such a case (I guess it means a default material should |
| // be used). This is what the converter would do anyway, and it |
| // avoids losing the material if there are more material layers |
| // coming of which at least one contains actual data (did observe |
| // that with one test file). |
| const size_t count_neg = std::count_if(temp_materials.begin(),temp_materials.end(),[](int n) { return n < 0; }); |
| if(count_neg == temp_materials.size()) { |
| FBXImporter::LogWarn("ignoring dummy material layer (all entries -1)"); |
| return; |
| } |
| |
| std::swap(temp_materials, m_materials); |
| } |
| else if (type == "LayerElementNormal") { |
| if (m_normals.size() > 0) { |
| FBXImporter::LogError("ignoring additional normal layer"); |
| return; |
| } |
| |
| ReadVertexDataNormals(m_normals,source, |
| MappingInformationType, |
| ReferenceInformationType |
| ); |
| } |
| else if (type == "LayerElementTangent") { |
| if (m_tangents.size() > 0) { |
| FBXImporter::LogError("ignoring additional tangent layer"); |
| return; |
| } |
| |
| ReadVertexDataTangents(m_tangents,source, |
| MappingInformationType, |
| ReferenceInformationType |
| ); |
| } |
| else if (type == "LayerElementBinormal") { |
| if (m_binormals.size() > 0) { |
| FBXImporter::LogError("ignoring additional binormal layer"); |
| return; |
| } |
| |
| ReadVertexDataBinormals(m_binormals,source, |
| MappingInformationType, |
| ReferenceInformationType |
| ); |
| } |
| else if (type == "LayerElementColor") { |
| if(index >= AI_MAX_NUMBER_OF_COLOR_SETS) { |
| FBXImporter::LogError(Formatter::format("ignoring vertex color layer, maximum number of color sets exceeded: ") |
| << index << " (limit is " << AI_MAX_NUMBER_OF_COLOR_SETS << ")" ); |
| return; |
| } |
| |
| ReadVertexDataColors(m_colors[index],source, |
| MappingInformationType, |
| ReferenceInformationType |
| ); |
| } |
| } |
| |
| |
| // ------------------------------------------------------------------------------------------------ |
| // Lengthy utility function to read and resolve a FBX vertex data array - that is, the |
| // output is in polygon vertex order. This logic is used for reading normals, UVs, colors, |
| // tangents .. |
| template <typename T> |
| void ResolveVertexDataArray(std::vector<T>& data_out, const Scope& source, |
| const std::string& MappingInformationType, |
| const std::string& ReferenceInformationType, |
| const char* dataElementName, |
| const char* indexDataElementName, |
| size_t vertex_count, |
| const std::vector<unsigned int>& mapping_counts, |
| const std::vector<unsigned int>& mapping_offsets, |
| const std::vector<unsigned int>& mappings) |
| { |
| |
| |
| // handle permutations of Mapping and Reference type - it would be nice to |
| // deal with this more elegantly and with less redundancy, but right |
| // now it seems unavoidable. |
| if (MappingInformationType == "ByVertice" && ReferenceInformationType == "Direct") { |
| std::vector<T> tempData; |
| ParseVectorDataArray(tempData, GetRequiredElement(source, dataElementName)); |
| |
| data_out.resize(vertex_count); |
| for (size_t i = 0, e = tempData.size(); i < e; ++i) { |
| |
| const unsigned int istart = mapping_offsets[i], iend = istart + mapping_counts[i]; |
| for (unsigned int j = istart; j < iend; ++j) { |
| data_out[mappings[j]] = tempData[i]; |
| } |
| } |
| } |
| else if (MappingInformationType == "ByVertice" && ReferenceInformationType == "IndexToDirect") { |
| std::vector<T> tempData; |
| ParseVectorDataArray(tempData, GetRequiredElement(source, dataElementName)); |
| |
| data_out.resize(vertex_count); |
| |
| std::vector<int> uvIndices; |
| ParseVectorDataArray(uvIndices,GetRequiredElement(source,indexDataElementName)); |
| |
| for (size_t i = 0, e = uvIndices.size(); i < e; ++i) { |
| |
| const unsigned int istart = mapping_offsets[i], iend = istart + mapping_counts[i]; |
| for (unsigned int j = istart; j < iend; ++j) { |
| if (static_cast<size_t>(uvIndices[i]) >= tempData.size()) { |
| DOMError("index out of range",&GetRequiredElement(source,indexDataElementName)); |
| } |
| data_out[mappings[j]] = tempData[uvIndices[i]]; |
| } |
| } |
| } |
| else if (MappingInformationType == "ByPolygonVertex" && ReferenceInformationType == "Direct") { |
| std::vector<T> tempData; |
| ParseVectorDataArray(tempData, GetRequiredElement(source, dataElementName)); |
| |
| if (tempData.size() != vertex_count) { |
| FBXImporter::LogError(Formatter::format("length of input data unexpected for ByPolygon mapping: ") |
| << tempData.size() << ", expected " << vertex_count |
| ); |
| return; |
| } |
| |
| data_out.swap(tempData); |
| } |
| else if (MappingInformationType == "ByPolygonVertex" && ReferenceInformationType == "IndexToDirect") { |
| std::vector<T> tempData; |
| ParseVectorDataArray(tempData, GetRequiredElement(source, dataElementName)); |
| |
| data_out.resize(vertex_count); |
| |
| std::vector<int> uvIndices; |
| ParseVectorDataArray(uvIndices,GetRequiredElement(source,indexDataElementName)); |
| |
| if (uvIndices.size() != vertex_count) { |
| FBXImporter::LogError("length of input data unexpected for ByPolygonVertex mapping"); |
| return; |
| } |
| |
| unsigned int next = 0; |
| for(int i : uvIndices) { |
| if (static_cast<size_t>(i) >= tempData.size()) { |
| DOMError("index out of range",&GetRequiredElement(source,indexDataElementName)); |
| } |
| |
| data_out[next++] = tempData[i]; |
| } |
| } |
| else { |
| FBXImporter::LogError(Formatter::format("ignoring vertex data channel, access type not implemented: ") |
| << MappingInformationType << "," << ReferenceInformationType); |
| } |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| void MeshGeometry::ReadVertexDataNormals(std::vector<aiVector3D>& normals_out, const Scope& source, |
| const std::string& MappingInformationType, |
| const std::string& ReferenceInformationType) |
| { |
| ResolveVertexDataArray(normals_out,source,MappingInformationType,ReferenceInformationType, |
| "Normals", |
| "NormalsIndex", |
| m_vertices.size(), |
| m_mapping_counts, |
| m_mapping_offsets, |
| m_mappings); |
| } |
| |
| |
| // ------------------------------------------------------------------------------------------------ |
| void MeshGeometry::ReadVertexDataUV(std::vector<aiVector2D>& uv_out, const Scope& source, |
| const std::string& MappingInformationType, |
| const std::string& ReferenceInformationType) |
| { |
| ResolveVertexDataArray(uv_out,source,MappingInformationType,ReferenceInformationType, |
| "UV", |
| "UVIndex", |
| m_vertices.size(), |
| m_mapping_counts, |
| m_mapping_offsets, |
| m_mappings); |
| } |
| |
| |
| // ------------------------------------------------------------------------------------------------ |
| void MeshGeometry::ReadVertexDataColors(std::vector<aiColor4D>& colors_out, const Scope& source, |
| const std::string& MappingInformationType, |
| const std::string& ReferenceInformationType) |
| { |
| ResolveVertexDataArray(colors_out,source,MappingInformationType,ReferenceInformationType, |
| "Colors", |
| "ColorIndex", |
| m_vertices.size(), |
| m_mapping_counts, |
| m_mapping_offsets, |
| m_mappings); |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| static const std::string TangentIndexToken = "TangentIndex"; |
| static const std::string TangentsIndexToken = "TangentsIndex"; |
| |
| void MeshGeometry::ReadVertexDataTangents(std::vector<aiVector3D>& tangents_out, const Scope& source, |
| const std::string& MappingInformationType, |
| const std::string& ReferenceInformationType) |
| { |
| const char * str = source.Elements().count( "Tangents" ) > 0 ? "Tangents" : "Tangent"; |
| const char * strIdx = source.Elements().count( "Tangents" ) > 0 ? TangentsIndexToken.c_str() : TangentIndexToken.c_str(); |
| ResolveVertexDataArray(tangents_out,source,MappingInformationType,ReferenceInformationType, |
| str, |
| strIdx, |
| m_vertices.size(), |
| m_mapping_counts, |
| m_mapping_offsets, |
| m_mappings); |
| } |
| |
| // ------------------------------------------------------------------------------------------------ |
| static const std::string BinormalIndexToken = "BinormalIndex"; |
| static const std::string BinormalsIndexToken = "BinormalsIndex"; |
| |
| void MeshGeometry::ReadVertexDataBinormals(std::vector<aiVector3D>& binormals_out, const Scope& source, |
| const std::string& MappingInformationType, |
| const std::string& ReferenceInformationType) |
| { |
| const char * str = source.Elements().count( "Binormals" ) > 0 ? "Binormals" : "Binormal"; |
| const char * strIdx = source.Elements().count( "Binormals" ) > 0 ? BinormalsIndexToken.c_str() : BinormalIndexToken.c_str(); |
| ResolveVertexDataArray(binormals_out,source,MappingInformationType,ReferenceInformationType, |
| str, |
| strIdx, |
| m_vertices.size(), |
| m_mapping_counts, |
| m_mapping_offsets, |
| m_mappings); |
| } |
| |
| |
| // ------------------------------------------------------------------------------------------------ |
| void MeshGeometry::ReadVertexDataMaterials(std::vector<int>& materials_out, const Scope& source, |
| const std::string& MappingInformationType, |
| const std::string& ReferenceInformationType) |
| { |
| const size_t face_count = m_faces.size(); |
| ai_assert(face_count); |
| |
| // 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. |
| ParseVectorDataArray(materials_out,GetRequiredElement(source,"Materials")); |
| |
| if (MappingInformationType == "AllSame") { |
| // easy - same material for all faces |
| if (materials_out.empty()) { |
| FBXImporter::LogError(Formatter::format("expected material index, ignoring")); |
| return; |
| } |
| 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(); |
| } |
| |
| m_materials.assign(m_vertices.size(),materials_out[0]); |
| } |
| else if (MappingInformationType == "ByPolygon" && ReferenceInformationType == "IndexToDirect") { |
| m_materials.resize(face_count); |
| |
| if(materials_out.size() != face_count) { |
| FBXImporter::LogError(Formatter::format("length of input data unexpected for ByPolygon mapping: ") |
| << materials_out.size() << ", expected " << face_count |
| ); |
| return; |
| } |
| } |
| else { |
| FBXImporter::LogError(Formatter::format("ignoring material assignments, access type not implemented: ") |
| << MappingInformationType << "," << ReferenceInformationType); |
| } |
| } |
| |
| } // !FBX |
| } // !Assimp |
| |
| #endif |
| |