/**************************************************************************** * VCGLib o o * * Visual and Computer Graphics Library o o * * _ O _ * * Copyright(C) 2004-2016 \/)\/ * * 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 __VCGLIB_IMPORT_OFF #define __VCGLIB_IMPORT_OFF #include #include #include #include namespace vcg { namespace tri { namespace io { // /** \addtogroup */ // /* @{ */ /** This class encapsulate a filter for importing OFF meshes. A basic description of the OFF file format can be found at http://www.geomview.org/docs/html/geomview_41.html */ template class ImporterOFF { public: typedef typename MESH_TYPE::VertexType VertexType; typedef typename MESH_TYPE::VertexIterator VertexIterator; typedef typename MESH_TYPE::VertexPointer VertexPointer; typedef typename MESH_TYPE::FaceType FaceType; typedef typename MESH_TYPE::FaceIterator FaceIterator; typedef typename MESH_TYPE::FacePointer FacePointer; typedef typename MESH_TYPE::CoordType CoordType; typedef typename MESH_TYPE::ScalarType ScalarType; // OFF codes enum OFFCodes { NoError = 0, CantOpen, InvalidFile, InvalidFile_MissingOFF, UnsupportedFormat, ErrorNotTriangularFace, ErrorHighDimension, ErrorDegenerateFace }; /*! * Standard call for knowing the meaning of an error code * \param message_code The code returned by Open * \return The string describing the error code */ static const char* ErrorMsg(int message_code) { static const char* error_msg[] = { "No errors", "Can't open file", "Invalid file", "Invalid file: OFF file should have in the first line the OFF keyword as a first token", "Unsupported format", "Face with more than 3 vertices","File with high dimensional vertexes are not supported", "Error Degenerate Face with less than 3 vertices" }; if (message_code > 6 || message_code < 0) return "Unknown error"; else return error_msg[message_code]; }; /** * Load only the properties of the 3D objects. * * \param filename the name of the file to read from * \param loadmask the mask which encodes the properties * \return the operation result */ static bool LoadMask(const char *filename, int &loadmask) { // To obtain the loading mask all the file must be parsed // to distinguish between per-vertex and per-face color attribute. loadmask = 0; MESH_TYPE dummyMesh; return (Open(dummyMesh, filename, loadmask) == NoError); } static int Open(MESH_TYPE &mesh, const char *filename, CallBackPos *cb = 0) { int loadmask; return Open(mesh, filename, loadmask, cb); } static int OpenMem(MESH_TYPE &mesh, const char *mem, size_t sz, int &loadmask, CallBackPos *cb = 0) { std::string str; str.append(mem, sz); std::istringstream strm(str); return OpenStream(mesh, strm, loadmask, cb); } /*! * Standard call for reading a mesh. * * \param mesh the destination mesh * \param filename the name of the file to read from * \return the operation result */ static int Open(MESH_TYPE &mesh, const char *filename, int &loadmask, CallBackPos *cb = 0) { std::ifstream stream(filename); if (stream.fail()) return CantOpen; return OpenStream(mesh, stream, loadmask, cb); } static int OpenStream(MESH_TYPE &mesh, std::istream &stream, int &loadmask, CallBackPos *cb = 0) { std::vector< std::string > tokens; TokenizeNextLine(stream, tokens); if (tokens.empty()) return InvalidFile_MissingOFF; bool isNormalDefined = false; bool isColorDefined = false; bool isTexCoordDefined = false; int dimension = 3; bool homogeneousComponents = false; /* [ST][C][N][4][n]OFF # Header keyword [Ndim] # Space dimension of vertices, present only if nOFF NVertices NFaces NEdges # NEdges not used or checked x[0] y[0] z[0] # Vertices, possibly with normals, colors, and/or texture coordinates, in that order, if the prefixes N, C, ST are present. # If 4OFF, each vertex has 4 components including a final homogeneous component. # If nOFF, each vertex has Ndim components. # If 4nOFF, each vertex has Ndim+1 components. ... x[NVertices-1] y[NVertices-1] z[NVertices-1] # Faces # Nv = # vertices on this face # v[0] ... v[Nv-1]: vertex indices # in range 0..NVertices-1 Nv v[0] v[1] ... v[Nv-1] colorspec ... # colorspec continues past v[Nv-1] to end-of-line; may be 0 to 4 numbers # nothing: default # integer: colormap index # 3 or 4 integers: RGB[A] values 0..255 # 3 or 4 floats: RGB[A] values 0..1 */ std::string header = tokens[0]; if (header.rfind("OFF") != std::basic_string::npos) { // the OFF string is in the header go on parsing it. for (int u = static_cast(header.rfind("OFF") - 1); u >= 0; u--) { if (header[u] == 'C') isColorDefined = true; else if (header[u] == 'N') isNormalDefined = true; else if (u > 0 && header[u - 1] == 'S' && header[u] == 'T') isTexCoordDefined = true; else if (header[u] == '4') homogeneousComponents = true; else if (header[u] == 'n') return ErrorHighDimension; } } else return InvalidFile_MissingOFF; // If the file is slightly malformed and it has nvert and nface AFTER the OFF string instead of in the next line // we manage it here... if (tokens.size() == 1) TokenizeNextLine(stream, tokens); else tokens.erase(tokens.begin(), tokens.begin() + 1); // Update loading mask /////////////////////////////////////// loadmask = Mask::IOM_VERTCOORD | Mask::IOM_FACEINDEX; if (isNormalDefined) loadmask |= Mask::IOM_VERTNORMAL; if (isTexCoordDefined) loadmask |= Mask::IOM_VERTTEXCOORD; //if (isColorDefined) { loadmask |= Mask::IOM_VERTCOLOR;loadmask |= Mask::IOM_FACECOLOR;} //if(onlyMaskFlag) return NoError; mesh.Clear(); // check on next 2 lines to detect corrupted files if (tokens.size() < 3) return InvalidFile; unsigned int nVertices, nFaces, nEdges; nVertices = atoi(tokens[0].c_str()); nFaces = atoi(tokens[1].c_str()); nEdges = atoi(tokens[2].c_str()); // dimension is the space dimension of vertices => it must be three(!) if (dimension != 3) return UnsupportedFormat; if (homogeneousComponents) return UnsupportedFormat; // READ VERTICES ////////////////////////////////////////////////////// VertexIterator v_iter = Allocator::AddVertices(mesh, nVertices); TokenizeNextLine(stream, tokens); size_t k = 0; // next token to read for (unsigned int i = 0; i < nVertices; i++, v_iter++) { if (cb && (i % 1000) == 0) cb(i * 50 / nVertices, "Vertex Loading"); // Read 3 vertex coordinates for (unsigned int j = 0; j < 3; j++) { // Go to next line when needed if (k == tokens.size()) // if EOL { TokenizeNextLine(stream, tokens); if (tokens.size() == 0) // if EOF return InvalidFile; k = 0; } // Read vertex coordinate (*v_iter).P()[j] = (ScalarType)atof(tokens[k].c_str()); k++; } if (isNormalDefined) { // Read 3 normal coordinates for (unsigned int j = 0; j < 3; j++) { // Go to next line when needed if (k == tokens.size()) // if EOL { TokenizeNextLine(stream, tokens); if (tokens.size() == 0) // if EOF return InvalidFile; k = 0; } // Read normal coordinate (*v_iter).N()[j] = (ScalarType)atof(tokens[k].c_str()); k++; } } // NOTE: It is assumed that colored vertex takes exactly one text line // (otherwise it is impossible to parse color information since // color components can vary) if (isColorDefined) { // The number of color components varies from 0 to 4. // The OFF format guaranties that there is 1 vertex per line. int nb_color_components = static_cast(tokens.size()) - static_cast(k) /* tokens already parsed */ - 2 * (isTexCoordDefined ? 1 : 0); if (nb_color_components < 0 || nb_color_components > 4) return InvalidFile; // set per-vertex color attribute if (nb_color_components > 0) loadmask |= Mask::IOM_VERTCOLOR; // Store color components if (tri::HasPerVertexColor(mesh)) { // Read color components if (nb_color_components == 1) { // read color index (*v_iter).C().Import(ColorMap(atoi(tokens[k].c_str()))); } else if (nb_color_components == 3) { // read RGB color if (tokens[k].find(".") == size_t(-1))// if it is a float there is a dot { // integers unsigned char r = static_cast(atoi(tokens[k].c_str())); unsigned char g = static_cast(atoi(tokens[k + 1].c_str())); unsigned char b = static_cast(atoi(tokens[k + 2].c_str())); vcg::Color4b color(r, g, b, 255); (*v_iter).C().Import(color); } else { // floats float r = static_cast(atof(tokens[k].c_str())); float g = static_cast(atof(tokens[k + 1].c_str())); float b = static_cast(atof(tokens[k + 2].c_str())); vcg::Color4f color(r, g, b, 1.0); (*v_iter).C().Import(color); } } else if (nb_color_components == 4) { // read RGBA color if (tokens[k].find(".") == size_t(-1)) { // integers unsigned char r = static_cast(atoi(tokens[k].c_str())); unsigned char g = static_cast(atoi(tokens[k + 1].c_str())); unsigned char b = static_cast(atoi(tokens[k + 2].c_str())); unsigned char a = static_cast(atoi(tokens[k + 3].c_str())); Color4b color(r, g, b, a); (*v_iter).C().Import(color); } else { // floats float r = static_cast(atof(tokens[k].c_str())); float g = static_cast(atof(tokens[k + 1].c_str())); float b = static_cast(atof(tokens[k + 2].c_str())); float a = static_cast(atof(tokens[k + 3].c_str())); vcg::Color4f color(r, g, b, a); (*v_iter).C().Import(color); } } } k += nb_color_components; } if (isTexCoordDefined) { for (unsigned int j = 0; j < 2; j++) { // Go to next line when needed if (k == tokens.size()) // if EOL { TokenizeNextLine(stream, tokens); if (tokens.size() == 0) // if EOF return InvalidFile; k = 0; } std::string str = tokens[k]; k++; // Store texture coordinates if (tri::HasPerWedgeTexCoord(mesh)) { //...TODO... } } } } // for i=... // READ FACES ////////////////////////////////////////////////////// if (FaceType::HasPolyInfo()) { for (unsigned int f = 0; f < nFaces; f++) { if (cb && (f % 1000) == 0) cb(50 + f * 50 / nFaces, "Face Loading"); TokenizeNextLine(stream, tokens); int vert_per_face = atoi(tokens[0].c_str()); std::vector vInd(vert_per_face); k = 1; for (int j = 0; j < vert_per_face; j++) { if (k == tokens.size()) // if EOL // Go to next line when needed { TokenizeNextLine(stream, tokens); if (tokens.size() == 0) return InvalidFile; // if EOF k = 0; } vInd[j] = atoi(tokens[k].c_str()); k++; } if (vert_per_face == 3) Allocator::AddFace(mesh, &mesh.vert[vInd[0]], &mesh.vert[vInd[1]], &mesh.vert[vInd[2]]); if (vert_per_face == 4) Allocator::AddQuadFace(mesh, &mesh.vert[vInd[0]], &mesh.vert[vInd[1]], &mesh.vert[vInd[2]], &mesh.vert[vInd[3]]); } } else // Standard Triangular Mesh Loading { Allocator::AddFaces(mesh, nFaces); unsigned int f0 = 0; // Initial call to the QuadTriangulate with an empty vector to just reset the static set of existing diagonals std::vector qtmp; BitQuad::QuadTriangulate(qtmp); for (unsigned int f = 0; f < nFaces; f++) { f0 = f; if (stream.fail()) return InvalidFile; if (cb && (f % 1000) == 0) cb(50 + f * 50 / nFaces, "Face Loading"); TokenizeNextLine(stream, tokens); int vert_per_face = atoi(tokens[0].c_str()); if (vert_per_face < 3) return ErrorDegenerateFace; k = 1; if (vert_per_face == 3) { for (int j = 0; j < 3; j++) { if (k == tokens.size()) // if EOL // Go to next line when needed { TokenizeNextLine(stream, tokens); if (tokens.size() == 0) return InvalidFile; // if EOF k = 0; } mesh.face[f].V(j) = &(mesh.vert[atoi(tokens[k].c_str())]); k++; } } else { // The face must be triangulated unsigned int trigs = vert_per_face - 3; // number of extra faces to add nFaces += trigs; Allocator::AddFaces(mesh, trigs); std::vector vertIndices(vert_per_face); std::vector polygonVect(vert_per_face); // vec of polygon loops used for the triangulation of polygonal face for (int j = 0; j < vert_per_face; j++) { if (k == tokens.size()) // if EOL // Go to next line when needed { TokenizeNextLine(stream, tokens); if (tokens.size() == 0) return InvalidFile; // if EOF k = 0; } vertIndices[j] = atoi(tokens[k].c_str()); polygonVect[j].Import(mesh.vert[vertIndices[j]].P()); k++; } if (vert_per_face == 4) { // To well triangulate use the bitquad support function that reorders vertex for a simple fan std::vector q(4); for (int qqi = 0; qqi < 4; ++qqi) q[qqi] = &mesh.vert[vertIndices[qqi]]; BitQuad::QuadTriangulate(q); for (int qqi = 0; qqi < 4; ++qqi) vertIndices[qqi] = q[qqi] - &mesh.vert[0]; // build a two face fan for (int j = 0; j < 2; j++) { mesh.face[f + j].V(0) = &(mesh.vert[vertIndices[0]]); mesh.face[f + j].V(1) = &(mesh.vert[vertIndices[1 + j]]); mesh.face[f + j].V(2) = &(mesh.vert[vertIndices[2 + j]]); if (tri::HasPerFaceFlags(mesh)) { // tag internal polygonal edges as "faux" if (j > 0) mesh.face[f + j].SetF(0); if (j < vert_per_face - 3) mesh.face[f + j].SetF(2); loadmask |= Mask::IOM_BITPOLYGONAL; } } } else // standard fan triangulation (we hope the polygon is convex...) { std::vector indexTriangulatedVect; // TessellatePlanarPolygon3(polygonVect,indexTriangulatedVect); std::vector< std::vector > loopVect; loopVect.push_back(polygonVect); #ifdef __gl_h_ //qDebug("OK: using opengl tessellation for a polygon of %i vertices",vertexesPerFace); vcg::glu_tesselator::tesselate(loopVect, indexTriangulatedVect); #else //qDebug("Warning: using fan tessellation for a polygon of %i vertices",vertexesPerFace); tri::io::FanTessellator(loopVect, indexTriangulatedVect); #endif for (size_t j = 0; j < indexTriangulatedVect.size(); j += 3) { mesh.face[f + j / 3].V(0) = &(mesh.vert[vertIndices[indexTriangulatedVect[j + 0]]]); mesh.face[f + j / 3].V(1) = &(mesh.vert[vertIndices[indexTriangulatedVect[j + 1]]]); mesh.face[f + j / 3].V(2) = &(mesh.vert[vertIndices[indexTriangulatedVect[j + 2]]]); // To correctly set Faux edges we have to clear the faux bit for all the edges that do not correspond to consecutive vertices // Consecutivity is in the space of the index of the polygon. for (int qq = 0; qq < 3; ++qq) { if ((indexTriangulatedVect[j + qq] + 1) % vert_per_face == indexTriangulatedVect[j + (qq + 1) % 3]) mesh.face[f + j / 3].ClearF(qq); else mesh.face[f + j / 3].SetF(qq); } } } f += trigs; } // NOTE: It is assumed that colored face takes exactly one text line // (otherwise it is impossible to parse color information since // color components can vary) size_t color_elements = tokens.size() - vert_per_face - 1; //isColorDefined |= (color_elements>0); //if(isColorDefined) loadmask |= Mask::IOM_FACECOLOR; if (color_elements > 0) { loadmask |= Mask::IOM_FACECOLOR; if (tri::HasPerFaceColor(mesh)) { switch (color_elements) { case 0: { for (; f0 <= f; f0++) mesh.face[f0].C().Import(vcg::Color4f(.666f, .666f, .666f, .666f)); break; } case 1: { for (; f0 <= f; f0++) mesh.face[f0].C().Import(ColorMap(atoi(tokens[vert_per_face + 1].c_str()))); break; } case 3: { if (tokens[vert_per_face + 1].find('.') == std::string::npos) // if there is a float there is a dot { Color4b cc(Color4b::White); cc[0] = (unsigned char)atoi(tokens[vert_per_face + 1].c_str()); cc[1] = (unsigned char)atoi(tokens[vert_per_face + 2].c_str()); cc[2] = (unsigned char)atoi(tokens[vert_per_face + 3].c_str()); for (; f0 <= f; f0++) mesh.face[f0].C() = cc; } else { float color[3]; color[0] = (float)atof(tokens[vert_per_face + 1].c_str()); color[1] = (float)atof(tokens[vert_per_face + 2].c_str()); color[2] = (float)atof(tokens[vert_per_face + 3].c_str()); for (; f0 <= f; f0++) mesh.face[f0].C().Import(vcg::Color4f(color[0], color[1], color[2], 1.0f)); } break; } case 4: { if (tokens[vert_per_face + 1].find('.') == std::string::npos) // if it is a float there is a dot { Color4b cc; cc[0] = (unsigned char)atoi(tokens[vert_per_face + 1].c_str()); cc[1] = (unsigned char)atoi(tokens[vert_per_face + 2].c_str()); cc[2] = (unsigned char)atoi(tokens[vert_per_face + 3].c_str()); cc[3] = (unsigned char)atoi(tokens[vert_per_face + 4].c_str()); for (; f0 <= f; f0++) mesh.face[f0].C() = cc; } else { float color[4]; color[0] = float(atof(tokens[vert_per_face + 1].c_str())); color[1] = float(atof(tokens[vert_per_face + 2].c_str())); color[2] = float(atof(tokens[vert_per_face + 3].c_str())); color[3] = float(atof(tokens[vert_per_face + 4].c_str())); for (; f0 <= f; f0++) mesh.face[f0].C().Import(vcg::Color4f(color[0], color[1], color[2], color[3])); } break; } } //end switch } } // end if (isColorDefined) } // end of for f=... } return NoError; } // end Open protected: /*! * Read the next valid line and parses it into "tokens", allowing the tokens to be read one at a time. * \param stream The object providing the input stream * \param tokens The "tokens" in the next line */ inline static void TokenizeNextLine(std::istream &stream, std::vector< std::string > &tokens) { std::string line; do std::getline(stream, line, '\n'); while ((line[0] == '#' || line.length() == 0 || line[0] == '\r') && (!stream.eof())); size_t from = 0; size_t to = 0; size_t length = line.size(); tokens.clear(); do { while (from != length && (line[from] == ' ' || line[from] == '\t' || line[from] == '\r')) from++; if (from != length) { to = from + 1; while (to != length && (((line[to] != ' ') && (line[to] != '\t')) || (line[to] == '\r'))) to++; tokens.push_back(line.substr(from, to - from).c_str()); from = to; } } while (from < length); } // end Tokenize /*! * Provide the int->color mapping, according to the Geomview's `cmap.fmap' file. * \param i the color index * \return the corresponding vcg::Color4f color */ static const vcg::Color4f ColorMap(int i) { static const float colorMap[148][4] = { { 1.0f, 1.0f, 1.0f, 1.0f }, { 1.0f, 1.0f, 1.0f, 1.0f }, { 1.0f, 1.0f, 1.0f, 1.0f }, { 1.0f, 1.0f, 1.0f, 1.0f }, { 1.0f, 1.0f, 1.0f, 1.0f }, { 1.0f, 1.0f, 1.0f, 1.0f }, { 0.7f, 0.7f, 0.7f, 0.7f }, { 0.2f, 0.2f, 0.2f, 0.2f }, { 0.9f, 0.9f, 0.9f, 0.9f }, { 0.1f, 0.1f, 0.1f, 0.1f }, { 0.1f, 0.1f, 0.1f, 0.1f }, { 0.8f, 0.8f, 0.8f, 0.8f }, { 0.7f, 0.7f, 0.7f, 0.7f }, { 0.7f, 0.7f, 0.7f, 0.7f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.9f, 0.9f, 0.9f, 0.9f }, { 0.2f, 0.2f, 0.2f, 0.2f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.75f, 0.75f, 0.75f, 0.75f }, { 0.8f, 0.8f, 0.8f, 0.8f }, { 0.8f, 0.8f, 0.8f, 0.8f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.4f, 0.4f, 0.4f, 0.4f }, { 0.4f, 0.4f, 0.4f, 0.4f }, { 0.8f, 0.8f, 0.8f, 0.8f }, { 0.8f, 0.8f, 0.8f, 0.8f }, { 0.7f, 0.7f, 0.7f, 0.7f }, { 0.7f, 0.7f, 0.7f, 0.7f }, { 0.7f, 0.7f, 0.7f, 0.7f }, { 0.7f, 0.7f, 0.7f, 0.7f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.9f, 0.9f, 0.9f, 0.9f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.75f, 0.75f, 0.75f, 0.75f }, { 0.8f, 0.8f, 0.8f, 0.8f }, { 0.4f, 0.4f, 0.4f, 0.4f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.4f, 0.4f, 0.4f, 0.4f }, { 0.8f, 0.8f, 0.8f, 0.8f }, { 0.7f, 0.7f, 0.7f, 0.7f }, { 0.7f, 0.7f, 0.7f, 0.7f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.9f, 0.9f, 0.9f, 0.9f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.75f, 0.75f, 0.75f, 0.75f }, { 0.8f, 0.8f, 0.8f, 0.8f }, { 0.4f, 0.4f, 0.4f, 0.4f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.4f, 0.4f, 0.4f, 0.4f }, { 0.8f, 0.8f, 0.8f, 0.8f }, { 0.7f, 0.7f, 0.7f, 0.7f }, { 0.7f, 0.7f, 0.7f, 0.7f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.9f, 0.9f, 0.9f, 0.9f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.75f, 0.75f, 0.75f, 0.75f }, { 0.8f, 0.8f, 0.8f, 0.8f }, { 0.4f, 0.4f, 0.4f, 0.4f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.4f, 0.4f, 0.4f, 0.4f }, { 0.8f, 0.8f, 0.8f, 0.8f }, { 1.0f, 1.0f, 1.0f, 1.0f }, { 1.0f, 1.0f, 1.0f, 1.0f }, { 1.0f, 1.0f, 1.0f, 1.0f }, { 1.0f, 1.0f, 1.0f, 1.0f }, { 1.0f, 1.0f, 1.0f, 1.0f }, { 1.0f, 1.0f, 1.0f, 1.0f }, { 0.05f, 0.05f, 0.05f, 0.05f }, { 0.7f, 0.7f, 0.7f, 0.7f }, { 0.2f, 0.2f, 0.2f, 0.2f }, { 0.9f, 0.9f, 0.9f, 0.9f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.1f, 0.1f, 0.1f, 0.1f }, { 0.8f, 0.8f, 0.8f, 0.8f }, { 0.7f, 0.7f, 0.7f, 0.7f }, { 0.7f, 0.7f, 0.7f, 0.7f }, { 0.7f, 0.7f, 0.7f, 0.7f }, { 0.7f, 0.7f, 0.7f, 0.7f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.9f, 0.9f, 0.9f, 0.9f }, { 0.9f, 0.9f, 0.9f, 0.9f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.75f, 0.75f, 0.75f, 0.75f }, { 0.75f, 0.75f, 0.75f, 0.75f }, { 0.8f, 0.8f, 0.8f, 0.8f }, { 0.8f, 0.8f, 0.8f, 0.8f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.4f, 0.4f, 0.4f, 0.4f }, { 0.4f, 0.4f, 0.4f, 0.4f }, { 0.8f, 0.8f, 0.8f, 0.8f }, { 0.8f, 0.8f, 0.8f, 0.8f }, { 0.7f, 0.7f, 0.7f, 0.7f }, { 0.7f, 0.7f, 0.7f, 0.7f }, { 0.7f, 0.7f, 0.7f, 0.7f }, { 0.7f, 0.7f, 0.7f, 0.7f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.9f, 0.9f, 0.9f, 0.9f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.75f, 0.75f, 0.75f, 0.75f }, { 0.8f, 0.8f, 0.8f, 0.8f }, { 0.4f, 0.4f, 0.4f, 0.4f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.4f, 0.4f, 0.4f, 0.4f }, { 0.8f, 0.8f, 0.8f, 0.8f }, { 0.7f, 0.7f, 0.7f, 0.7f }, { 0.7f, 0.7f, 0.7f, 0.7f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.9f, 0.9f, 0.9f, 0.9f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.75f, 0.75f, 0.75f, 0.75f }, { 0.8f, 0.8f, 0.8f, 0.8f }, { 0.4f, 0.4f, 0.4f, 0.4f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.4f, 0.4f, 0.4f, 0.4f }, { 0.8f, 0.8f, 0.8f, 0.8f }, { 0.7f, 0.7f, 0.7f, 0.7f }, { 0.7f, 0.7f, 0.7f, 0.7f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.9f, 0.9f, 0.9f, 0.9f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.75f, 0.75f, 0.75f, 0.75f }, { 0.8f, 0.8f, 0.8f, 0.8f }, { 0.4f, 0.4f, 0.4f, 0.4f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f, 0.0f }, { 0.4f, 0.4f, 0.4f, 0.4f }, { 0.8f, 0.8f, 0.8f, 0.8f } }; return Color4f(colorMap[i][0], colorMap[i][1], colorMap[i][2], colorMap[i][3]); } }; // /*! @} */ } //namespace io }//namespace tri } // namespace vcg #endif //__VCGLIB_IMPORT_OFF