diff --git a/ComplexShape.cpp b/ComplexShape.cpp index 09abb3ff97a6e51ca367f9262e1d65705b2cc878..66d34f4d8b329205a2f1bf9c2c81197488652c4f 100644 --- a/ComplexShape.cpp +++ b/ComplexShape.cpp @@ -5,6 +5,7 @@ All rights reserved. Please see niflib.h for licence. */ #include "obj/NiNode.h" #include "obj/NiProperty.h" #include "obj/NiAVObject.h" +#include "obj/NiTriBasedGeom.h" #include "obj/NiTriShape.h" #include "obj/NiTriShapeData.h" #include "obj/NiTexturingProperty.h" @@ -15,6 +16,38 @@ All rights reserved. Please see niflib.h for licence. */ using namespace Niflib; +struct VertNorm { + Vector3 position; + Vector3 normal; + map<NiNodeRef, float> weights; + + VertNorm() {} + ~VertNorm() {} + VertNorm( const VertNorm & n ) { + *this = n; + } + VertNorm & operator=( const VertNorm & n ) { + position = n.position; + normal = n.normal; + weights = n.weights; + return *this; + } + bool operator==( const VertNorm & n ) { + if ( abs(position.x - n.position.x) > 0.001 || abs(position.y - n.position.y) > 0.001 || abs(position.z - n.position.z) > 0.001 ) { + return false; + } + if ( abs(normal.x - n.normal.x) > 0.001 || abs(normal.y - n.normal.y) > 0.001 || abs(normal.z - n.normal.z) > 0.001 ) { + return false; + } + //if ( weights != n.weights ) { + // return false; + //} + + return true; + } +}; + + struct CompoundVertex { Vector3 position; Vector3 normal; @@ -42,7 +75,7 @@ struct CompoundVertex { if ( normal != n.normal ) { return false; } - if ( color.r != n.color.r && color.g != n.color.g && color.b != n.color.b && color.a != n.color.a ) { + if ( color != n.color ) { return false; } if ( texCoords != n.texCoords ) { @@ -121,6 +154,372 @@ vector< Ref<NiNode> > ComplexShape::GetSkinInfluences() const { return skinInfluences; } +void ComplexShape::Clear() { + vertices.clear(); + colors.clear(); + normals.clear(); + texCoordSets.clear(); + faces.clear(); + propGroups.clear(); + skinInfluences.clear(); + name.clear(); +} + +struct MergeLookUp { + unsigned vertIndex; + unsigned normIndex; + unsigned colorIndex; + map<unsigned, unsigned> uvIndices; //TexCoordSet Index, TexCoord Index +}; + +void ComplexShape::Merge( const Ref<NiAVObject> & root ) { + + if ( root == NULL ) { + throw runtime_error("Called ComplexShape::Merge with a null root reference."); + } + + vector<NiTriBasedGeomRef> shapes; + + //cout << "Determine root type" << endl; + if ( root->IsDerivedType( NiTriBasedGeom::TypeConst() ) ) { + //The function was called on a single shape. + //Add it to the list + shapes.push_back( DynamicCast<NiTriBasedGeom>(root) ); + } else if ( root->IsDerivedType( NiNode::TypeConst() ) ) { + //The function was called on a NiNOde. Search for + //shape children + NiNodeRef nodeRoot = DynamicCast<NiNode>(root); + vector<NiAVObjectRef> children = nodeRoot->GetChildren(); + for ( unsigned child = 0; child < children.size(); ++child ) { + if ( children[child]->IsDerivedType( NiTriBasedGeom::TypeConst() ) ) { + shapes.push_back( DynamicCast<NiTriBasedGeom>(children[child]) ); + } + } + + if ( shapes.size() == 0 ) { + throw runtime_error("The NiNode passed to ComplexShape::Merge has no shape children."); + } + } else { + throw runtime_error(" The ComplexShape::Merge function requies either a NiNode or a NiTriBasedGeom AVObject."); + } + + //The vector of VertNorm struts allows us to to refuse + //to merge vertices that have different normals. + vector<VertNorm> vns; + + //cout << "Clear all existing data" << endl; + //Clear all existing data + Clear(); + + //cout << "Merge in data from each shape" << endl; + //Merge in data from each shape + bool has_any_verts = false; + bool has_any_norms = false; + propGroups.resize( shapes.size() ); + unsigned prop_group_index = 0; + for ( vector<NiTriBasedGeomRef>::iterator geom = shapes.begin(); geom != shapes.end(); ++geom ) { + + //cout << "Merging in " << *geom << endl; + //Get properties of this shape + propGroups[prop_group_index] = (*geom)->GetProperties(); + + + NiTriBasedGeomDataRef geomData = (*geom)->GetData(); + + if ( geomData == NULL ) { + throw runtime_error("One of the NiTriBasedGeom found by ComplexShape::Merge with a NiTriBasedGeom has no NiTriBasedGeomData attached."); + } + + //cout << "Get Data" << endl; + //Get Data + vector<Vector3> shapeVerts; + //If this is a skin influenced mesh, get vertices from niGeom + if ( (*geom)->GetSkinInstance() != NULL ) { + shapeVerts = (*geom)->GetSkinInfluencedVertices(); + } else { + shapeVerts = geomData->GetVertices(); + } + + vector<Vector3> shapeNorms = geomData->GetNormals(); + vector<Color4> shapeColors = geomData->GetColors(); + vector< vector<TexCoord> > shapeUVs( geomData->GetUVSetCount() ); + for ( unsigned i = 0; i < shapeUVs.size(); ++i ) { + shapeUVs[i] = geomData->GetUVSet(i); + } + vector<Triangle> shapeTris= geomData->GetTriangles(); + + //Lookup table + vector<MergeLookUp> lookUp( geomData->GetVertexCount() ); + + //cout << "Vertices and normals" << endl; + //Vertices and normals + if ( shapeVerts.size() != 0 ) { + has_any_verts = true; + } + + bool shape_has_norms = ( shapeNorms.size() == shapeVerts.size() ); + + if ( shape_has_norms ) { + has_any_norms = true; + } + for ( unsigned v = 0; v < shapeVerts.size(); ++v ) { + VertNorm newVert; + + newVert.position = shapeVerts[v]; + if ( shape_has_norms ) { + newVert.normal = shapeNorms[v]; + } + + //Search for matching vert/norm + bool match_found = false; + for ( unsigned vn_index = 0; vn_index < vns.size(); ++vn_index ) { + if ( vns[vn_index] == newVert ) { + //Match found, use existing index + lookUp[v].vertIndex = vn_index; + if ( shapeNorms.size() != 0 ) { + lookUp[v].normIndex = vn_index; + } + match_found = true; + //Stop searching + break; + } + } + + if ( match_found == false ) { + //No match found, add this vert/norm to the list + vns.push_back(newVert); + //Record new index + lookUp[v].vertIndex = unsigned(vns.size()) - 1; + if ( shapeNorms.size() != 0 ) { + lookUp[v].normIndex = unsigned(vns.size()) - 1; + } + } + } + + //cout << "Colors" << endl; + //Colors + for ( unsigned c = 0; c < shapeColors.size(); ++c ) { + Color4 newColor; + + newColor = shapeColors[c]; + + //Search for matching color + bool match_found = false; + for ( unsigned c_index = 0; c_index < colors.size(); ++c_index ) { + if ( colors[c_index].r == newColor.r && colors[c_index].g == newColor.g && colors[c_index].b == newColor.b && colors[c_index].a == newColor.a ) { + //Match found, use existing index + //cout << "Color match found: " << colors[c_index] << " and " << newColor << " at index " << c_index << endl; + lookUp[c].colorIndex = c_index; + match_found = true; + //Stop searching + break; + } + } + + if ( match_found == false ) { + //No match found, add this color to the list + colors.push_back(newColor); + //Record new index + lookUp[c].colorIndex = unsigned(colors.size()) - 1; + //cout << "No Match found. Placed new color " << newColor << " at lookUp[" << c << "].colorIndex: " << lookUp[c].colorIndex << endl; + } + } + + //cout << "Texture Coordinates" << endl; + //Texture Coordinates + + //Create UV set list + vector<TexType> uvSetList; + NiPropertyRef niProp = (*geom)->GetPropertyByType( NiTexturingProperty::TypeConst() ); + NiTexturingPropertyRef niTexProp; + if ( niProp != NULL ) { + niTexProp = DynamicCast<NiTexturingProperty>(niProp); + } + if ( niTexProp != NULL ) { + for ( int tex = 0; tex < 8; ++tex ) { + if ( niTexProp->HasTexture(tex) == true ) { + ////cout << "Adding texture type to list: " << TexType(tex) << endl; + uvSetList.push_back( TexType(tex) ); + } + } + } + + for ( unsigned set = 0; set < shapeUVs.size(); ++set ) { + TexType newType = BASE_MAP; + if ( uvSetList.size() > set ) { + newType = uvSetList[set]; + } + + //Search for matching UV set + bool match_found = false; + unsigned uvSetIndex; + for ( unsigned set_index = 0; set_index < texCoordSets.size(); ++set_index ) { + if ( texCoordSets[set_index].texType == newType ) { + ////cout << "Match found, use existing texture set index" << endl; + //Match found, use existing index + uvSetIndex = set_index; + match_found = true; + //Stop searching + break; + } + } + + if ( match_found == false ) { + ////cout << "No match found, add this texture set to the list" << endl; + //No match found, add this UV set to the list + TexCoordSet newTCS; + newTCS.texType = newType; + texCoordSets.push_back( newTCS ); + //Record new index + uvSetIndex = unsigned(texCoordSets.size()) - 1; + } + + ////cout << "Loop through texture cooridnates in this set" << endl; + for ( unsigned v = 0; v < shapeUVs[set].size(); ++v ) { + TexCoord newCoord; + + newCoord = shapeUVs[set][v]; + + //cout << "Search for matching texture coordinate" << endl; + //cout << "uvSetIndex: " << uvSetIndex << endl; + //cout << "set: " << set << endl; + //cout << "texCoordSets.size(): " << unsigned(texCoordSets.size()) << endl; + //cout << "v: " << v << endl; + //cout << "lookUp.size(): " << unsigned(lookUp.size()) << endl; + //cout << "texCoordSets[uvSetIndex].texCoords.size(): " << unsigned(texCoordSets[uvSetIndex].texCoords.size()) << endl; + //Search for matching texture cooridnate + bool match_found = false; + for ( unsigned tc_index = 0; tc_index < texCoordSets[uvSetIndex].texCoords.size(); ++tc_index ) { + if ( texCoordSets[uvSetIndex].texCoords[tc_index] == newCoord ) { + ////cout << " Match found, using existing index" << endl;; + //Match found, use existing index + lookUp[v].uvIndices[uvSetIndex] = tc_index; + match_found = true; + ////cout << "Stop searching" << endl; + //Stop searching + break; + } + } + + ////cout << "Done with loop, check if match was found" << endl; + if ( match_found == false ) { + ////cout << "No match found" << endl; + //No match found, add this texture coordinate to the list + texCoordSets[uvSetIndex].texCoords.push_back( newCoord ); + ////cout << "Record new index" << endl; + //Record new index + lookUp[v].uvIndices[uvSetIndex] = unsigned(texCoordSets[uvSetIndex].texCoords.size()) - 1; + } + } + } + + //cout << "Look up table colors:" << endl; + for ( unsigned z = 0; z < lookUp.size(); ++z ) { + //cout << z << ": " << colors[lookUp[z].colorIndex] << endl; + } + + //cout << "Use look up table to build list of faces" << endl; + //Use look up table to build list of faces + for ( unsigned t = 0; t < shapeTris.size(); ++t ) { + ComplexFace newFace; + newFace.propGroupIndex = prop_group_index; + newFace.points.resize(3); + const Triangle & tri = shapeTris[t]; + for ( unsigned p = 0; p < 3; ++p ) { + if ( shapeVerts.size() != 0 ) { + newFace.points[p].vertexIndex = lookUp[ tri[p] ].vertIndex; + } + if ( shapeNorms.size() != 0 ) { + newFace.points[p].normalIndex = lookUp[ tri[p] ].normIndex; + } + if ( shapeColors.size() != 0 ) { + newFace.points[p].colorIndex = lookUp[ tri[p] ].colorIndex; + } + for ( map<unsigned,unsigned>::iterator set = lookUp[ tri[p] ].uvIndices.begin(); set != lookUp[ tri[p] ].uvIndices.end(); ++set ) { + TexCoordIndex tci; + tci.texCoordSetIndex = set->first; + tci.texCoordIndex = set->second; + newFace.points[p].texCoordIndices.push_back(tci); + } + } + faces.push_back(newFace); + } + + //cout << "Use look up table to set vertex wights, if any" << endl; + //Use look up table to set vertex weights, if any + NiSkinInstanceRef skinInst = (*geom)->GetSkinInstance(); + + if ( skinInst != NULL ) { + + NiSkinDataRef skinData = skinInst->GetSkinData(); + + if ( skinData !=NULL ) { + //Get influence list + vector<NiNodeRef> shapeBones = skinInst->GetBones(); + + //Get weights + vector<SkinWeight> shapeWeights; + for ( unsigned b = 0; b < shapeBones.size(); ++b ) { + shapeWeights = skinData->GetBoneWeights(b); + for ( unsigned w = 0; w < shapeWeights.size(); ++w ) { + unsigned vn_index = lookUp[ shapeWeights[w].index ].vertIndex; + NiNodeRef boneRef = shapeBones[b]; + float weight = shapeWeights[w].weight; + + vns[vn_index].weights[boneRef] = weight; + } + } + } + } + + //Next Shape + ++prop_group_index; + } + + //cout << "Finished with all shapes. Build up a list of influences" << endl; + //Finished with all shapes. Build up a list of influences + map<NiNodeRef,unsigned> boneLookUp; + for ( unsigned v = 0; v < vns.size(); ++v ) { + for ( map<NiNodeRef,float>::iterator w = vns[v].weights.begin(); w != vns[v].weights.end(); ++w ) { + boneLookUp[w->first] = 0; //will change later + } + } + + skinInfluences.resize( boneLookUp.size() ); + unsigned si_index = 0; + for ( map<NiNodeRef,unsigned>::iterator si = boneLookUp.begin(); si != boneLookUp.end(); ++si ) { + si->second = si_index; + skinInfluences[si_index] = si->first; + ++si_index; + } + + //cout << "Copy vns data to vertices and normals" << endl; + //Copy vns data to vertices and normals + if ( has_any_verts ) { + vertices.resize( vns.size() ); + } + if ( has_any_norms ) { + normals.resize( vns.size() ); + } + + for ( unsigned v = 0; v < vns.size(); ++v ) { + if ( has_any_verts ) { + vertices[v].position = vns[v].position; + vertices[v].weights.resize( vns[v].weights.size() ); + unsigned weight_index = 0; + for ( map<NiNodeRef,float>::iterator w = vns[v].weights.begin(); w != vns[v].weights.end(); ++w ) { + vertices[v].weights[weight_index].influenceIndex = boneLookUp[w->first]; + vertices[v].weights[weight_index].weight = w->second; + ++weight_index; + } + } + if ( has_any_norms ) { + normals[v] = vns[v].normal; + } + } + //cout << "Done Merging" << endl; +} + //void ComplexShape::CombineTriShapes( list<blk_ref> & tri_shapes ) { // //Clear all internal datea // _vertices.clear(); diff --git a/ComplexShape.h b/ComplexShape.h index 24600ab910a07299e14c677ce88597d9d8938d6e..8ee5d835c215aa3ab311102adafb836f3fc15f52 100644 --- a/ComplexShape.h +++ b/ComplexShape.h @@ -17,11 +17,16 @@ using namespace std; class NiProperty; class NiNode; class NiAVObject; +class NiTriBasedGeom; + +const unsigned CS_NO_INDEX = 0xFFFFFFFF; class ComplexShape { public: struct SkinInfluence { + SkinInfluence() : influenceIndex(CS_NO_INDEX) {} + ~SkinInfluence() {} unsigned int influenceIndex; float weight; }; @@ -32,11 +37,15 @@ public: }; struct TexCoordIndex { + TexCoordIndex() : texCoordSetIndex(CS_NO_INDEX), texCoordIndex(CS_NO_INDEX) {} + ~TexCoordIndex() {} unsigned int texCoordSetIndex; unsigned int texCoordIndex; }; struct ComplexPoint { + ComplexPoint() : vertexIndex(CS_NO_INDEX), normalIndex(CS_NO_INDEX), colorIndex(CS_NO_INDEX) {} + ~ComplexPoint() {} unsigned int vertexIndex; unsigned int normalIndex; unsigned int colorIndex; @@ -44,6 +53,8 @@ public: }; struct ComplexFace { + ComplexFace() : propGroupIndex(CS_NO_INDEX) {} + ~ComplexFace() {} vector<ComplexPoint> points; unsigned int propGroupIndex; }; @@ -54,6 +65,8 @@ public: }; Ref<NiAVObject> Split( Ref<NiNode> & parent ) const; + void Merge( const Ref<NiAVObject> & root ); + void Clear(); //Setters void SetName( const string & n ); diff --git a/nif_math.cpp b/nif_math.cpp index 19c59a7cfaffa152e364eecc91557bda99a59bcc..91b94d57afeb7d9f68353dd405779e2fb118b245 100644 --- a/nif_math.cpp +++ b/nif_math.cpp @@ -359,6 +359,16 @@ bool Matrix44::operator==( const Matrix44 & rh ) const { return true; } +bool Matrix44::operator!=( const Matrix44 & rh ) const { + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 4; j++) { + if ( (*this)[i][j] == rh[i][j] ) + return false; + } + } + return true; +} + Matrix44 Matrix44::Transpose() const { const Matrix44 & t = *this; return Matrix44( t[0][0], t[0][1], t[0][2], t[0][3], diff --git a/nif_math.h b/nif_math.h index 7e3d21e74d6163d2283b26ae6bb846575f3bb995..36044cc19a5a179c55b79f102498afa9aecf4b0e 100644 --- a/nif_math.h +++ b/nif_math.h @@ -735,6 +735,13 @@ struct Matrix44 { */ NIFLIB_API bool operator==( const Matrix44 & rh ) const; + /* Compares two 4x4 matricies. They are considered inequal if any corresponding + * components are inequal. + * \param rh The matrix to compare this one with. + * \return true if the matricies are inequal, false otherwise. + */ + NIFLIB_API bool operator!=( const Matrix44 & rh ) const; + /*! Calculates the transpose of this matrix. * \return The transpose of this matrix. */ @@ -856,6 +863,14 @@ struct NIFLIB_API Color4 { this->b = b; this->a = a; } + + bool operator==( const Color4 & n ) const { + return ( r == n.r && g == n.g && b == n.b && a == n.a ); + } + + bool operator!=( const Color4 & n ) const { + return ( r != n.r || g != n.g || b != n.b || a != n.a ); + } }; /*! Represents a quaternion - a 4D extention of complex numbers used as an alternitive to matrices to represent rotation.*/ diff --git a/obj/NiNode.cpp b/obj/NiNode.cpp index d3191aa6e69610d049640cc3a9048c2f3973fdfe..3c6e3de8876a745a32f01e3276b773b536087a32 100644 --- a/obj/NiNode.cpp +++ b/obj/NiNode.cpp @@ -211,4 +211,32 @@ void NiNode::GoToSkeletonBindPosition() { } } } +} + +bool NiNode::IsSplitMeshProxy() const { + //Let us guess that a node is a split mesh proxy if: + // 1) All its children are NiTriBasedGeom derived objects. + // 2) All its children have identity transforms. + // 3) It has more than one child + // 4) All meshes are visible + // 5) ???? May need more criteria as time goes on. + + if ( children.size() < 2 ) { + return false; + } + + for ( unsigned i = 0; i < children.size(); ++i ) { + if ( children[i]->IsDerivedType( NiTriBasedGeom::TypeConst() ) == false ) { + return false; + } + if ( children[i]->GetLocalTransform() != Matrix44::IDENTITY ) { + return false; + } + if ( children[i]->GetVisibility() == false ) { + return false; + } + } + + //Made it all the way through the loop without returning false + return true; } \ No newline at end of file diff --git a/obj/NiNode.h b/obj/NiNode.h index ef282a8814a0f0149665f88c62a05c8fb9f7c509..2bcb6ee708171f49a23556cfd7d1f1e6bb1e43da 100644 --- a/obj/NiNode.h +++ b/obj/NiNode.h @@ -63,6 +63,15 @@ public: /*! Checks if this node influences the vertices in any skins. */ bool IsSkinInfluence() const; + /*! + * Applies a huristic to guess whether this node was created as a proxy + * when a mesh which had more than one material in the original model + * was split in an exporter. + * /return Whether or not this node is probably a split mesh proxy + */ + bool IsSplitMeshProxy() const; + + /*! Causes all children's transforms to be changed so that all the skin * pieces line up without any vertex transformations. */