iMSTK
Interactive Medical Simulation Toolkit
All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator Friends Pages
imstkMshMeshIO.cpp
1 /*
2 ** This file is part of the Interactive Medical Simulation Toolkit (iMSTK)
3 ** iMSTK is distributed under the Apache License, Version 2.0.
4 ** See accompanying NOTICE for details.
5 */
6 
7 #include "imstkMshMeshIO.h"
8 #include "imstkHexahedralMesh.h"
9 #include "imstkLineMesh.h"
10 #include "imstkSurfaceMesh.h"
11 #include "imstkTetrahedralMesh.h"
12 #include "imstkVecDataArray.h"
13 
14 #include <fstream>
15 
16 namespace imstk
17 {
21 static void
22 readToDelimiter(std::ifstream& file)
23 {
24  char next = static_cast<char>(file.peek());
25  while (next == '\n' || next == ' ' || next == '\t' || next == '\r')
26  {
27  file.get();
28  next = static_cast<char>(file.peek());
29  }
30 }
31 
35 template<int N>
36 static std::shared_ptr<VecDataArray<int, N>>
37 toVecDataArray(const std::vector<int>& vertIds)
38 {
39  const int cellCount = static_cast<int>(vertIds.size() / N);
40  CHECK(vertIds.size() % N == 0) << "Failed to convert array stride not divisable";
41  std::shared_ptr<VecDataArray<int, N>> indicesPtr =
42  std::make_shared<VecDataArray<int, N>>(cellCount);
43  std::copy(vertIds.data(), vertIds.data() + cellCount * N,
44  std::dynamic_pointer_cast<DataArray<int>>(indicesPtr)->getPointer());
45  return indicesPtr;
46 }
47 
48 std::shared_ptr<PointSet>
49 MshMeshIO::read(const std::string& filePath)
50 {
51  // based on the format provided on (ASCII version)
52  // ASCII: http://www.manpagez.com/info/gmsh/gmsh-2.2.6/gmsh_63.php
53  // Binary: https://www.manpagez.com/info/gmsh/gmsh-2.4.0/gmsh_57.php
54 
55  std::shared_ptr<PointSet> results = nullptr;
56 
57  // Reopen as binary
58  std::ifstream file;
59  file.open(filePath, std::ios::binary | std::ios::in);
60  CHECK(file.is_open()) << "Failed to read file, ifstream failed to open " << filePath;
61 
62  // Read $MeshFormat\n
63  std::string bufferStr;
64  file >> bufferStr;
65 
66  // Read version, type, dataSize (refers to floats/doubles)
67  double version;
68  int fileType;
69  int dataSize;
70  file >> version >> fileType >> dataSize;
71  CHECK(dataSize == 8) << "Failed to read file, data size must be 8 bytes";
72  CHECK(sizeof(int) == 4) << "Failed to read file, code must be compiled with int size 4 bytes";
73 
74  const bool isBinary = (fileType == 1);
75 
76  // Read the number one written in binary to check endianness
77  // If it's not one then file was written with different endian
78  if (isBinary)
79  {
80  int oneFromBinary;
81  readToDelimiter(file);
82  file.read(reinterpret_cast<char*>(&oneFromBinary), sizeof(int));
83  CHECK(oneFromBinary == 1) << "Failed to read file, file saved with different endianness than this machine";
84  }
85 
86  file >> bufferStr; // Read $EndMeshFormat
87  CHECK(bufferStr == "$EndMeshFormat") << "Failed to read file, invalid format";
88 
89  CHECK(!file.fail()) << "Failed to read file, ifstream error";
90 
91  std::shared_ptr<VecDataArray<double, 3>> verticesPtr = nullptr;
92  while (!file.eof())
93  {
94  bufferStr.clear();
95  file >> bufferStr;
96  CHECK(!file.fail()) << "Failed to read file, ifstream error";
97 
98  if (bufferStr == "$Nodes")
99  {
100  int nNodes = -1;
101  file >> nNodes; // Read # of Nodes
102 
103  // Get the node IDs and the node coordinates
104  verticesPtr = std::make_shared<VecDataArray<double, 3>>(nNodes);
105  VecDataArray<double, 3>& vertices = *verticesPtr;
106  std::vector<size_t> nodeIDs(nNodes);
107 
108  if (isBinary)
109  {
110  // Read entire buffer as bytes
111  size_t numBytes = (4 + 3 * dataSize) * nNodes;
112  std::vector<char> data(numBytes);
113  readToDelimiter(file);
114  file.read(data.data(), numBytes);
115  for (int i = 0; i < nNodes; i++)
116  {
117  int id = *reinterpret_cast<int*>(&data[i * (4 + 3 * dataSize)]) - 1;
118  nodeIDs[i] = id;
119 
120  // Note in code above we restrict to only double (8 byte floating pt), but in
121  // the spec support could be added here for float (4 byte floating pt)
122 
123  // Copy the next 3 doubles
124  double* x = reinterpret_cast<double*>(&data[i * (4 + 3 * dataSize) + 4]);
125  std::copy(x, x + 3, &vertices[id][0]);
126  }
127  }
128  else
129  {
130  for (int i = 0; i < nNodes; i++)
131  {
132  int id;
133  Vec3d pos;
134  file >> id >> pos[0] >> pos[1] >> pos[2];
135  nodeIDs[i] = id - 1;
136  vertices[id - 1] = pos;
137  }
138  }
139 
140  file >> bufferStr; // Read $EndNodes
141  CHECK(bufferStr == "$EndNodes") << "Failed to read file, invalid format";
142  }
143  else if (bufferStr == "$Elements")
144  {
145  std::array<int, 6> elemTypeToCount =
146  {
147  0,
148  2, // Line
149  3, // Triangle
150  4, // Quad
151  4, // Tetrahedron
152  8 // Hexahedron
153  };
154 
155  // 1 - line, 2 - triangle, 3 - quad, 4 - tet, 5 - hex
156  //std::array<std::vector<int>, 6> elementsIds;
157  std::array<std::vector<int>, 6> elementVertIds;
158 
159  int numElements;
160  file >> numElements;
161  CHECK(!file.fail()) << "Failed to read file, ifstream error";
162  readToDelimiter(file);
163 
164  if (isBinary)
165  {
166  int elemIter = 0;
167  // Why not use a for loop?
168  while (elemIter < numElements)
169  {
170  // Parse element header.
171  int elemType, numElems, numTags;
172  file.read((char*)&elemType, sizeof(int));
173  file.read((char*)&numElems, sizeof(int));
174  file.read((char*)&numTags, sizeof(int));
175 
176  CHECK(elemType > 0 && elemType < 6) <<
177  "Failed to read file, unsupported element type";
178  int vertexCount = elemTypeToCount[elemType];
179  std::vector<int>& elemVertIds = elementVertIds[elemType];
180  //std::vector<int>& elemIds = elementsIds[elemType];
181 
182  for (int i = 0; i < numElems; i++)
183  {
184  int elementId;
185  file.read((char*)&elementId, sizeof(int));
186  //elemIds.push_back(elementId - 1); // Msh starts from 1
187 
188  // Read the tags but don't do anything with them
189  for (int j = 0; j < numTags; j++)
190  {
191  int tag;
192  file.read((char*)&tag, sizeof(int));
193  }
194 
195  // Vertex ids
196  for (int j = 0; j < vertexCount; j++)
197  {
198  int vertId;
199  file.read((char*)&vertId, sizeof(int));
200  elemVertIds.push_back(vertId - 1);
201  }
202  }
203 
204  elemIter += numElems;
205  }
206  }
207  else
208  {
209  for (int i = 0; i < numElements; i++)
210  {
211  // Parse element header.
212  int elemGroupId = -1;
213  int elemType = -1;
214  int numTags = 0;
215  file >> elemGroupId >> elemType >> numTags;
216  CHECK(elemType > 0 && elemType < 6) <<
217  "Failed to read file, unsupported element type";
218 
219  int vertexCount = elemTypeToCount[elemType];
220  std::vector<int>& elemVertIds = elementVertIds[elemType];
221 
222  // Read the tags but don't do anything with them
223  for (int j = 0; j < numTags; j++)
224  {
225  int tag;
226  file >> tag;
227  }
228 
229  // Vertex ids
230  for (int j = 0; j < vertexCount; j++)
231  {
232  int vertId;
233  file >> vertId;
234  elemVertIds.push_back(vertId - 1);
235  }
236  }
237  }
238 
239  // We only support homogenous element types
240  int typeToUse = 0;
241  int typeCount = 0;
242  for (int i = 0; i < 6; i++)
243  {
244  if (elementVertIds[i].size() > 0)
245  {
246  typeCount++;
247  typeToUse = i;
248  }
249  }
250  // If we have more than one only choose the highest in vertex count of the element
251  // so hex > tet > quad > tri > line
252  if (typeCount > 1)
253  {
254  LOG(WARNING) << "MshMeshIO::read only supports homogenous types of elements, " <<
255  typeCount << " types of elements were found, choosing one";
256  }
257 
258  if (typeToUse == 1)
259  {
260  auto mesh = std::make_shared<LineMesh>();
261  mesh->initialize(verticesPtr, toVecDataArray<2>(elementVertIds[typeToUse]));
262  results = mesh;
263  }
264  else if (typeToUse == 2)
265  {
266  auto mesh = std::make_shared<SurfaceMesh>();
267  mesh->initialize(verticesPtr, toVecDataArray<3>(elementVertIds[typeToUse]));
268  results = mesh;
269  }
270  else if (typeToUse == 4)
271  {
272  auto mesh = std::make_shared<TetrahedralMesh>();
273  mesh->initialize(verticesPtr, toVecDataArray<4>(elementVertIds[typeToUse]));
274  results = mesh;
275  }
276  else if (typeToUse == 5)
277  {
278  auto mesh = std::make_shared<HexahedralMesh>();
279  mesh->initialize(verticesPtr, toVecDataArray<8>(elementVertIds[typeToUse]));
280  results = mesh;
281  }
282 
283  file >> bufferStr; // Read $EndElements
284  CHECK(bufferStr == "$EndElements") << "Failed to read file, invalid format";
285 
286  // File is considered read after elements
287  break;
288  }
289  }
290 
291  file.close();
292  return results;
293 }
294 } // namespace imstk
static std::shared_ptr< PointSet > read(const std::string &filePath)
Read and generate a volumetric mesh given a external msh file.
Compound Geometry.