Coverage Report

Created: 2026-09-02 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libigl/include/igl/MshLoader.cpp
Line
Count
Source
1
// based on MSH reader from PyMesh 
2
3
// Copyright (c) 2015 Qingnan Zhou <qzhou@adobe.com>           
4
// Copyright (C) 2020 Vladimir Fonov <vladimir.fonov@gmail.com> 
5
//
6
// This Source Code Form is subject to the terms of the Mozilla 
7
// Public License v. 2.0. If a copy of the MPL was not distributed 
8
// with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 
9
10
#include "MshLoader.h"
11
12
#include <cassert>
13
#include <iostream>
14
#include <sstream>
15
#include <vector>
16
17
#include <string.h>
18
19
namespace igl {
20
    // helper function
21
392k
    void inline _msh_eat_white_space(std::ifstream& fin) {
22
392k
        char next = fin.peek();
23
399k
        while (next == '\n' || next == ' ' || next == '\t' || next == '\r') {
24
6.97k
            fin.get();
25
6.97k
            next = fin.peek();
26
6.97k
        }
27
392k
    }
28
}
29
30
1.33k
IGL_INLINE igl::MshLoader::MshLoader(const std::string &filename) {
31
1.33k
    std::ifstream fin(filename, std::ios::in | std::ios::binary);
32
33
1.33k
    if (!fin.is_open()) {
34
0
        std::stringstream err_msg;
35
0
        err_msg << "failed to open file \"" << filename << "\"";
36
0
        throw std::ios_base::failure(err_msg.str());
37
0
    }
38
    // Parse header
39
1.33k
    std::string buf;
40
1.33k
    double version;
41
1.33k
    int type;
42
1.33k
    fin >> buf;
43
1.33k
    if (buf != "$MeshFormat") { throw std::runtime_error("Unexpected .msh format"); }
44
45
1.30k
    fin >> version >> type >> m_data_size;
46
1.30k
    m_binary = (type == 1);
47
1.30k
    if(version>2.2 || version<2.0)
48
1
    {
49
        // probably unsupported version
50
1
        std::stringstream err_msg;
51
1
        err_msg << "Error: Unsupported file version:" << version << std::endl;
52
1
        throw std::runtime_error(err_msg.str());
53
54
1
    }
55
    // Some sanity check.
56
1.30k
    if (m_data_size != 8) {
57
4
        std::stringstream err_msg;
58
4
        err_msg << "Error: data size must be 8 bytes." << std::endl;
59
4
        throw std::runtime_error(err_msg.str());
60
4
    }
61
1.29k
    if (sizeof(int) != 4) {
62
0
        std::stringstream err_msg;
63
0
        err_msg << "Error: code must be compiled with int size 4 bytes." << std::endl;
64
0
        throw std::runtime_error(err_msg.str());
65
0
    }
66
67
    // Read in extra info from binary header.
68
1.29k
    if (m_binary) {
69
4
        int one;
70
4
        igl::_msh_eat_white_space(fin);
71
4
        fin.read(reinterpret_cast<char*>(&one), sizeof(int));
72
4
        if (one != 1) {
73
4
            std::stringstream err_msg;
74
4
                err_msg << "Binary msh file " << filename
75
4
                << " is saved with different endianness than this machine."
76
4
                << std::endl;
77
4
            throw std::runtime_error(err_msg.str());
78
4
        }
79
4
    }
80
81
1.29k
    fin >> buf;
82
1.29k
    if (buf != "$EndMeshFormat") 
83
12
    { 
84
12
        std::stringstream err_msg;
85
12
        err_msg << "Unexpected contents in the file header." << std::endl;
86
12
        throw std::runtime_error(err_msg.str());
87
12
    }
88
89
15.4k
    while (!fin.eof()) {
90
14.5k
        buf.clear();
91
14.5k
        fin >> buf;
92
14.5k
        if (buf == "$Nodes") {
93
3.26k
            parse_nodes(fin);
94
3.26k
            fin >> buf;
95
3.26k
            if (buf != "$EndNodes") { throw std::runtime_error("Unexpected tag"); }
96
11.2k
        } else if (buf == "$Elements") {
97
415
            parse_elements(fin);
98
415
            fin >> buf;
99
415
            if (buf != "$EndElements") { throw std::runtime_error("Unexpected tag"); }
100
10.8k
        } else if (buf == "$NodeData") {
101
6.19k
            parse_node_field(fin);
102
6.19k
            fin >> buf;
103
6.19k
            if (buf != "$EndNodeData") { throw std::runtime_error("Unexpected tag"); }
104
6.19k
        } else if (buf == "$ElementData") {
105
1.89k
            parse_element_field(fin);
106
1.89k
            fin >> buf;
107
1.89k
            if (buf != "$EndElementData") { throw std::runtime_error("Unexpected tag"); }
108
2.77k
        } else if (fin.eof()) {
109
60
            break;
110
2.71k
        } else {
111
2.71k
            parse_unknown_field(fin, buf);
112
2.71k
        }
113
14.5k
    }
114
958
    fin.close();
115
958
}
116
117
20.3k
IGL_INLINE int igl::MshLoader::node_dense_index(int node_tag) const {
118
20.3k
    const auto it = m_node_tag_to_dense.find(node_tag);
119
20.3k
    if (it == m_node_tag_to_dense.end()) {
120
155
        std::stringstream err_msg;
121
155
        err_msg << "Unknown node tag: " << node_tag;
122
155
        throw std::runtime_error(err_msg.str());
123
155
    }
124
20.2k
  return it->second;
125
20.3k
}
126
127
49
IGL_INLINE int igl::MshLoader::element_dense_index(int elem_tag) const {
128
49
  const auto it = m_element_tag_to_dense.find(elem_tag);
129
49
    if (it == m_element_tag_to_dense.end()) {
130
49
    std::stringstream err_msg;
131
49
        err_msg << "Unknown element tag: " << elem_tag;
132
49
    throw std::runtime_error(err_msg.str());
133
49
    }
134
0
  return it->second;
135
49
}
136
137
3.26k
IGL_INLINE void igl::MshLoader::parse_nodes(std::ifstream& fin) {
138
3.26k
    size_t num_nodes;
139
3.26k
    fin >> num_nodes;
140
3.26k
    m_nodes.resize(num_nodes*3);
141
3.26k
    m_node_tag_to_dense.clear();
142
143
3.26k
    if (m_binary) {
144
0
    size_t stride = (4+3*m_data_size);
145
0
        size_t num_bytes = stride * num_nodes;
146
0
        char* data = new char[num_bytes];
147
0
        igl::_msh_eat_white_space(fin);
148
0
        fin.read(data, num_bytes);
149
150
0
        for (size_t i=0; i<num_nodes; i++) {
151
0
            int node_tag;
152
0
      memcpy(&node_tag, data+i*stride, sizeof(int));
153
154
0
            if (node_tag <= 0) {
155
0
                throw std::runtime_error("Invalid node tag");
156
0
            }
157
0
            if (m_node_tag_to_dense.find(node_tag) != m_node_tag_to_dense.end()) {
158
0
                throw std::runtime_error("Duplicate node tag");
159
0
            }
160
0
            m_node_tag_to_dense[node_tag] = static_cast<int>(i);
161
            // directly move into vector storage
162
            // this works only when m_data_size==sizeof(Float)==sizeof(double)
163
0
      memcpy(&m_nodes[i*3], data+i*stride + 4, m_data_size*3);
164
0
        }
165
0
        delete [] data;
166
3.26k
    } else {
167
3.26k
        int node_tag;
168
6.56k
        for (size_t i=0; i<num_nodes; i++) {
169
3.48k
            fin >> node_tag;
170
171
3.48k
            if (node_tag <= 0) {
172
27
        throw std::runtime_error("Invalid node tag");
173
27
            }
174
3.45k
            if (m_node_tag_to_dense.find(node_tag) != m_node_tag_to_dense.end()) {
175
148
                throw std::runtime_error("Duplicate node tag");
176
148
            }
177
3.30k
            m_node_tag_to_dense[node_tag] = static_cast<int>(i);
178
            // here it's 3D node explicitly
179
3.30k
            fin >> m_nodes[i*3]
180
3.30k
                >> m_nodes[i*3+1]
181
3.30k
                >> m_nodes[i*3+2];
182
3.30k
        }
183
3.26k
    }
184
3.26k
}
185
186
415
IGL_INLINE void igl::MshLoader::parse_elements(std::ifstream& fin) {
187
415
    m_elements_tags.resize(2); //hardcoded to have 2 tags
188
415
    size_t num_elements;
189
415
    fin >> num_elements;
190
415
    m_element_tag_to_dense.clear();
191
192
415
    size_t nodes_per_element;
193
194
415
    if (m_binary) {
195
0
        igl::_msh_eat_white_space(fin);
196
0
        int elem_read = 0;
197
0
        while (elem_read < num_elements) {
198
            // Parse element header.
199
0
            int elem_type, num_elems, num_tags;
200
0
            fin.read((char*)&elem_type, sizeof(int));
201
0
            fin.read((char*)&num_elems, sizeof(int));
202
0
            fin.read((char*)&num_tags,  sizeof(int));
203
0
            nodes_per_element = num_nodes_per_elem_type(elem_type);
204
205
            // store node info
206
0
            for (size_t i=0; i<num_elems; i++) {
207
0
                int elem_tag;
208
209
                // all elements in the segment share the same elem_type and number of nodes per element
210
0
                m_elements_types.push_back(elem_type);
211
0
                m_elements_lengths.push_back(nodes_per_element);
212
213
0
                fin.read((char*)&elem_tag, sizeof(int));
214
215
0
                if (elem_tag <= 0) {
216
0
                    throw std::runtime_error("Invalid element tag");
217
0
                }
218
0
                if (m_element_tag_to_dense.find(elem_tag) != m_element_tag_to_dense.end()) {
219
0
                    throw std::runtime_error("Duplicate element tag");
220
0
                }
221
0
                m_element_tag_to_dense[elem_tag] = static_cast<int>(m_elements_ids.size());
222
223
0
                elem_tag -= 1;
224
0
                m_elements_ids.push_back(elem_tag);
225
226
                // read first two tags
227
0
                for (size_t j=0; j<num_tags; j++) {
228
0
                    int tag;
229
0
                    fin.read((char*)&tag, sizeof(int));
230
0
                    if(j<2) m_elements_tags[j].push_back(tag);
231
0
                }
232
233
0
                for (size_t j=num_tags; j<2; j++) 
234
0
                    m_elements_tags[j].push_back(-1); // fill up tags if less then 2
235
236
0
                m_elements_nodes_idx.push_back(m_elements.size());
237
                // Element values.
238
0
                for (size_t j=0; j<nodes_per_element; j++) {
239
0
                    int node_tag;
240
0
                    fin.read((char*)&node_tag, sizeof(int));
241
                    
242
0
                    m_elements.push_back(node_dense_index(node_tag));
243
0
                }
244
0
            }
245
0
            elem_read += num_elems;
246
0
        }
247
415
    } else {
248
662
        for (size_t i=0; i<num_elements; i++) {
249
            // Parse per element header
250
281
            int elem_tag, elem_type, num_tags;
251
281
            fin >> elem_tag >> elem_type >> num_tags;
252
253
281
            if (elem_tag <= 0) {
254
34
                throw std::runtime_error("Invalid element tag");
255
34
            }
256
247
            if (m_element_tag_to_dense.find(elem_tag) != m_element_tag_to_dense.end()) {
257
0
                throw std::runtime_error("Duplicate element tag");
258
0
            }
259
247
            m_element_tag_to_dense[elem_tag] = static_cast<int>(m_elements_ids.size());
260
261
            // read tags.
262
11.6G
            for (size_t j=0; j<num_tags; j++) {
263
11.6G
                int tag;
264
11.6G
                fin >> tag;
265
11.6G
                if(j<2) m_elements_tags[j].push_back(tag);
266
11.6G
            }
267
543
            for (size_t j=num_tags; j<2; j++) 
268
296
                m_elements_tags[j].push_back(-1); // fill up tags if less then 2
269
            
270
247
            nodes_per_element = num_nodes_per_elem_type(elem_type);
271
247
            m_elements_types.push_back(elem_type);
272
247
            m_elements_lengths.push_back(nodes_per_element);
273
274
247
            elem_tag -= 1;
275
247
            m_elements_ids.push_back(elem_tag);
276
247
            m_elements_nodes_idx.push_back(m_elements.size());
277
            // Parse node idx.
278
885
            for (size_t j=0; j<nodes_per_element; j++) {
279
638
                int node_tag;
280
638
                fin >> node_tag;
281
638
                m_elements.push_back(node_dense_index(node_tag)); // msh index starts from 1.
282
638
            }
283
247
        }
284
415
    }
285
    // debug
286
415
    assert(m_elements_types.size()   == m_elements_ids.size());
287
381
    assert(m_elements_tags[0].size() == m_elements_ids.size());
288
238
    assert(m_elements_tags[1].size() == m_elements_ids.size());
289
238
    assert(m_elements_lengths.size() == m_elements_ids.size());
290
238
}
291
292
6.19k
IGL_INLINE void igl::MshLoader::parse_node_field( std::ifstream& fin ) {
293
6.19k
    size_t num_string_tags;
294
6.19k
    size_t num_real_tags;
295
6.19k
    size_t num_int_tags;
296
297
6.19k
    fin >> num_string_tags;
298
6.19k
    std::vector<std::string> str_tags(num_string_tags);
299
300
295k
    for (size_t i=0; i<num_string_tags; i++) {
301
289k
        igl::_msh_eat_white_space(fin);
302
289k
        if (fin.peek() == '\"') {
303
            // Handle field name between quotes.
304
1.10k
            char buf[128];
305
1.10k
            fin.get(); // remove the quote at the beginning.
306
1.10k
            fin.getline(buf, 128, '\"');
307
1.10k
            str_tags[i] = std::string(buf);
308
288k
        } else {
309
288k
            fin >> str_tags[i];
310
288k
        }
311
289k
    }
312
313
6.19k
    fin >> num_real_tags;
314
6.19k
    std::vector<Float> real_tags(num_real_tags);
315
92.3M
    for (size_t i=0; i<num_real_tags; i++)
316
92.3M
        fin >> real_tags[i];
317
318
6.19k
    fin >> num_int_tags;
319
6.19k
    std::vector<int> int_tags(num_int_tags);
320
33.9M
    for (size_t i=0; i<num_int_tags; i++)
321
33.9M
        fin >> int_tags[i];
322
323
6.19k
    if (num_string_tags <= 0 || num_int_tags <= 2) {
324
5
        throw std::runtime_error("Unexpected number of field tags");
325
5
    }
326
6.19k
    std::string fieldname = str_tags[0];
327
6.19k
    int num_components    = int_tags[1];
328
6.19k
    int num_entries       = int_tags[2];
329
330
6.19k
    std::vector<Float> field((m_nodes.size()/3)*num_components);
331
332
6.19k
    if (m_binary) {
333
0
        size_t num_bytes = (num_components * m_data_size + 4) * num_entries;
334
0
        char* data = new char[num_bytes];
335
0
        igl::_msh_eat_white_space(fin);
336
0
        fin.read(data, num_bytes);
337
0
        for (size_t i=0; i<num_entries; i++) {
338
0
      int node_tag;
339
0
      memcpy(&node_tag,&data[i*(4+num_components*m_data_size)],4);
340
341
0
            const int node_idx = node_dense_index(node_tag);
342
0
            size_t base_idx = i*(4+num_components*m_data_size) + 4;
343
            // TODO: make this work when m_data_size != sizeof(double) ?
344
0
      memcpy(&field[node_idx*num_components], &data[base_idx], num_components*m_data_size);
345
0
        }
346
0
        delete [] data;
347
6.19k
    } else {
348
6.19k
        int node_tag;
349
25.9k
        for (size_t i=0; i<num_entries; i++) {
350
19.7k
            fin >> node_tag;
351
19.7k
            const int node_idx = node_dense_index(node_tag);
352
417M
            for (size_t j=0; j<num_components; j++) {
353
417M
                fin >> field[node_idx*num_components+j];
354
417M
            }
355
19.7k
        }
356
6.19k
    }
357
    
358
6.19k
    m_node_fields_names.push_back(fieldname);
359
6.19k
    m_node_fields.push_back(field);
360
6.19k
    m_node_fields_components.push_back(num_components);
361
6.19k
}
362
363
1.89k
IGL_INLINE void igl::MshLoader::parse_element_field(std::ifstream& fin) {
364
1.89k
    size_t num_string_tags;
365
1.89k
    size_t num_real_tags;
366
1.89k
    size_t num_int_tags;
367
368
1.89k
    fin >> num_string_tags;
369
1.89k
    std::vector<std::string> str_tags(num_string_tags);
370
105k
    for (size_t i=0; i<num_string_tags; i++) {
371
103k
        igl::_msh_eat_white_space(fin);
372
103k
        if (fin.peek() == '\"') {
373
            // Handle field name between quoates.
374
197
            char buf[128];
375
197
            fin.get(); // remove the quote at the beginning.
376
197
            fin.getline(buf, 128, '\"');
377
197
            str_tags[i] = buf;
378
103k
        } else {
379
103k
            fin >> str_tags[i];
380
103k
        }
381
103k
    }
382
383
1.89k
    fin >> num_real_tags;
384
1.89k
    std::vector<Float> real_tags(num_real_tags);
385
368M
    for (size_t i=0; i<num_real_tags; i++)
386
368M
        fin >> real_tags[i];
387
388
1.89k
    fin >> num_int_tags;
389
1.89k
    std::vector<int> int_tags(num_int_tags);
390
797M
    for (size_t i=0; i<num_int_tags; i++)
391
797M
        fin >> int_tags[i];
392
393
1.89k
    if (num_string_tags <= 0 || num_int_tags <= 2) {
394
1
        throw std::runtime_error("Invalid file format");
395
1
    }
396
1.89k
    std::string fieldname = str_tags[0];
397
1.89k
    int num_components = int_tags[1];
398
1.89k
    int num_entries = int_tags[2];
399
1.89k
    std::vector<Float> field(m_elements_ids.size()*num_components);
400
401
1.89k
    if (m_binary) {
402
0
        size_t num_bytes = (num_components * m_data_size + 4) * num_entries;
403
0
        char* data = new char[num_bytes];
404
0
        igl::_msh_eat_white_space(fin);
405
0
        fin.read(data, num_bytes);
406
0
        for (int i=0; i<num_entries; i++) {
407
0
      int elem_tag;
408
      // works with sizeof(int)==4
409
0
      memcpy(&elem_tag, &data[i*(4+num_components*m_data_size)],4);
410
0
            const int elem_idx = element_dense_index(elem_tag);
411
      
412
      // directly copy data into vector storage space
413
0
      memcpy(&field[elem_idx*num_components], &data[i*(4+num_components*m_data_size) + 4], m_data_size*num_components);
414
0
        }
415
0
        delete [] data;
416
1.89k
    } else {
417
1.89k
        int elem_tag;
418
1.94k
        for (size_t i=0; i<num_entries; i++) {
419
49
            fin >> elem_tag;
420
49
            const int elem_idx = element_dense_index(elem_tag);
421
49
            for (size_t j=0; j<num_components; j++) {
422
0
                fin >> field[elem_idx*num_components+j];
423
0
            }
424
49
        }
425
1.89k
    }
426
1.89k
    m_element_fields_names.push_back(fieldname);
427
1.89k
    m_element_fields.push_back(field);
428
1.89k
    m_element_fields_components.push_back(num_components);
429
1.89k
}
430
431
IGL_INLINE void igl::MshLoader::parse_unknown_field(std::ifstream& fin,
432
2.71k
        const std::string& fieldname) {
433
2.71k
    std::cerr << "Warning: \"" << fieldname << "\" not supported yet.  Ignored." << std::endl;
434
2.71k
    std::string endmark = fieldname.substr(0,1) + "End"
435
2.71k
        + fieldname.substr(1,fieldname.size()-1);
436
437
2.71k
    std::string buf("");
438
12.7k
    while (buf != endmark && !fin.eof()) {
439
10.0k
        fin >> buf;
440
10.0k
    }
441
2.71k
}
442
443
247
IGL_INLINE int igl::MshLoader::num_nodes_per_elem_type(int elem_type) {
444
247
    int nodes_per_element = 0;
445
247
    switch (elem_type) {
446
78
        case ELEMENT_LINE:         // 2-node line
447
78
            nodes_per_element = 2; 
448
78
            break;
449
24
        case ELEMENT_TRI:
450
24
            nodes_per_element = 3; // 3-node triangle
451
24
            break;
452
19
        case ELEMENT_QUAD:
453
19
            nodes_per_element = 4; // 5-node quad
454
19
            break;
455
18
        case ELEMENT_TET:
456
18
            nodes_per_element = 4; // 4-node tetrahedra
457
18
            break;
458
12
        case ELEMENT_HEX:          // 8-node hexahedron
459
12
            nodes_per_element = 8; 
460
12
            break;
461
8
        case ELEMENT_PRISM:        // 6-node prism
462
8
            nodes_per_element = 6; 
463
8
            break;
464
13
        case ELEMENT_LINE_2ND_ORDER: 
465
13
            nodes_per_element = 3;
466
13
            break;
467
12
        case ELEMENT_TRI_2ND_ORDER: 
468
12
            nodes_per_element = 6;
469
12
            break;
470
3
        case ELEMENT_QUAD_2ND_ORDER: 
471
3
            nodes_per_element = 9;
472
3
            break;
473
12
        case ELEMENT_TET_2ND_ORDER: 
474
12
            nodes_per_element = 10;
475
12
            break;
476
11
        case ELEMENT_HEX_2ND_ORDER: 
477
11
            nodes_per_element = 27;
478
11
            break;
479
2
        case ELEMENT_PRISM_2ND_ORDER: 
480
2
            nodes_per_element = 18;
481
2
            break;
482
5
        case ELEMENT_PYRAMID_2ND_ORDER: 
483
5
            nodes_per_element = 14;
484
5
            break;
485
2
        case ELEMENT_POINT:        // 1-node point
486
2
            nodes_per_element = 1; 
487
2
            break;
488
28
        default:
489
28
            std::stringstream err_msg;
490
28
                err_msg << "Element type (" << elem_type << ") is not supported yet."
491
28
                << std::endl;
492
28
            throw std::runtime_error(err_msg.str());
493
247
    }
494
219
    return nodes_per_element;
495
247
}
496
497
498
IGL_INLINE bool igl::MshLoader::is_element_map_identity() const
499
0
{
500
0
    for(int i=0;i<m_elements_ids.size();i++) {
501
0
        int id=m_elements_ids[i];
502
0
        if (id!=i) return false;
503
0
    }
504
0
    return true;
505
0
}
506
507
508
IGL_INLINE void igl::MshLoader::index_structures(int tag_column)
509
0
{
510
    //cleanup
511
0
    m_structure_index.clear();
512
0
    m_structures.clear();
513
0
    m_structure_length.clear();
514
515
    //index structure tags
516
0
    for(auto i=0; i != m_elements_tags[tag_column].size(); ++i )
517
0
    {
518
0
        m_structure_index.insert(
519
0
            std::pair<msh_struct,int>(
520
0
                msh_struct( m_elements_tags[tag_column][i], 
521
0
                            m_elements_types[i]), i)
522
0
            );
523
0
    }
524
525
    // identify unique structures 
526
0
    std::vector<StructIndex::value_type> _unique_structs;
527
0
    std::unique_copy(std::begin(m_structure_index), 
528
0
                     std::end(m_structure_index), 
529
0
                     std::back_inserter(_unique_structs),
530
0
                     [](const StructIndex::value_type &c1, const StructIndex::value_type &c2)
531
0
                     { return c1.first == c2.first; });
532
533
0
    std::for_each( _unique_structs.begin(), _unique_structs.end(), 
534
0
        [this](const StructIndex::value_type &n){ this->m_structures.push_back(n.first); });
535
536
0
    for(auto t = m_structures.begin(); t != m_structures.end(); ++t)
537
0
    {
538
        // identify all elements corresponding to this tag
539
0
        auto structure_range = m_structure_index.equal_range( *t );
540
0
        int cnt=0;
541
542
0
        for(auto i=structure_range.first; i!=structure_range.second; i++)
543
0
            cnt++;
544
545
0
        m_structure_length.insert( std::pair<msh_struct,int>( *t, cnt));
546
0
    }
547
0
}