// Copyright (c) 2010 David W. Shattuck, PhD // $Id: dfsurface.h 154 2010-08-24 22:15:01Z shattuck $ #ifndef DFSurface_H #define DFSurface_H /** \brief BrainSuite Surface Format file reader classes \author David Shattuck \date $Date: 2010-08-24 15:15:01 -0700 (Tue, 24 Aug 2010) $ This file contains C++ code to read and write the BrainSuite surface file format (dfs). A DFS file contains the following layout: [ DFS Header ]
[ metadata ] embedded XML (not used)
[ subject data ] embedded XML (not used)
[ triangle data ] a list of triangles, specified by an (a,b,c) triple of 32-bit integer numbers
[ vertices ] a list of vertices, specified by an (x,y,z) triple of 32-bit floating point numbers
[ vertex normals ] vertex normal data (0 if not in file) (nx,ny,nz) in 32-bit floating point
[ vertex UV coordinates] surface parameterization data (u,v) in 32-bit floating point
[ vertex colors ] per vertex color data in (r,g,b) format in 32-bit floating point ([0-1])
[ vertex labels ] per vertex label index specified in 16-bit integer format
[ vertex attributes ] vertex attributes (float32 array of length NV)
The header portion contains information such as the number of triangles (NT), the number of vertices (NV), and pointers to each of the data dields. The metadata and subject data areas are not currently used, but the layout is designed to store embedded XML to allow a variety of information to be stored in the file. The triangle data is a vector of NT triangles. Each triangle consists of 3 32-bit integers, so this data block is NT * 3 * 4 bytes in length. Each triangle specifying a triple of vertex indices that point into the vertex data block. The vertex indices are specified starting from zero with a maximum value of (NV-1). The vertex data block is of length NV * 3 * 4, as each vertex is represented by a triple of 32-bit floating point numbers. All fields after the triangle and vertex data are optional. Each data field has NV elements, with the size of the element depending on the particular attribute. For example, UV coordinates are specified as pairs of 32-bit floating point numbers, so the vertex UV block will be of length NV * 2 * 4 if it is present. The label and attribute fields can be used to store various types of scalar data. For example, the labels may be used to indicate anatomical areas or regions of interest. Similarly, the vertex attribute may be used to store curvature measures or activation measures. */ #include #include #include #include namespace SILT { inline void endian_swap(unsigned short& x) /// These endian swap functions were obtained from CodeGuru C++ FAQ: /// http://www.codeguru.com/forum/showthread.php?t=292902 { x = (x>>8) | (x<<8); } inline void endian_swap(unsigned int& x) /// These endian swap functions were obtained from CodeGuru C++ FAQ: /// http://www.codeguru.com/forum/showthread.php?t=292902 { x = (x>>24) | ((x<<8) & 0x00FF0000) | ((x>>8) & 0x0000FF00) | (x<<24); } inline void endian_swap(int& x) /// This function recasts an signed 16-bit integer to use the above unsigned integer byte-swapping { endian_swap(*(unsigned int *)&x); } inline void endian_swap(float& x) /// This function recasts a 32-bit floating point number to use the above unsigned integer byte-swapping { endian_swap(*(unsigned int *)&x); } /** \brief ByteOrder provides two simple functions for testing if a system is big endian or little endian. \author David Shattuck */ class ByteOrder { public: static bool bigEndian() ///< returns true if the current system stores data in big endian format { short int word = 0x0001; char *byte = (char *) &word; return (!byte[0]); } static bool littleEndian() ///< returns true if the current system stores data in little endian format { short int word = 0x0001; char *byte = (char *) &word; return (byte[0]!=0); } }; /** \brief BrainSuite Surface Format (dfs) file header \author David Shattuck The DFSHeader consists of: (1) a 12 byte header identifying the file type, data endianness, and header version number.
(2) a series of 4-byte integers specifying the size of the header, the numbers of triangles and vertices stored in the file, and the locations of other data.
The triangle and vertex arrays are required. All other fields are optional, and some of the fields specified in the header are now deprecated (e.g., stripSize and nStrips). The field index represents the file position where the attribute vector can be read. If a field index is set to zero it indicates that the attributes for that field are not present in the file. */ class DFSHeader { public: DFSHeader() : ///< The constructor for DFSHeader sets the headerType field and the headerSize. All other values are set to zero. headerSize(sizeof(DFSHeader)), metadataOffset(0), subjectDataOffset(0), nTriangles(0), nVertices(0), nStrips(0), stripSize(0), vertexNormalsOffset(0), vertexUVOffset(0), vertexColorsOffset(0), vertexLabelsOffset(0), vertexAttributesOffset(0) { headerType[0]='D'; headerType[1]='F'; headerType[2]='S'; headerType[3]='_'; headerType[4]=(ByteOrder::bigEndian()) ? 'B' : 'L'; headerType[5]='E'; headerType[6]=' '; headerType[7]='v'; headerType[8]='2'; headerType[9]='.'; headerType[10]='0'; headerType[11]='\0'; } void swap() ///< performs byte-swapping on the fields in the header { endian_swap(headerSize); endian_swap(metadataOffset); endian_swap(subjectDataOffset); endian_swap(nTriangles); endian_swap(nVertices); endian_swap(nStrips); endian_swap(stripSize); endian_swap(vertexNormalsOffset); endian_swap(vertexUVOffset); endian_swap(vertexColorsOffset); endian_swap(vertexLabelsOffset); endian_swap(vertexAttributesOffset); } bool isSwapped() ///< returns true if the current contents of the header are in a different byte order than the system { if (headerType[4]=='B') { return ByteOrder::littleEndian(); } if (headerType[4]=='L') { return ByteOrder::bigEndian(); } return (headerSize>32000); // in case it is an old version of the header } char headerType[12]; ///< 12 character string identifying the header type and format. This will be either DFS_BE v2.0 or DFS_LE v2.0 (null terminated in both cases) int headerSize; ///< size of complete header (i.e., offset of first data element) int metadataOffset; ///< start of metadata (not used presently) int subjectDataOffset; ///< start of subject data (not used presently) int nTriangles; ///< number of triangles int nVertices; ///< number of vertices int nStrips; ///< number of triangle strips (not used) int stripSize; ///< size of strip data (not used) int vertexNormalsOffset; ///< start of vertex normal data (0 if not in file) int vertexUVOffset; ///< start of surface parameterization data (0 if not in file) int vertexColorsOffset; ///< vertex color int vertexLabelsOffset; ///< vertex labels int vertexAttributesOffset; ///< vertex attributes (float32 array of length NV) char pad1[8*4*4-4]; ///< this area previously contained a structure that is now deprecated; it shrinks when new fields are added to the header }; template inline std::istream &operator>>(std::istream &is, std::vector &v) /// read a vector from an input stream (assumed binary) { if (!v.empty()) is.read((char *)&v[0],(std::streamsize)(v.size()*sizeof(T))); return is; } template inline std::ostream &operator<<(std::ostream &os, std::vector &v) /// read a vector to an output stream (assumed binary) { if (!v.empty()) os.write((char *)&v[0],(std::streamsize)(v.size()*sizeof(T))); return os; } /// \brief The Triangle class consists of 3 integer values that index into a vertex list. /// \author David Shattuck /// /// BrainSuite assumes that the list of vertices starts at zero. class Triangle { public: Triangle() : a(0), b(0), c(0) {} int a,b,c; }; /// \brief The PointUV class consists of 2 32-bit floating point values that specify a 2D coordinate. /// \author David Shattuck /// /// The UV coordinates may be used for representing flattened surfaces or for performing texture /// mapping onto a surface. class PointUV { public: PointUV() : u(0), v(0) {} float u,v; }; /// \brief The Point3D class is specifies a 3D coordinate using 32-bit floating point values. class Point3D { public: Point3D() : x(0), y(0), z(0) {} float x,y,z; }; /// \brief swap each element of a UV Point inline void endian_swap(PointUV &p) { endian_swap(p.u); endian_swap(p.v); } /// \brief swap each element of a triangle inline void endian_swap(Triangle &t) { endian_swap(t.a); endian_swap(t.b); endian_swap(t.c); } /// \brief swap each element of a 3D point inline void endian_swap(Point3D &p) { endian_swap(p.x); endian_swap(p.y); endian_swap(p.z); } /// \brief swap each element in the vector v template void swap_vec(std::vector &v) { const size_t n = v.size(); for (size_t i=0;i0) ? header.nVertices : 0); vertexColors.resize((header.vertexColorsOffset>0) ? header.nVertices : 0); vertexUV.resize((header.vertexUVOffset>0) ? header.nVertices : 0); vertexLabels.resize((header.vertexLabelsOffset>0) ? header.nVertices : 0); vertexAttributes.resize((header.vertexAttributesOffset>0) ? header.nVertices : 0); ifile.seekg(header.headerSize+header.metadataOffset+header.subjectDataOffset,std::ios_base::beg); ifile>>triangles; ifile>>vertices; if (header.vertexNormalsOffset>0) { ifile.seekg(header.vertexNormalsOffset,std::ios_base::beg); ifile>>vertexNormals; } if (header.vertexColorsOffset>0) { ifile.seekg(header.vertexColorsOffset,std::ios_base::beg); ifile>>vertexColors; } if (header.vertexUVOffset>0) { ifile.seekg(header.vertexUVOffset,std::ios_base::beg); ifile>>vertexUV; } if (header.vertexLabelsOffset>0) { ifile.seekg(header.vertexLabelsOffset,std::ios_base::beg); ifile>>vertexLabels; } if (header.vertexAttributesOffset>0) { ifile.seekg(header.vertexAttributesOffset,std::ios_base::beg); ifile>>vertexAttributes; } if (swapped) { swap_vec(triangles); swap_vec(vertices); swap_vec(vertexNormals); swap_vec(vertexColors); swap_vec(vertexUV); swap_vec(vertexLabels); swap_vec(vertexAttributes); } return true; } /// writes a surface object as a DFS file; returns true if successful and false if unsuccessful. bool writeDFS(std::string ofname) ///< the output filename (should end in .dfs). { std::ofstream ofile; ofile.open(ofname.c_str(), std::ios::binary); if (!ofile) return false; DFSHeader header; const int nv = (int)vertices.size(); const int nt = (int)triangles.size(); header.nTriangles = nt; header.nVertices = nv; int pos = sizeof(header) + sizeof(Triangle)*nt + sizeof(Point3D)*nv; if (vertexNormals.size()==nv) { header.vertexNormalsOffset = pos; pos += sizeof(vertexNormals[0])*nv; } if (vertexColors.size()==nv) { header.vertexColorsOffset = pos; pos += sizeof(vertexColors[0])*nv; } if (vertexUV.size()==nv) { header.vertexUVOffset = pos; pos += sizeof(vertexUV[0])*nv; } if (vertexLabels.size()==nv) { header.vertexLabelsOffset = pos; pos += sizeof(vertexLabels[0])*nv; } if (vertexAttributes.size()==nv) { header.vertexAttributesOffset = pos; pos += sizeof(vertexAttributes[0])*nv; } ofile.write((char *)&header,sizeof(header)); ofile< triangles; ///< triangle index array; indices start at zero std::vector vertices; ///< array of mesh vertices std::vector vertexNormals; ///< array of vertex normals (optional) std::vector vertexColors; ///< array of vertex colors (optional) std::vector vertexUV; ///< array of surface-based coordinates for flat-mapping (optional) std::vector vertexLabels; ///< array of labels for the vertices (optional) std::vector vertexAttributes; ///< array of attributes for the vertices (optional) }; } // end of namespace SILT #endif