/**************************************************************************** * 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. * * * ****************************************************************************/ /**************************************************************************** History $Log: not supported by cvs2svn $ Revision 1.2 2004/07/12 15:57:33 ganovelli first draft: it includes glew ! ****************************************************************************/ #ifndef __VCG_GLTRIMESH #define __VCG_GLTRIMESH #include #include #include #include #include namespace vcg { // classe base di glwrap usata solo per poter usare i vari drawmode, normalmode senza dover // specificare tutto il tipo (a volte lunghissimo) // della particolare classe glwrap usata. class GLW { public: enum DrawMode {DMNone, DMBox, DMPoints, DMWire, DMHidden, DMFlat, DMSmooth, DMFlatWire, DMRadar, DMLast} ; enum NormalMode{NMNone, NMPerVert, NMPerFace, NMPerWedge, NMLast}; enum ColorMode {CMNone, CMPerMesh, CMPerFace, CMPerVert, CMLast}; enum TextureMode {TMNone, TMPerVert, TMPerWedge, TMPerWedgeMulti}; enum Hint { HNUseTriStrip = 0x0001, // ha bisogno che ci sia la fftopology gia calcolata! // HNUseEdgeStrip = 0x0002, // HNUseDisplayList = 0x0004, HNCacheDisplayList = 0x0008, // Each mode has its dl; HNLazyDisplayList = 0x0010, // Display list are generated only when requested HNIsTwoManifold = 0x0020, // There is no need to make DetachComplex before . HNUsePerWedgeNormal = 0x0040, // HNHasFFTopology = 0x0080, // E' l'utente che si preoccupa di tenere aggiornata la topologia ff HNHasVFTopology = 0x0100, // E' l'utente che si preoccupa di tenere aggiornata la topologia vf HNHasVertNormal = 0x0200, // E' l'utente che si preoccupa di tenere aggiornata le normali per faccia HNHasFaceNormal = 0x0400, // E' l'utente che si preoccupa di tenere aggiornata le normali per vertice HNUseVArray = 0x0800, HNUseLazyEdgeStrip = 0x1000, // Edge Strip are generated only when requested HNUseVBO = 0x2000 // Use Vertex Buffer Object }; enum Change { CHVertex = 0x01, CHNormal = 0x02, CHColor = 0x04, CHFace = 0x08, CHFaceNormal= 0x10, CHAll = 0xff }; enum HintParami { HNPDisplayListSize =0 }; enum HintParamf { HNPCreaseAngle =0, // crease angle in radians HNPZTwist = 1 // Z offset used in Flatwire and hiddenline modality }; template class VertToSplit { public: typename MESH_TYPE::face_base_pointer f; char z; char edge; bool newp; typename MESH_TYPE::vertex_pointer v; }; // GL Array Elemet class GLAElem { public : int glmode; int len; int start; }; }; template > class GlTrimesh : public GLW { public: MESH_TYPE *m; GLWrap(){ m=0; dl=0xffffffff; h=HNUseLazyEdgeStrip; cdm=DMNone; ccm=CMNone; cnm=NMNone; SetHintParamf(HNPCreaseAngle,float(M_PI/5)); SetHintParamf(HNPZTwist,0.00005f); } typedef MESH_TYPE mesh_type; FACE_POINTER_CONTAINER face_pointers; unsigned int b[3]; int h; // the current hints // The parameters of hints int HNParami[8]; float HNParamf[8]; void SetHintParami(const HintParami hip, const int value) { HNParamI[hip]=value; } int GetHintParami(const HintParami hip) const { return HNParamI[hip]; } void SetHintParamf(const HintParamf hip, const float value) { HNParamf[hip]=value; } float GetHintParamf(const HintParamf hip) const { return HNParamf[hip]; } void SetHint(Hint hn) { h |= hn; } void ClearHint(Hint hn) { h&=(~hn); } unsigned int dl; std::vector indices; DrawMode cdm; // Current DrawMode NormalMode cnm; // Current NormalMode ColorMode ccm; // Current ColorMode void Update(Change c=CHAll) { if(m==0) return; if(h&HNUseVArray){ MESH_TYPE::FaceIterator fi; indices.clear(); for(fi = m->face.begin(); fi != m->face.end(); ++fi) { indices.push_back((unsigned int)((*fi).V(0) - &(*m->vert.begin()))); indices.push_back((unsigned int)((*fi).V(1) - &(*m->vert.begin()))); indices.push_back((unsigned int)((*fi).V(2) - &(*m->vert.begin()))); } if(h&HNUseVBO){ if(!glIsBuffer(b[1])) glGenBuffers(2,b); glBindBuffer(GL_ARRAY_BUFFER,b[0]); glBufferData(GL_ARRAY_BUFFER_ARB, m->vn * sizeof(MESH_TYPE::VertexType), (char *)&(m->vert[0].P()), GL_STATIC_DRAW_ARB); glBindBuffer(GL_ARRAY_BUFFER,b[1]); glBufferData(GL_ARRAY_BUFFER_ARB, m->vn * sizeof(MESH_TYPE::VertexType), (char *)&(m->vert[0].N()), GL_STATIC_DRAW_ARB); } glVertexPointer(3,GL_FLOAT,sizeof(MESH_TYPE::VertexType),0); glNormalPointer(GL_FLOAT,sizeof(MESH_TYPE::VertexType),0); } //int C=c; //if((C&CHVertex) || (C&CHFace)) { // ComputeBBox(*m); // if(!(h&HNHasFaceNormal)) m->ComputeFaceNormal(); // if(!(h&HNHasVertNormal)) m->ComputeVertexNormal(); // C= (C | CHFaceNormal); //} //if((C&CHFace) && (h&HNUseEdgeStrip)) ComputeEdges(); //if((C&CHFace) && (h&HNUseLazyEdgeStrip)) ClearEdges(); //if(MESH_TYPE::HasFFTopology()) // if((C&CHFace) && (h&HNUseTriStrip)) { // if(!(h&HNHasFFTopology)) m->FFTopology(); // ComputeTriStrip(); // } //if((C&CHFaceNormal) && (h&HNUsePerWedgeNormal)) { // if(!(h&HNHasVFTopology)) m->VFTopology(); // CreaseWN(*m,MESH_TYPE::scalar_type(GetHintParamf(HNPCreaseAngle))); //} //if(C!=0) { // force the recomputation of display list // cdm=DMNone; // ccm=CMNone; // cnm=NMNone; //} //if((h&HNUseVArray) && (h&HNUseTriStrip)) // { // ConvertTriStrip(*m,TStrip,TStripF,TStripVED,TStripVEI); // } } void Draw(DrawMode dm ,ColorMode cm, TextureMode tm) { switch(dm) { case DMNone : Draw(cm,tm); break; case DMBox : Draw(cm,tm); break; case DMPoints : Draw(cm,tm); break; case DMWire : Draw(cm,tm); break; case DMHidden : Draw(cm,tm); break; case DMFlat : Draw(cm,tm); break; case DMSmooth : Draw(cm,tm); break; case DMFlatWire: Draw(cm,tm); break; default : break; } } template< DrawMode dm > void Draw(ColorMode cm, TextureMode tm) { switch(cm) { case CMNone : Draw(tm); break; case CMPerMesh : Draw(tm); break; case CMPerFace : Draw(tm); break; case CMPerVert : Draw(tm); break; default : break; } } template< DrawMode dm, ColorMode cm > void Draw(TextureMode tm) { switch(tm) { case TMNone : Draw(); break; case TMPerVert : Draw(); break; case TMPerWedge : Draw(); break; case TMPerWedgeMulti : Draw(); break; default : break; } } template< DrawMode dm, ColorMode cm, TextureMode tm> void Draw() { if(!m) return; if((h & HNUseDisplayList)){ if (cdm==dm && ccm==cm){ glCallList(dl); return; } else { if(dl==0xffffffff) dl=glGenLists(1); glNewList(dl,GL_COMPILE); } } glPushMatrix(); switch(dm) { case DMNone : break; case DMBox : DrawBBox(cm);break; case DMPoints : DrawPoints();break; case DMHidden : DrawHidden();break; case DMFlat : DrawFill();break; case DMFlatWire : DrawFlatWire();break; case DMRadar : DrawRadar();break; case DMWire : DrawWire();break; case DMSmooth : DrawFill();break; default : break; } glPopMatrix(); if((h & HNUseDisplayList)){ cdm=dm; ccm=cm; glEndList(); glCallList(dl); } } /*********************************************************************************************/ /*********************************************************************************************/ template void DrawFill() { FACE_POINTER_CONTAINER::iterator fp; MESH_TYPE::FaceIterator fi; std::vector::iterator fip; short curtexname=-1; if(cm == CMPerMesh) glColor(m->C()); if(h&HNUseVArray) { if( (nm==NMPerVert) && ((cm==CMNone) || (cm==CMPerMesh))) { glEnableClientState (GL_NORMAL_ARRAY); glEnableClientState (GL_VERTEX_ARRAY); if(h&HNUseVBO){ glBindBuffer(GL_ARRAY_BUFFER,b[1]); glNormalPointer(GL_FLOAT,sizeof(MESH_TYPE::VertexType),0); glBindBuffer(GL_ARRAY_BUFFER,b[0]); glVertexPointer(3,GL_FLOAT,sizeof(MESH_TYPE::VertexType),0); } else { glNormalPointer(GL_FLOAT,sizeof(MESH_TYPE::VertexType),&m->vert[0].N()[0]); glVertexPointer(3,GL_FLOAT,sizeof(MESH_TYPE::VertexType),&m->vert[0].P()[0]); } glDrawElements(GL_TRIANGLES ,m->fn*3,GL_UNSIGNED_INT, &(*indices.begin()) ); glDisableClientState (GL_VERTEX_ARRAY); glDisableClientState (GL_NORMAL_ARRAY ); return; } } else if(h&HNUseTriStrip) { //if( (nm==NMPerVert) && ((cm==CMNone) || (cm==CMPerMesh))) // if(h&HNUseVArray){ // glEnableClientState (GL_NORMAL_ARRAY ); // glNormalPointer(GL_FLOAT,sizeof(MESH_TYPE::VertexType),&(m->vert[0].cN())); // glEnableClientState (GL_VERTEX_ARRAY); // glVertexPointer(3,GL_FLOAT,sizeof(MESH_TYPE::VertexType),&(m->vert[0].cP())); // std::vector::iterator vi; // for(vi=TStripVED.begin();vi!=TStripVED.end();++vi) // glDrawElements(vi->glmode ,vi->len,GL_UNSIGNED_SHORT,&TStripVEI[vi->start] ); // // glDisableClientState (GL_NORMAL_ARRAY ); // glDisableClientState (GL_VERTEX_ARRAY); // return; // } //std::vector< MESH_TYPE::VertexType *>::iterator vi; //glBegin(GL_TRIANGLE_STRIP); //if(nm == NMPerFace) fip=TStripF.begin(); //for(vi=TStrip.begin();vi!=TStrip.end(); ++vi){ // if((*vi)){ // if(nm==NMPerVert) glNormal((*vi)->cN()); // if(nm==NMPerFace) glNormal((*fip)->cN()); // glVertex((*vi)->P()); // } // else // { // glEnd(); // glBegin(GL_TRIANGLE_STRIP); // } // if(nm == NMPerFace) ++fip; // } //glEnd(); } else { glBegin(GL_TRIANGLES); if(partial) fp = face_pointers.begin(); else fi = m->face.begin(); while( (partial)?(fp!=face_pointers.end()):(fi!=m->face.end())) { MESH_TYPE::FaceType & f = (partial)?(*(*fp)): *fi; if(!f.IsD()){ //ATLTRACE("Drawing face\n"); //if(tm==TMPerWedgeMulti) // if(f.WT(0).n() != curtexname) // { // curtexname=(*fi).WT(0).n(); // glEnd(); // glBindTexture(GL_TEXTURE_2D,TMId(curtexname)); // glBegin(GL_TRIANGLES); // } if(nm == NMPerFace) glNormal(f.cN()); if(cm == CMPerFace) glColor(f.C()); if(nm==NMPerVert) glNormal(f.V(0)->cN()); if(nm==NMPerWedge)glNormal(f.WN(0)); if(cm==CMPerVert) glColor(f.V(0)->C()); // if(tm==TMPerVert) glTexCoord(f.V(0)->T()); if( (tm==TMPerWedge)|| (tm==TMPerWedgeMulti)) glTexCoord(f.WT(0).t(0)); glVertex(f.V(0)->P()); if(nm==NMPerVert) glNormal(f.V(1)->cN()); if(nm==NMPerWedge)glNormal(f.WN(1)); if(cm == CMPerVert) glColor(f.V(1)->C()); // if(tm==TMPerVert) glTexCoord(f.V(1)->T()); if( (tm==TMPerWedge)|| (tm==TMPerWedgeMulti)) glTexCoord(f.WT(1).t(0)); glVertex(f.V(1)->P()); if(nm==NMPerVert) glNormal(f.V(2)->cN()); if(nm==NMPerWedge)glNormal(f.WN(2)); if(cm == CMPerVert) glColor(f.V(2)->C()); // if(tm==TMPerVert) glTexCoord(f.V(2)->T()); if( (tm==TMPerWedge)|| (tm==TMPerWedgeMulti)) glTexCoord(f.WT(2).t(0)); glVertex(f.V(2)->P()); } if(partial) ++fp; else ++fi; } glEnd(); } } template void DrawPoints() { MESH_TYPE::VertexIterator vi; glBegin(GL_POINTS); if(cm==CMPerMesh) glColor(m->C()); for(vi=m->vert.begin();vi!=m->vert.end();++vi)if(!(*vi).IsD()) { if(nm==NMPerVert) glNormal((*vi).cN()); if(cm==CMPerVert) glColor((*vi).C()); glVertex((*vi).P()); } glEnd(); } void DrawHidden() { const float ZTWIST=HNParamf[HNPZTwist]; glDepthRange(ZTWIST,1.0f); glDisable(GL_LIGHTING); glColorMask(GL_FALSE,GL_FALSE,GL_FALSE,GL_FALSE); DrawFill(); glEnable(GL_LIGHTING); glColorMask(GL_TRUE,GL_TRUE,GL_TRUE,GL_TRUE); glDepthRange(0.0f,1.0f-ZTWIST); DrawWire(); glDepthRange(0,1.0f); } template void DrawFlatWire() { const float ZTWIST=HNParamf[HNPZTwist]; glDepthRange(ZTWIST,1.0f); DrawFill(); glDepthRange(0.0f,1.0f-ZTWIST); glPushAttrib(GL_CURRENT_BIT); glColor3f(.3f,.3f,.3f); DrawWire(); glPopAttrib(); glDepthRange(0,1.0f); } template void DrawRadar() { const float ZTWIST=HNParamf[HNPZTwist]; glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDepthMask(0); glDepthRange(ZTWIST,1.0f); if (cm == CMNone) glColor4f(0.2f, 1.0f, 0.4f, 0.2f); // DrawFill(); Draw(); glDepthMask(1); glColorMask(GL_FALSE,GL_FALSE,GL_FALSE,GL_FALSE); // DrawFill(); Draw(); glDepthRange(0.0f,1.0f-ZTWIST); glColorMask(GL_TRUE,GL_TRUE,GL_TRUE,GL_TRUE); glColor4f(0.1f, 1.0f, 0.2f, 0.6f); Draw(); glDisable(GL_BLEND); glDepthRange(0,1.0f); } #ifdef GL_TEXTURE0_ARB // Multitexturing nel caso voglia usare due texture unit. void DrawTexture_NPV_TPW2() { unsigned int texname=(*(m->face.begin())).WT(0).n(0); glBindTexture(GL_TEXTURE_2D,TMId(texname)); MESH_TYPE::FaceIterator fi; glBegin(GL_TRIANGLES); for(fi=m->face.begin();fi!=m->face.end();++fi)if(!(*fi).IsD()){ if(texname!=(*fi).WT(0).n(0)) { texname=(*fi).WT(0).n(0); glEnd(); glBindTexture(GL_TEXTURE_2D,TMId(texname)); glBegin(GL_TRIANGLES); } glMultiTexCoordARB(GL_TEXTURE0_ARB, (*fi).WT(0).t(0)); glMultiTexCoordARB(GL_TEXTURE1_ARB, (*fi).WT(0).t(0)); glNormal((*fi).V(0)->N()); glVertex((*fi).V(0)->P()); glMultiTexCoordARB(GL_TEXTURE0_ARB, (*fi).WT(1).t(0)); glMultiTexCoordARB(GL_TEXTURE1_ARB, (*fi).WT(1).t(0)); glNormal((*fi).V(1)->N()); glVertex((*fi).V(1)->P()); glMultiTexCoordARB(GL_TEXTURE0_ARB, (*fi).WT(2).t(0)); glMultiTexCoordARB(GL_TEXTURE1_ARB, (*fi).WT(2).t(0)); glNormal((*fi).V(2)->N()); glVertex((*fi).V(2)->P()); } glEnd(); } #endif int MemUsed() { int tot=sizeof(GLWrap); tot+=sizeof(edge_type)*edge.size(); tot+=sizeof(MESH_TYPE::VertexType *) * EStrip.size(); tot+=sizeof(MESH_TYPE::VertexType *) * TStrip.size(); tot+=sizeof(MESH_TYPE::FaceType *) * TStripF.size(); return tot; } private: template void DrawWire() { //if(!(h & (HNUseEdgeStrip | HNUseLazyEdgeStrip) ) ) // { glPushAttrib(GL_POLYGON_BIT); glPolygonMode(GL_FRONT_AND_BACK ,GL_LINE); DrawFill(); glPopAttrib(); // } //else // { // if(!HasEdges()) ComputeEdges(); //if(cm==CMPerMesh) glColor(m->C()); //std::vector< MESH_TYPE::VertexType *>::iterator vi; //glBegin(GL_LINE_STRIP); //for(vi=EStrip.begin();vi!=EStrip.end(); ++vi){ // if((*vi)){ // glNormal((*vi)->N()); // glVertex((*vi)->P()); // } // else // { // glEnd(); // glBegin(GL_LINE_STRIP); // } //} //glEnd(); // } } void DrawBBox(ColorMode cm) { if(cm==CMPerMesh) glColor(m->C()); // glBoxWire(m->bbox); } };// end class /* Crease Angle Assume che: la mesh abbia la topologia ff la mesh non abbia complex (o se li aveva fossero stati detached) Abbia le normali per faccia normalizzate!! Prende una mesh e duplica tutti gli edge le cui normali nelle facce incidenti formano un angolo maggiore di (espresso in rad). foreach face foreach unvisited vert vi scan the star of triangles around vi duplicating vi each time we encounter a crease angle. the new (and old) vertexes are put in a std::vector that is swapped with the original one at the end. */ // uncomment one of the following line to enable the Verbose Trace for Crease #define VCTRACE (void)0 //#define VCTRACE TRACE template void Crease(MESH_TYPE &m, typename MESH_TYPE::scalar_type angleRad) { assert(m.HasFFTopology()); MESH_TYPE::scalar_type cosangle=Cos(angleRad); std::vector > SPL; std::vector newvert; newvert.reserve(m.fn*3); // indica se un il vertice z della faccia e' stato processato enum {VISITED_0= MESH_TYPE::FaceType::USER0, VISITED_1= MESH_TYPE::FaceType::USER0<<1, VISITED_2= MESH_TYPE::FaceType::USER0<<2} ; int vis[3]={VISITED_0,VISITED_1,VISITED_2}; int _t2=clock(); MESH_TYPE::FaceIterator fi; for(fi=m.face.begin();fi!=m.face.end();++fi) if(!(*fi).IsD()) (*fi).Supervisor_Flags()&= (~(VISITED_0 | VISITED_1 | VISITED_2)); for(fi=m.face.begin();fi!=m.face.end();++fi) if(!(*fi).IsD()) for(int j=0;j<3;++j) if(!((*fi).Supervisor_Flags() & (vis[j]))) { //VCTRACE("Face %i Spinning around vertex %i\n",fi-m.face.begin(), (*fi).V(j)-m.vert.begin()); //(*fi).Supervisor_Flags() |= vis[j]; MESH_TYPE::hedgepos_type he(&*fi,j,(*fi).V(j)); MESH_TYPE::hedgepos_type she=he; MESH_TYPE::face_base_pointer nextf; GLW::VertToSplit spl; spl.newp=false; spl.edge=-1; //Primo giro per trovare un bordo da cui partire do { he.FlipF(); he.FlipE(); if(he.IsBorder()) break; } while(he!=she); if(he==she) // non c'e'bordi allora si cerca un crease { do { he.FlipF(); he.FlipE(); nextf=he.f->F(he.z); MESH_TYPE::scalar_type ps=nextf->N()*he.f->N(); if(psV(he.z)) vz=he.z; if(he.v == he.f->V((he.z+1)%3)) vz=(he.z+1)%3; assert((he.f->Supervisor_Flags() & vis[vz] )==0); } while(he!=she); } he.FlipE(); she=he; newvert.push_back(*(*fi).V(j)); MESH_TYPE::vertex_pointer curvert=&newvert.back(); // VCTRACE("Starting from face %i edge %i vert %i \n",he.f-m.face.begin(), he.z, he.v-m.vert.begin()); // Secondo giro in cui si riempie il vettore SPL con tutte le info per fare i nuovi vertici do{ //TRACE(" -- spinning face %i edge %i vert %i\n",he.f-m.face.begin(), he.z, he.v-m.vert.begin()); spl.v=curvert; spl.f=he.f; spl.z=-1; if(he.v == he.f->V(he.z)) spl.z=he.z; if(he.v == he.f->V((he.z+1)%3)) spl.z=(he.z+1)%3; assert(spl.z>=0); //VCTRACE(" -- spinning face vert %i Adding spl face %i vert %i\n",\ // he.v-m.vert.begin(), spl.f-m.face.begin(), spl.z ); assert((spl.f->Supervisor_Flags() & vis[spl.z] )==0); spl.f->Supervisor_Flags() |= vis[spl.z]; SPL.push_back(spl); spl.newp=false; spl.edge=-1; if(he.IsBorder()) break; nextf=he.f->F(he.z); if(nextf==she.f) break; MESH_TYPE::scalar_type ps=nextf->N()*he.f->N(); if(ps >::iterator vsi; for(vsi=SPL.begin();vsi!=SPL.end();++vsi) { (*vsi).f->V((*vsi).z)=(*vsi).v; if((*vsi).newp){ assert((*vsi).edge>=0 && (*vsi).edge<3); if(!(*vsi).f->IsBorder( (*vsi).edge) ) (*vsi).f->Detach((*vsi).edge); } } m.vert.math::Swap(newvert); m.vn=m.vert.size(); } /* Secondo tipo di crease angle. ha bisogno del per wedge normal e delle adiacence per vertice faccia gia fatte; Assume che le normali per faccia siano gia'state fatte (se ci sono) */ template void CreaseWN(MESH_TYPE &m, typename MESH_TYPE::scalar_type angle) { if(!(MESH_TYPE::FaceType::OBJ_TYPE & MESH_TYPE::FaceType::OBJ_TYPE_WN) ) { assert(0); // You needs a mesh with faces having per wedge normals return; } MESH_TYPE::scalar_type cosangle=Cos(angle); MESH_TYPE::FaceIterator fi; // Clear the per wedge normals for(fi=m.face.begin();fi!=m.face.end();++fi) if(!(*fi).IsD()) { (*fi).WN(0)=MESH_TYPE::vectorial_type(0,0,0); (*fi).WN(1)=MESH_TYPE::vectorial_type(0,0,0); (*fi).WN(2)=MESH_TYPE::vectorial_type(0,0,0); } MESH_TYPE::FaceType::vectorial_type nn; for(fi=m.face.begin();fi!=m.face.end();++fi) if(!(*fi).IsD()) { nn=(*fi).cN(); for(int i=0;i<3;++i) { VEdgePosB x; for(x.f = (*fi).V(i)->Fp(), x.z = (*fi).V(i)->Zp(); x.f!=0; x.NextF() ) { assert(x.f->V(x.z)==(*fi).V(i)); if(x.f->cN()*nn > cosangle) x.f->WN(x.z)+=nn; } } } } } // end namespace #endif