From 85a487f6b8e68021a2351938a054db837695a60d Mon Sep 17 00:00:00 2001 From: ulfjorensen Date: Wed, 26 Nov 2008 15:45:34 +0000 Subject: [PATCH] - Bugfix: ColladaLoader node rotation was miscalculated - ColladaLoader now supports more primitive types - ColladaLoader now honours the up-vector specification - moved Collada temporary structures to separate Helper header - fast_atof_move() now also eats localized numbers with comma separator (e.g. 0,3210) correctly git-svn-id: https://assimp.svn.sourceforge.net/svnroot/assimp/trunk@251 67173fc5-114c-0410-ac8e-9d2fd5bffc1f --- code/ColladaHelper.h | 172 +++++++++++++++++++++++++++++++++++ code/ColladaLoader.cpp | 21 ++++- code/ColladaLoader.h | 4 +- code/ColladaParser.cpp | 107 +++++++++++++++++----- code/ColladaParser.h | 140 ++++------------------------ code/fast_atof.h | 2 +- workspaces/vc8/assimp.vcproj | 4 + 7 files changed, 298 insertions(+), 152 deletions(-) create mode 100644 code/ColladaHelper.h diff --git a/code/ColladaHelper.h b/code/ColladaHelper.h new file mode 100644 index 000000000..894ed773c --- /dev/null +++ b/code/ColladaHelper.h @@ -0,0 +1,172 @@ +/** Helper structures for the Collada loader */ + +/* +Open Asset Import Library (ASSIMP) +---------------------------------------------------------------------- + +Copyright (c) 2006-2008, ASSIMP Development 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 Development 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. + +---------------------------------------------------------------------- +*/ + +#ifndef AI_COLLADAHELPER_H_INC +#define AI_COLLADAHELPER_H_INC + +namespace Assimp +{ +namespace Collada +{ + +/** Transformation types that can be applied to a node */ +enum TransformType +{ + TF_LOOKAT, + TF_ROTATE, + TF_TRANSLATE, + TF_SCALE, + TF_SKEW, + TF_MATRIX +}; + +/** Contains all data for one of the different transformation types */ +struct Transform +{ + TransformType mType; + float f[16]; ///< Interpretation of data depends on the type of the transformation +}; + +/** A node in a scene hierarchy */ +struct Node +{ + std::string mName; + std::string mID; + Node* mParent; + std::vector mChildren; + + /** Operations in order to calculate the resulting transformation to parent. */ + std::vector mTransforms; + + std::vector mMeshes; ///< Meshes at this node + + Node() { mParent = NULL; } + ~Node() { for( std::vector::iterator it = mChildren.begin(); it != mChildren.end(); ++it) delete *it; } +}; + +/** Data source array */ +struct Data +{ + std::vector mValues; +}; + +/** Accessor to a data array */ +struct Accessor +{ + size_t mCount; // in number of objects + size_t mOffset; // in number of values + size_t mStride; // Stride in number of values + std::vector mParams; // names of the data streams in the accessors. Empty string tells to ignore. + size_t mSubOffset[4]; // Suboffset inside the object for the common 4 elements. For a vector, thats XYZ, for a color RGBA and so on. + // For example, SubOffset[0] denotes which of the values inside the object is the vector X component. + std::string mSource; // URL of the source array + mutable const Data* mData; // Pointer to the source array, if resolved. NULL else + + Accessor() + { + mCount = 0; mOffset = 0; mStride = 0; mData = NULL; + mSubOffset[0] = mSubOffset[1] = mSubOffset[2] = mSubOffset[3] = 0; + } +}; + +/** A single face in a mesh */ +struct Face +{ + std::vector mIndices; +}; + +/** Different types of input data to a vertex or face */ +enum InputType +{ + IT_Invalid, + IT_Vertex, // special type for per-index data referring to the element carrying the per-vertex data. + IT_Position, + IT_Normal, + IT_Texcoord, + IT_Color +}; + +/** An input channel for mesh data, referring to a single accessor */ +struct InputChannel +{ + InputType mType; // Type of the data + size_t mIndex; // Optional index, if multiple sets of the same data type are given + size_t mOffset; // Index offset in the indices array of per-face indices. Don't ask, can't explain that any better. + std::string mAccessor; // ID of the accessor where to read the actual values from. + mutable const Accessor* mResolved; // Pointer to the accessor, if resolved. NULL else + + InputChannel() { mType = IT_Invalid; mIndex = 0; mOffset = 0; mResolved = NULL; } +}; + +/** Contains data for a single mesh */ +struct Mesh +{ + std::string mVertexID; // just to check if there's some sophisticated addressing involved... which we don't support, and therefore should warn about. + std::vector mPerVertexData; // Vertex data addressed by vertex indices + + // actual mesh data, assembled on encounter of a

element. Verbose format, not indexed + std::vector mPositions; + std::vector mNormals; + std::vector mTexCoords[AI_MAX_NUMBER_OF_TEXTURECOORDS]; + std::vector mColors[AI_MAX_NUMBER_OF_COLOR_SETS]; + + // Faces. Stored are only the number of vertices for each face. 1 == point, 2 == line, 3 == triangle, 4+ == poly + std::vector mFaceSize; +}; + +/** Which type of primitives the ReadPrimitives() function is going to read */ +enum PrimitiveType +{ + Prim_Invalid, + Prim_Lines, + Prim_LineStrip, + Prim_Triangles, + Prim_TriStrips, + Prim_TriFans, + Prim_Polylist, + Prim_Polygon +}; + +} // end of namespace Collada +} // end of namespace Assimp + +#endif // AI_COLLADAHELPER_H_INC diff --git a/code/ColladaLoader.cpp b/code/ColladaLoader.cpp index 21f06d07c..c50320b16 100644 --- a/code/ColladaLoader.cpp +++ b/code/ColladaLoader.cpp @@ -90,6 +90,21 @@ void ColladaLoader::InternReadFile( const std::string& pFile, aiScene* pScene, I // build the node hierarchy from it pScene->mRootNode = BuildHierarchy( parser, parser.mRootNode); + // Convert to Z_UP, if different orientation + if( parser.mUpDirection == ColladaParser::UP_X) + pScene->mRootNode->mTransformation *= aiMatrix4x4( + 0, -1, 0, 0, + 0, 0, -1, 0, + 1, 0, 0, 0, + 0, 0, 0, 1); + else if( parser.mUpDirection == ColladaParser::UP_Y) + pScene->mRootNode->mTransformation *= aiMatrix4x4( + 1, 0, 0, 0, + 0, 0, -1, 0, + 0, 1, 0, 0, + 0, 0, 0, 1); + + // store all meshes StoreSceneMeshes( pScene); @@ -113,7 +128,7 @@ void ColladaLoader::InternReadFile( const std::string& pFile, aiScene* pScene, I // ------------------------------------------------------------------------------------------------ // Recursively constructs a scene node for the given parser node and returns it. -aiNode* ColladaLoader::BuildHierarchy( const ColladaParser& pParser, const ColladaParser::Node* pNode) +aiNode* ColladaLoader::BuildHierarchy( const ColladaParser& pParser, const Collada::Node* pNode) { // create a node for it aiNode* node = new aiNode( pNode->mName); @@ -138,7 +153,7 @@ aiNode* ColladaLoader::BuildHierarchy( const ColladaParser& pParser, const Colla // ------------------------------------------------------------------------------------------------ // Builds meshes for the given node and references them -void ColladaLoader::BuildMeshesForNode( const ColladaParser& pParser, const ColladaParser::Node* pNode, aiNode* pTarget) +void ColladaLoader::BuildMeshesForNode( const ColladaParser& pParser, const Collada::Node* pNode, aiNode* pTarget) { // accumulated mesh references by this node std::vector newMeshRefs; @@ -163,7 +178,7 @@ void ColladaLoader::BuildMeshesForNode( const ColladaParser& pParser, const Coll { // else we have to add the mesh to the collection and store its newly assigned index at the node aiMesh* dstMesh = new aiMesh; - const ColladaParser::Mesh* srcMesh = srcMeshIt->second; + const Collada::Mesh* srcMesh = srcMeshIt->second; // copy positions dstMesh->mNumVertices = srcMesh->mPositions.size(); diff --git a/code/ColladaLoader.h b/code/ColladaLoader.h index 0b0e29e5b..0e5c6972d 100644 --- a/code/ColladaLoader.h +++ b/code/ColladaLoader.h @@ -83,10 +83,10 @@ protected: void InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler); /** Recursively constructs a scene node for the given parser node and returns it. */ - aiNode* BuildHierarchy( const ColladaParser& pParser, const ColladaParser::Node* pNode); + aiNode* BuildHierarchy( const ColladaParser& pParser, const Collada::Node* pNode); /** Builds meshes for the given node and references them */ - void BuildMeshesForNode( const ColladaParser& pParser, const ColladaParser::Node* pNode, aiNode* pTarget); + void BuildMeshesForNode( const ColladaParser& pParser, const Collada::Node* pNode, aiNode* pTarget); /** Stores all meshes in the given scene */ void StoreSceneMeshes( aiScene* pScene); diff --git a/code/ColladaParser.cpp b/code/ColladaParser.cpp index d263c3bbb..d412a129f 100644 --- a/code/ColladaParser.cpp +++ b/code/ColladaParser.cpp @@ -46,6 +46,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "ParsingUtils.h" using namespace Assimp; +using namespace Assimp::Collada; // ------------------------------------------------------------------------------------------------ // Constructor to be privately used by Importer @@ -253,7 +254,8 @@ void ColladaParser::ReadMesh( Mesh* pMesh) // read per-vertex mesh data ReadVertexData( pMesh); } - else if( IsElement( "polylist") || IsElement( "triangles")) + else if( IsElement( "triangles") || IsElement( "lines") || IsElement( "linestrips") + || IsElement( "polygons") || IsElement( "polylist") || IsElement( "trifans") || IsElement( "tristrips")) { // read per-index mesh data and faces setup ReadIndexData( pMesh); @@ -438,7 +440,23 @@ void ColladaParser::ReadIndexData( Mesh* pMesh) // distinguish between polys and triangles std::string elementName = mReader->getNodeName(); - bool isPolylist = IsElement( "polylist"); + PrimitiveType primType = Prim_Invalid; + if( IsElement( "lines")) + primType = Prim_Lines; + else if( IsElement( "linestrips")) + primType = Prim_LineStrip; + else if( IsElement( "polygons")) + primType = Prim_Polygon; + else if( IsElement( "polylist")) + primType = Prim_Polylist; + else if( IsElement( "triangles")) + primType = Prim_Triangles; + else if( IsElement( "trifans")) + primType = Prim_TriFans; + else if( IsElement( "tristrips")) + primType = Prim_TriStrips; + + assert( primType != Prim_Invalid); // also a number of elements, but in addition a

primitive collection and propably index counts for all primitives while( mReader->read()) @@ -469,7 +487,7 @@ void ColladaParser::ReadIndexData( Mesh* pMesh) else if( IsElement( "p")) { // now here the actual fun starts - these are the indices to construct the mesh data from - ReadPrimitives( pMesh, perIndexData, numPrimitives, vcount, isPolylist); + ReadPrimitives( pMesh, perIndexData, numPrimitives, vcount, primType); } else { ThrowException( "Unexpected sub element in tag \"vertices\"."); @@ -519,7 +537,7 @@ void ColladaParser::ReadInputChannel( std::vector& poChannels) // ------------------------------------------------------------------------------------------------ // Reads a

primitive index list and assembles the mesh data into the given mesh void ColladaParser::ReadPrimitives( Mesh* pMesh, std::vector& pPerIndexChannels, - size_t pNumPrimitives, const std::vector& pVCount, bool pIsPolylist) + size_t pNumPrimitives, const std::vector& pVCount, PrimitiveType pPrimType) { // determine number of indices coming per vertex // find the offset index for all per-vertex channels @@ -534,32 +552,52 @@ void ColladaParser::ReadPrimitives( Mesh* pMesh, std::vector& pPer // determine the expected number of indices size_t expectedPointCount = 0; - if( pIsPolylist) + switch( pPrimType) { - BOOST_FOREACH( size_t i, pVCount) - expectedPointCount += i; - } else - { - // everything triangles - expectedPointCount = 3 * pNumPrimitives; + case Prim_Polylist: + { + BOOST_FOREACH( size_t i, pVCount) + expectedPointCount += i; + break; + } + case Prim_Lines: + expectedPointCount = 2 * pNumPrimitives; + break; + case Prim_Triangles: + expectedPointCount = 3 * pNumPrimitives; + break; + default: + // other primitive types don't state the index count upfront... we need to guess + break; } // and read all indices into a temporary array - std::vector indices( expectedPointCount * numOffsets); + std::vector indices; + if( expectedPointCount > 0) + indices.reserve( expectedPointCount * numOffsets); + const char* content = GetTextContent(); - BOOST_FOREACH( size_t& value, indices) + while( *content != 0) { - if( *content == 0) - ThrowException( "Expected more values while reading primitive indices."); - // read a value in place - value = strtol10( content, &content); + // read a value + unsigned int value = strtol10( content, &content); + indices.push_back( size_t( value)); // skip whitespace after it SkipSpacesAndLineEnd( &content); } + // complain if the index count doesn't fit + if( expectedPointCount > 0 && indices.size() != expectedPointCount * numOffsets) + ThrowException( "Expected different index count in

element."); + else if( expectedPointCount == 0 && (indices.size() % numOffsets) != 0) + ThrowException( "Expected different index count in

element."); + // find the data for all sources BOOST_FOREACH( InputChannel& input, pMesh->mPerVertexData) { + if( input.mResolved) + continue; + // find accessor input.mResolved = &ResolveLibraryReference( mAccessorLibrary, input.mAccessor); // resolve accessor's data pointer as well, if neccessary @@ -570,6 +608,9 @@ void ColladaParser::ReadPrimitives( Mesh* pMesh, std::vector& pPer // and the same for the per-index channels BOOST_FOREACH( InputChannel& input, pPerIndexChannels) { + if( input.mResolved) + continue; + // ignore vertex pointer, it doesn't refer to an accessor if( input.mType == IT_Vertex) { @@ -590,12 +631,28 @@ void ColladaParser::ReadPrimitives( Mesh* pMesh, std::vector& pPer // now assemble vertex data according to those indices std::vector::const_iterator idx = indices.begin(); - for( size_t a = 0; a < pNumPrimitives; a++) + + // For continued primitives, the given count does not come all in one

, but only one primitive per

+ size_t numPrimitives = pNumPrimitives; + if( pPrimType == Prim_TriFans || pPrimType == Prim_Polygon) + numPrimitives = 1; + + for( size_t a = 0; a < numPrimitives; a++) { // determine number of points for this primitive - size_t numPoints = 3; - if( pIsPolylist) - numPoints = pVCount[a]; + size_t numPoints = 0; + switch( pPrimType) + { + case Prim_Lines: numPoints = 2; break; + case Prim_Triangles: numPoints = 3; break; + case Prim_Polylist: numPoints = pVCount[a]; break; + case Prim_TriFans: + case Prim_Polygon: numPoints = indices.size() / numOffsets; break; + default: + // LineStrip and TriStrip not supported due to expected index unmangling + ThrowException( "Unsupported primitive type."); + break; + } // store the face size to later reconstruct the face from pMesh->mFaceSize.push_back( numPoints); @@ -618,7 +675,7 @@ void ColladaParser::ReadPrimitives( Mesh* pMesh, std::vector& pPer } } - // if I ever get my hands on that guy how invented this steaming pile of indirection... + // if I ever get my hands on that guy who invented this steaming pile of indirection... TestClosing( "p"); } @@ -979,7 +1036,9 @@ aiMatrix4x4 ColladaParser::CalculateResultTransform( const std::vector mChildren; - - /** Operations in order to calculate the resulting transformation to parent. */ - std::vector mTransforms; - - std::vector mMeshes; ///< Meshes at this node - - Node() { mParent = NULL; } - ~Node() { for( std::vector::iterator it = mChildren.begin(); it != mChildren.end(); ++it) delete *it; } - }; - - /** Data source array */ - struct Data - { - std::vector mValues; - }; - - /** Accessor to a data array */ - struct Accessor - { - size_t mCount; // in number of objects - size_t mOffset; // in number of values - size_t mStride; // Stride in number of values - std::vector mParams; // names of the data streams in the accessors. Empty string tells to ignore. - size_t mSubOffset[4]; // Suboffset inside the object for the common 4 elements. For a vector, thats XYZ, for a color RGBA and so on. - // For example, SubOffset[0] denotes which of the values inside the object is the vector X component. - std::string mSource; // URL of the source array - mutable const Data* mData; // Pointer to the source array, if resolved. NULL else - - Accessor() - { - mCount = 0; mOffset = 0; mStride = 0; mData = NULL; - mSubOffset[0] = mSubOffset[1] = mSubOffset[2] = mSubOffset[3] = 0; - } - }; - - /** A single face in a mesh */ - struct Face - { - std::vector mIndices; - }; - - /** Different types of input data to a vertex or face */ - enum InputType - { - IT_Invalid, - IT_Vertex, // special type for per-index data referring to the element carrying the per-vertex data. - IT_Position, - IT_Normal, - IT_Texcoord, - IT_Color - }; - - /** An input channel for mesh data, referring to a single accessor */ - struct InputChannel - { - InputType mType; // Type of the data - size_t mIndex; // Optional index, if multiple sets of the same data type are given - size_t mOffset; // Index offset in the indices array of per-face indices. Don't ask, can't explain that any better. - std::string mAccessor; // ID of the accessor where to read the actual values from. - mutable const Accessor* mResolved; // Pointer to the accessor, if resolved. NULL else - - InputChannel() { mType = IT_Invalid; mIndex = 0; mOffset = 0; mResolved = NULL; } - }; - - /** Contains data for a single mesh */ - struct Mesh - { - std::string mVertexID; // just to check if there's some sophisticated addressing involved... which we don't support, and therefore should warn about. - std::vector mPerVertexData; // Vertex data addressed by vertex indices - - // actual mesh data, assembled on encounter of a

element. Verbose format, not indexed - std::vector mPositions; - std::vector mNormals; - std::vector mTexCoords[AI_MAX_NUMBER_OF_TEXTURECOORDS]; - std::vector mColors[AI_MAX_NUMBER_OF_COLOR_SETS]; - - // Faces. Stored are only the number of vertices for each face. 1 == point, 2 == line, 3 == triangle, 4+ == poly - std::vector mFaceSize; - }; protected: /** Constructor from XML file */ @@ -180,7 +76,7 @@ protected: void ReadGeometryLibrary(); /** Reads a mesh from the geometry library */ - void ReadMesh( Mesh* pMesh); + void ReadMesh( Collada::Mesh* pMesh); /** Reads a data array holding a number of floats, and stores it in the global library */ void ReadFloatArray(); @@ -191,32 +87,32 @@ protected: void ReadAccessor( const std::string& pID); /** Reads input declarations of per-vertex mesh data into the given mesh */ - void ReadVertexData( Mesh* pMesh); + void ReadVertexData( Collada::Mesh* pMesh); /** Reads input declarations of per-index mesh data into the given mesh */ - void ReadIndexData( Mesh* pMesh); + void ReadIndexData( Collada::Mesh* pMesh); /** Reads a single input channel element and stores it in the given array, if valid */ - void ReadInputChannel( std::vector& poChannels); + void ReadInputChannel( std::vector& poChannels); /** Reads a

primitive index list and assembles the mesh data into the given mesh */ - void ReadPrimitives( Mesh* pMesh, std::vector& pPerIndexChannels, - size_t pNumPrimitives, const std::vector& pVCount, bool pIsPolylist); + void ReadPrimitives( Collada::Mesh* pMesh, std::vector& pPerIndexChannels, + size_t pNumPrimitives, const std::vector& pVCount, Collada::PrimitiveType pPrimType); /** Extracts a single object from an input channel and stores it in the appropriate mesh data array */ - void ExtractDataObjectFromChannel( const InputChannel& pInput, size_t pLocalIndex, Mesh* pMesh); + void ExtractDataObjectFromChannel( const Collada::InputChannel& pInput, size_t pLocalIndex, Collada::Mesh* pMesh); /** Reads the library of node hierarchies and scene parts */ void ReadSceneLibrary(); /** Reads a scene node's contents including children and stores it in the given node */ - void ReadSceneNode( Node* pNode); + void ReadSceneNode( Collada::Node* pNode); /** Reads a node transformation entry of the given type and adds it to the given node's transformation list. */ - void ReadNodeTransformation( Node* pNode, TransformType pType); + void ReadNodeTransformation( Collada::Node* pNode, Collada::TransformType pType); /** Reads a mesh reference in a node and adds it to the node's mesh list */ - void ReadNodeGeometry( Node* pNode); + void ReadNodeGeometry( Collada::Node* pNode); /** Reads the collada scene */ void ReadScene(); @@ -247,10 +143,10 @@ protected: const char* GetTextContent(); /** Calculates the resulting transformation fromm all the given transform steps */ - aiMatrix4x4 CalculateResultTransform( const std::vector& pTransforms) const; + aiMatrix4x4 CalculateResultTransform( const std::vector& pTransforms) const; /** Determines the input data type for the given semantic string */ - InputType GetTypeForSemantic( const std::string& pSemantic); + Collada::InputType GetTypeForSemantic( const std::string& pSemantic); /** Finds the item in the given library by its reference, throws if not found */ template const Type& ResolveLibraryReference( const std::map& pLibrary, const std::string& pURL) const; @@ -263,23 +159,23 @@ protected: irr::io::IrrXMLReader* mReader; /** All data arrays found in the file by ID. Might be referred to by actually everyone. Collada, you are a steaming pile of indirection. */ - typedef std::map DataLibrary; + typedef std::map DataLibrary; DataLibrary mDataLibrary; /** Same for accessors which define how the data in a data array is accessed. */ - typedef std::map AccessorLibrary; + typedef std::map AccessorLibrary; AccessorLibrary mAccessorLibrary; /** Mesh library: mesh by ID */ - typedef std::map MeshLibrary; + typedef std::map MeshLibrary; MeshLibrary mMeshLibrary; /** node library: root node of the hierarchy part by ID */ - typedef std::map NodeLibrary; + typedef std::map NodeLibrary; NodeLibrary mNodeLibrary; /** Pointer to the root node. Don't delete, it just points to one of the nodes in the node library. */ - Node* mRootNode; + Collada::Node* mRootNode; /** Size unit: how large compared to a meter */ float mUnitSize; diff --git a/code/fast_atof.h b/code/fast_atof.h index 080b6af3b..19e9f3229 100644 --- a/code/fast_atof.h +++ b/code/fast_atof.h @@ -194,7 +194,7 @@ inline const char* fast_atof_move( const char* c, float& out) else if (*c=='+')++c; f = (float) strtol10_64 ( c, &c ); - if (*c == '.') + if (*c == '.' || (c[0] == ',' && isdigit( c[1]))) { ++c; diff --git a/workspaces/vc8/assimp.vcproj b/workspaces/vc8/assimp.vcproj index 406cda939..ef747ecd3 100644 --- a/workspaces/vc8/assimp.vcproj +++ b/workspaces/vc8/assimp.vcproj @@ -1437,6 +1437,10 @@ + +