diff --git a/vcg/complex/algorithms/local_optimization/tri_edge_collapse_quadric_tex.h b/vcg/complex/algorithms/local_optimization/tri_edge_collapse_quadric_tex.h new file mode 100644 index 00000000..0d3976ad --- /dev/null +++ b/vcg/complex/algorithms/local_optimization/tri_edge_collapse_quadric_tex.h @@ -0,0 +1,777 @@ +/**************************************************************************** +* VCGLib o o * +* Visual and Computer Graphics Library o o * +* _ O _ * +* Copyright(C) 2004 \/)\/ * +* Visual Computing Lab /\/| * +* ISTI - Italian National Research Council | * +* \ * +* All rights reserved. * +* * +* This program is free software; you can redistribute it and/or modify * +* it under the terms of the GNU General Public License as published by * +* the Free Software Foundation; either version 2 of the License, or * +* (at your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * +* GNU General Public License (http://www.gnu.org/licenses/gpl.txt) * +* for more details. * +* * +****************************************************************************/ + + + + +#ifndef __VCG_TRIMESHCOLLAPSE_QUADRIC_TEX_ +#define __VCG_TRIMESHCOLLAPSE_QUADRIC_TEX_ + +#include +#include +#include +#include +namespace vcg +{ +namespace tri +{ + + +class TriEdgeCollapseQuadricTexParameter : public BaseParameterClass +{ +public: + double QualityThr; // all + double BoundaryWeight; + double NormalThr; + double CosineThr; + double QuadricEpsilon; + double ScaleFactor; + float ExtraTCoordWeight; + bool UseArea; + bool UseVertexWeight; + bool NormalCheck; + bool QualityCheck; + bool QualityQuadric; + bool OptimalPlacement; + bool MemoryLess; + bool ComplexCheck; + bool ScaleIndependent; +//*********************** + bool PreserveTopology; + bool PreserveBoundary; + bool MarkComplex; + bool SafeHeapUpdate; + + TriEdgeCollapseQuadricTexParameter() + { + SetDefaultParams(); + } + + void SetDefaultParams() + { + UseArea=true; + UseVertexWeight=false; + NormalCheck=false; + NormalThr=M_PI/2; + QualityCheck=true; + QualityThr=.1; + BoundaryWeight=.5; + OptimalPlacement=true; + ScaleIndependent=true; + ComplexCheck=false; + QuadricEpsilon =1e-15; + ScaleFactor=1.0; + ExtraTCoordWeight=0.0; + QualityQuadric = false; + PreserveTopology = false; + } +}; + + +// This is a static class that contains the references for the simple temporary data for the current mesh. +// for each vertex we keep a classic Quadric3D and a vector of pairs texcoord+Quadric5D + +template +class QuadricTexHelper + { + public: + typedef typename MeshType::VertexType VertexType; + + typedef SimpleTempData > > > Quadric5Temp; + typedef SimpleTempData > QuadricTemp; + + QuadricTexHelper(){} + + + static void Init(){} + + // it allocs the std::pair for the vertex relativly to the texture coord parameter + static void Alloc(VertexType *v,vcg::TexCoord2f &coord) + { + std::vector > > &qv = Vd(v); + Quadric5 newq5; + newq5.Zero(); + vcg::TexCoord2f newcoord; + newcoord.u() = coord.u(); + newcoord.v() = coord.v(); + + newq5.Sum3(Qd3(v),coord.u(),coord.v()); + + qv.push_back(std::pair >(newcoord,newq5)); + } + + static void SumAll(VertexType *v,vcg::TexCoord2f &coord, Quadric5& q) + { + std::vector > > &qv = Vd(v); + + for(int i = 0; i < qv.size(); i++) + { + vcg::TexCoord2f &f = qv[i].first; + if((f.u() == coord.u()) && (f.v() == coord.v())) + qv[i].second += q; + else + qv[i].second.Sum3(Qd3(v),f.u(),f.v()); + } + } + + static bool Contains(VertexType *v,vcg::TexCoord2f &coord) + { + std::vector > > &qv = Vd(v); + + for(int i = 0; i < qv.size(); i++) + { + vcg::TexCoord2f &f = qv[i].first; + if((f.u() == coord.u()) && (f.v() == coord.v())) + return true; + } + + return false; + } + + static Quadric5 &Qd(VertexType *v,vcg::TexCoord2f &coord) + { + std::vector > > &qv = Vd(v); + + for(int i = 0; i < qv.size(); i++) + { + vcg::TexCoord2f &f = qv[i].first; + if((f.u() == coord.u()) && (f.v() == coord.v())) + return qv[i].second; + } + + assert(0); + } + static math::Quadric &Qd3(VertexType *v) {return TD3()[*v];} + static math::Quadric &Qd3(VertexType &v) {return TD3()[v];} + + static std::vector > > &Vd(VertexType *v){return (TD()[*v]);} + static typename VertexType::ScalarType W(VertexType * /*v*/) {return 1.0;} + static typename VertexType::ScalarType W(VertexType & /*v*/) {return 1.0;} + static void Merge(VertexType & /*v_dest*/, VertexType const & /*v_del*/){} + static Quadric5Temp* &TDp() {static Quadric5Temp *td; return td;} + static Quadric5Temp &TD() {return *TDp();} + static QuadricTemp* &TDp3() {static QuadricTemp *td3; return td3;} + static QuadricTemp &TD3() {return *TDp3();} + }; + + + + + + + +template > +class TriEdgeCollapseQuadricTex: public vcg::tri::TriEdgeCollapse< TriMeshType, VertexPair, MYTYPE> +{ + typedef HelperType QH; + typedef typename tri::TriEdgeCollapse::HeapType HeapType; + typedef typename tri::TriEdgeCollapse::HeapElem HeapElem; + typedef typename TriMeshType::FaceType FaceType; + typedef typename TriMeshType::VertexType VertexType; + typedef typename TriMeshType::CoordType CoordType; + typedef typename TriMeshType::CoordType::ScalarType ScalarType; + typedef typename TriMeshType::VertexPointer VertexPointer; + + + public: + + inline TriEdgeCollapseQuadricTex(const VertexPair &p, int mark, BaseParameterClass *_pp) + { + TriEdgeCollapseQuadricTexParameter *pp = (TriEdgeCollapseQuadricTexParameter *)_pp; + this->localMark = mark; + this->pos=p; + this->_priority = ComputePriority(pp); + } + +// puntatori ai vertici che sono stati messi non-w per preservare il boundary + static std::vector & WV(){ + static std::vector _WV; return _WV; + }; + + + + static TriEdgeCollapseQuadricTexParameter & Params(){static TriEdgeCollapseQuadricTexParameter p; return p;} + + // Final Clean up after the end of the simplification process + static void Finalize(TriMeshType &m,HeapType & /*h_ret*/, BaseParameterClass *_pp) + { + TriEdgeCollapseQuadricTexParameter *pp = (TriEdgeCollapseQuadricTexParameter *)_pp; + vcg::tri::UpdateFlags::FaceBorderFromVF(m); + + // If we had the boundary preservation we should clean up the writable flags + if(pp->PreserveBoundary) + { + typename std::vector::iterator wvi; + for(wvi=WV().begin();wvi!=WV().end();++wvi) + if(!(*wvi)->IsD()) (*wvi)->SetW(); + } + } + + + + + inline static int matchVertexID(FaceType *f,VertexType *v) + { + if(f->V(0)==v) return 0; + if(f->V(1)==v) return 1; + if(f->V(2)==v) return 2; + + assert(0); return -1; + } + + inline int GetTexCoords(vcg::TexCoord2f &tcoord0_1,vcg::TexCoord2f &tcoord1_1,vcg::TexCoord2f &tcoord0_2,vcg::TexCoord2f &tcoord1_2) + { + int ncoords = 0; + + vcg::face::VFIterator vfi(this->pos.V(0)); + + for(vfi.F() = this->pos.V(0)->VFp(), vfi.I() = this->pos.V(0)->VFi(); vfi.F()!=0; ++vfi ) // for all faces in v0 + if(vfi.F()->V(0)==this->pos.V(1) || vfi.F()->V(1)==this->pos.V(1) || vfi.F()->V(2)==this->pos.V(1) ) // and v1 + { + if(ncoords == 0) + { + tcoord0_1 = vfi.F()->WT(matchVertexID(vfi.F(),this->pos.V(0))); + tcoord1_1 = vfi.F()->WT(matchVertexID(vfi.F(),this->pos.V(1))); + } + else + { + tcoord0_2 = vfi.F()->WT(matchVertexID(vfi.F(),this->pos.V(0))); + tcoord1_2 = vfi.F()->WT(matchVertexID(vfi.F(),this->pos.V(1))); + + if( + (tcoord0_1.u() == tcoord0_2.u()) && + (tcoord0_1.v() == tcoord0_2.v()) && + (tcoord1_1.u() == tcoord1_2.u()) && + (tcoord1_1.v() == tcoord1_2.v()) + ) + return 1; + else + return 2; + } + + ncoords++; + } + + return ncoords; + } + + + ScalarType ComputePriority(BaseParameterClass *_pp) + { + TriEdgeCollapseQuadricTexParameter *pp = (TriEdgeCollapseQuadricTexParameter *)_pp; + Quadric5 qsum1; + Quadric5 qsum2; + double min1[5]; + double min2[5]; + vcg::TexCoord2f tcoord0_1; + vcg::TexCoord2f tcoord1_1; + vcg::TexCoord2f tcoord0_2; + vcg::TexCoord2f tcoord1_2; + int ncoords; + + ncoords = GetTexCoords(tcoord0_1,tcoord1_1,tcoord0_2,tcoord1_2); + + return (ScalarType)ComputeMinimalsAndPriority(min1,min2,qsum1,qsum2,tcoord0_1,tcoord1_1,tcoord0_2,tcoord1_2,ncoords,pp); + } + + + /* + * the very important function that says how good is a collapse. + Originally is should be just the quadric error (e.g. you should choose the collapse that make the minimal quadric error) + but important correcting factors has to be applyed + - quality of the involved triangles + - normal checking + */ + ScalarType ComputeTexPriority(double vv[5],Quadric5 &qsum, BaseParameterClass *_pp) + { + TriEdgeCollapseQuadricTexParameter *pp = (TriEdgeCollapseQuadricTexParameter *)_pp; + VertexType * v[2]; + v[0] = this->pos.V(0); + v[1] = this->pos.V(1); + + //// Move the two vertexe into new position (storing the old ones) + CoordType OldPos0=v[0]->P(); + CoordType OldPos1=v[1]->P(); + + v[0]->P() = CoordType(vv[0],vv[1],vv[2]); + //v[0]->P() = (v[0]->P()+v[1]->P())/2.0; + v[1]->P()=v[0]->P(); + + double QuadErr = qsum.Apply(vv); + + //// Rescan faces and compute quality and difference between normals + int i; + + double qt, MinQual = 1e100; + vcg::face::VFIterator x(this->pos.V(0)); + + double ndiff,MinCos = 1e100; // minimo coseno di variazione di una normale della faccia + // (e.g. max angle) Mincos varia da 1 (normali coincidenti) a + // -1 (normali opposte); + + for(x.F() = v[0]->VFp(), x.I() = v[0]->VFi(),i=0; x.F()!=0; ++x ) // for all faces in v0 + if(x.F()->V(0)!=v[1] && x.F()->V(1)!=v[1] && x.F()->V(2)!=v[1] ) // skip faces with v1 + { + qt= QualityFace(*x.F()); + if(qtNormalCheck){ + Point3f nn=NormalizedNormal(*x.F()); + ndiff=nn.dot(x.F()->N()) / x.F()->N().Norm(); + if(ndiffVFp(), x.I() = v[1]->VFi(),i=0; x.F()!=0; ++x ) // for all faces in v1 + if(x.F()->V(0)!=v[0] && x.F()->V(1)!=v[0] && x.F()->V(2)!=v[0] ) // skip faces with v0 + { + qt= QualityFace(*x.F()); + if(qtNormalCheck){ + Point3f nn=NormalizedNormal(*x.F()); + ndiff=nn.dot(x.F()->N() / x.F()->N().Norm()); + if(ndiff have no penalty; + if(MinQual>pp->QualityThr) MinQual=pp->QualityThr; + if(QuadErr<1e-15) QuadErr=1e-15; // Do not use zero quality penalties + + this->_priority = (ScalarType)(QuadErr / MinQual); // the priority of collapses that create low quality triangles has a penalty (it is increased) + + + if(pp->NormalCheck){ + if(MinCosCosineThr) this->_priority *=1000; // gross penalty to collapses that move too much the original normals. + } + + + + //Restore old position of v0 and v1 + v[0]->P()=OldPos0; + v[1]->P()=OldPos1; + return this->_priority; + } + + inline ScalarType ComputeMinimalsAndPriority(double dest_1[5], + double dest_2[5], + Quadric5 &qsum_1, + Quadric5 &qsum_2, + vcg::TexCoord2f &tcoord0_1, + vcg::TexCoord2f &tcoord1_1, + vcg::TexCoord2f &tcoord0_2, + vcg::TexCoord2f &tcoord1_2, + int ncoords, + BaseParameterClass *_pp) + { + TriEdgeCollapseQuadricTexParameter *pp = (TriEdgeCollapseQuadricTexParameter *)_pp; + double tmp1[5]; + double tmp2[5]; + ScalarType priority1; + ScalarType priority2; + + tmp1[0] = this->pos.V(0)->P().X(); + tmp1[1] = this->pos.V(0)->P().Y(); + tmp1[2] = this->pos.V(0)->P().Z(); + tmp1[3] = tcoord0_1.u(); + tmp1[4] = tcoord0_1.v(); + + tmp2[0] = this->pos.V(1)->P().X(); + tmp2[1] = this->pos.V(1)->P().Y(); + tmp2[2] = this->pos.V(1)->P().Z(); + tmp2[3] = tcoord1_1.u(); + tmp2[4] = tcoord1_1.v(); + + + assert(QH::Qd(this->pos.V(0),tcoord0_1).IsValid()); + assert(QH::Qd(this->pos.V(1),tcoord1_1).IsValid()); + + qsum_1 = QH::Qd(this->pos.V(0),tcoord0_1); + qsum_1 += QH::Qd(this->pos.V(1),tcoord1_1); + + ComputeMinimal(dest_1,tmp1,tmp2,qsum_1,pp); + priority1 = ComputeTexPriority(dest_1,qsum_1,pp); + + if(ncoords < 2) + return priority1*(1 + (pp->ExtraTCoordWeight)*(QH::Vd(this->pos.V(0)).size()+ QH::Vd(this->pos.V(1)).size() - 2)); + + + tmp1[3] = tcoord0_2.u(); + tmp1[4] = tcoord0_2.v(); + + tmp2[3] = tcoord1_2.u(); + tmp2[4] = tcoord1_2.v(); + + assert(QH::Qd(this->pos.V(0),tcoord0_2).IsValid()); + assert(QH::Qd(this->pos.V(1),tcoord1_2).IsValid()); + + qsum_2 = QH::Qd(this->pos.V(0),tcoord0_2); + qsum_2 += QH::Qd(this->pos.V(1),tcoord1_2); + + ComputeMinimal(dest_2,tmp1,tmp2,qsum_2,pp); + priority2 = ComputeTexPriority(dest_2,qsum_2,pp); + + if(priority1 > priority2) + { + ComputeMinimalWithGeoContraints(dest_2,tmp1,tmp2,qsum_2,dest_1,pp); + priority2 = ComputeTexPriority(dest_2,qsum_2,pp); + } + else + { + ComputeMinimalWithGeoContraints(dest_1,tmp1,tmp2,qsum_1,dest_2,pp); + priority1 = ComputeTexPriority(dest_1,qsum_1,pp); + } + + + this->_priority = max(priority1, priority2)*(1 + (pp->ExtraTCoordWeight)*(QH::Vd(this->pos.V(0)).size()+QH::Vd(this->pos.V(1)).size() - 2)); + + return this->_priority; + } + + inline void ComputeMinimal(double vv[5],double v0[5],double v1[5], Quadric5 qsum,BaseParameterClass *_pp) + { + tri::TriEdgeCollapseQuadricParameter *pp =(tri::TriEdgeCollapseQuadricParameter *)_pp; + bool rt=qsum.Minimum(vv); + // if the computation of the minimum fails we choose between the two edge points and the middle one. + // Switch to this branch also in the case of not using the optimal placement. + if(!rt || !pp->OptimalPlacement ) { + + vv[0] = (v0[0] + v1[0])/2; + vv[1] = (v0[1] + v1[1])/2; + vv[2] = (v0[2] + v1[2])/2; + vv[3] = (v0[3] + v1[3])/2; + vv[4] = (v0[4] + v1[4])/2; + + // In the case of not using the optimal placement we have to be sure that the middle value is discarded. + double qvx= std::numeric_limits::max(); + if(pp->OptimalPlacement) + qvx = qsum.Apply(vv); + + + double qv0=qsum.Apply(v0); + double qv1=qsum.Apply(v1); + + + if(qv0 qsum, double geo[5],BaseParameterClass *_pp) + { + tri::TriEdgeCollapseQuadricParameter *pp =(tri::TriEdgeCollapseQuadricParameter *)_pp; + bool rt=qsum.MinimumWithGeoContraints(vv,geo); + // if the computation of the minimum fails we choose between the two edge points and the middle one. + // Switch to this branch also in the case of not using the optimal placement. + if(!rt || !pp->OptimalPlacement) { + double qvx = std::numeric_limits::max(); + vv[0] = geo[0]; + vv[1] = geo[1]; + vv[2] = geo[2]; + if(pp->OptimalPlacement) + { + vv[3] = (v0[3] + v1[3])/2; + vv[4] = (v0[4] + v1[4])/2; + qvx=qsum.Apply(vv); + } + vv[3] = v0[3]; + vv[4] = v0[4]; + + double qv0=qsum.Apply(vv); + + vv[3] = v1[3]; + vv[4] = v1[4]; + + double qv1=qsum.Apply(v1); + + vv[3] = (v0[3] + v1[3])/2; + vv[4] = (v0[4] + v1[4])/2; + + if(qv0IsR() &&(*pf).V(1)->IsR() &&(*pf).V(2)->IsR()) + { + Quadric5 q; + q.byFace(*pf, QH::Qd3((*pf).V(0)), QH::Qd3((*pf).V(1)), QH::Qd3((*pf).V(2)),pp->QualityQuadric); + + for(int j=0;j<3;++j) + if( (*pf).V(j)->IsW()) + { + if(!HelperType::Contains((*pf).V(j),(*pf).WT(j))) + { + HelperType::Alloc((*pf).V(j),(*pf).WT(j)); + } + + HelperType::SumAll((*pf).V(j),(*pf).WT(j),q); + } + + } + + } + + static void Init(TriMeshType &m,HeapType&h_ret,BaseParameterClass *_pp) + { + tri::TriEdgeCollapseQuadricParameter *pp =(tri::TriEdgeCollapseQuadricParameter *)_pp; + typename TriMeshType::VertexIterator vi; + typename TriMeshType::FaceIterator pf; + + vcg::tri::UpdateTopology::VertexFace(m); + vcg::tri::UpdateFlags::FaceBorderFromVF(m); + + if(pp->PreserveBoundary ) + { + WV().clear(); + for(pf=m.face.begin();pf!=m.face.end();++pf) + if( !(*pf).IsD() && (*pf).IsW() ) + for(int j=0;j<3;++j) + if((*pf).IsB(j)) + { + if((*pf).V(j)->IsW()) {(*pf).V(j)->ClearW(); WV().push_back((*pf).V(j));} + if((*pf).V1(j)->IsW()) {(*pf).V1(j)->ClearW();WV().push_back((*pf).V1(j));} + } + } + + InitQuadric(m,pp); + + // Initialize the heap with all the possible collapses + for(vi=m.vert.begin();vi!=m.vert.end();++vi) + if((*vi).IsRW()) + { + vcg::face::VFIterator x; + for( x.F() = (*vi).VFp(), x.I() = (*vi).VFi(); x.F()!=0; ++ x){ + x.V1()->ClearV(); + x.V2()->ClearV(); + } + for( x.F() = (*vi).VFp(), x.I() = (*vi).VFi(); x.F()!=0; ++x ) + { + assert(x.F()->V(x.I())==&(*vi)); + if((x.V0()IsRW() && !x.V1()->IsV()){ + x.V1()->SetV(); + h_ret.push_back(HeapElem(new MYTYPE(VertexPair(x.V0(),x.V1()),TriEdgeCollapse< TriMeshType,VertexPair,MYTYPE>::GlobalMark(),pp ))); + } + if((x.V0()IsRW()&& !x.V2()->IsV()){ + x.V2()->SetV(); + h_ret.push_back(HeapElem(new MYTYPE(VertexPair(x.V0(),x.V2()),TriEdgeCollapse< TriMeshType,VertexPair,MYTYPE>::GlobalMark(),pp ))); + } + } + } + make_heap(h_ret.begin(),h_ret.end()); + } + + inline void UpdateHeap(HeapType & h_ret,BaseParameterClass *_pp) + { + tri::TriEdgeCollapseQuadricParameter *pp =(tri::TriEdgeCollapseQuadricParameter *)_pp; + this->GlobalMark()++; + VertexType *v[2]; + v[0]= this->pos.V(0); + v[1]= this->pos.V(1); + v[1]->IMark() = this->GlobalMark(); + + // First loop around the remaining vertex to unmark visited flags + vcg::face::VFIterator vfi(v[1]); + + while (!vfi.End()){ + vfi.V1()->ClearV(); + vfi.V2()->ClearV(); + ++vfi; + } + + // Second Loop + vfi = face::VFIterator(v[1]); + while (!vfi.End()) + { + assert(!vfi.F()->IsD()); + for (int j=0;j<3;j++) + { + + if( !(vfi.V1()->IsV()) && vfi.V1()->IsRW()) + { + vfi.V1()->SetV(); + + h_ret.push_back(HeapElem(new MYTYPE(VertexPair(vfi.V0(),vfi.V1()), this->GlobalMark(),pp))); + std::push_heap(h_ret.begin(),h_ret.end()); + } + + if( !(vfi.V2()->IsV()) && vfi.V2()->IsRW()) + { + vfi.V2()->SetV(); + + h_ret.push_back(HeapElem(new MYTYPE(VertexPair(vfi.V0(),vfi.V2()),this->GlobalMark(),pp))); + std::push_heap(h_ret.begin(),h_ret.end()); + } + } + ++vfi; + } + } + + void Execute(TriMeshType &m, BaseParameterClass *_pp) + { + tri::TriEdgeCollapseQuadricParameter *pp =(tri::TriEdgeCollapseQuadricParameter *)_pp; + Quadric5 qsum1; + Quadric5 qsum2; + double min1[5]; + double min2[5]; + vcg::TexCoord2f tcoord0_1; + vcg::TexCoord2f tcoord1_1; + vcg::TexCoord2f tcoord0_2; + vcg::TexCoord2f tcoord1_2; + vcg::TexCoord2f newtcoord1; + vcg::TexCoord2f newtcoord2; + std::vector > > qv; + int ncoords; + VertexType * v[2]; + v[0] = this->pos.V(0); + v[1] = this->pos.V(1); + + math::Quadric qsum3 = QH::Qd3(v[0]); + qsum3 += QH::Qd3(v[1]); + + ncoords = GetTexCoords(tcoord0_1,tcoord1_1,tcoord0_2,tcoord1_2); + + ComputeMinimalsAndPriority(min1,min2,qsum1,qsum2,tcoord0_1,tcoord1_1,tcoord0_2,tcoord1_2,ncoords,pp); + + CoordType newPos(min1[0],min1[1],min1[2]); /* it's the same as min2[0],min2[1],min2[2] since the geometrical + constraint has been imposed during the re-computation of the other minimal */ + + + EdgeCollapser::Do(m, this->pos, newPos); + //DoCollapse(m, this->pos, newPos ); // v0 is deleted and v1 take the new position + + vcg::TexCoord2f newtcoord; + Quadric5 newq; + + + + newtcoord.u() = (float)min1[3]; + newtcoord.v() = (float)min1[4]; + newtcoord1 = newtcoord; + newq = qsum1; + + qv.push_back(std::pair >(newtcoord,newq)); + + if(ncoords > 1) + { + newtcoord.u() = min2[3]; + newtcoord.v() = min2[4]; + newtcoord2 = newtcoord; + newq = qsum2; + + qv.push_back(std::pair >(newtcoord2,newq)); + } + + + vcg::face::VFIterator vfi(v[1]); + while (!vfi.End()) + { + vcg::TexCoord2f & tcoords = vfi.F()->WT(matchVertexID(vfi.F(),v[1])); + + if( + ((tcoords.u() == tcoord0_1.u()) && (tcoords.v() == tcoord0_1.v())) || + ((tcoords.u() == tcoord1_1.u()) && (tcoords.v() == tcoord1_1.v())) + ) + { + tcoords.u() = newtcoord1.u(); + tcoords.v() = newtcoord1.v(); + } + else if( + (ncoords > 1) && + ( + ((tcoords.u() == tcoord0_2.u()) && (tcoords.v() == tcoord0_2.v())) || + ((tcoords.u() == tcoord1_2.u()) && (tcoords.v() == tcoord1_2.v())) + ) + ) + { + tcoords.u()= newtcoord2.u(); + tcoords.v()= newtcoord2.v(); + } + else + { + newtcoord = tcoords; + + if(QH::Contains(v[0],tcoords)) + { + newq = QH::Qd(v[0],tcoords); + newq.Sum3(QH::Qd3(v[1]),tcoords.u(),tcoords.v()); + } + else if(QH::Contains(v[1],tcoords)) + { + newq = QH::Qd(v[1],tcoords); + newq.Sum3(QH::Qd3(v[0]),tcoords.u(),tcoords.v()); + } + else + assert(0); + + qv.push_back(std::pair >(newtcoord,newq)); + } + + ++vfi; + } + QH::Qd3(v[1]) = qsum3; + QH::Vd(v[1]) = qv; + + } + +}; + + + + } // namespace tri + } // namespace vcg +#endif