Coverage Report

Created: 2026-05-30 07:00

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
8.25M
    void inline _msh_eat_white_space(std::ifstream& fin) {
22
8.25M
        char next = fin.peek();
23
8.26M
        while (next == '\n' || next == ' ' || next == '\t' || next == '\r') {
24
6.75k
            fin.get();
25
6.75k
            next = fin.peek();
26
6.75k
        }
27
8.25M
    }
28
}
29
30
941
IGL_INLINE igl::MshLoader::MshLoader(const std::string &filename) {
31
941
    std::ifstream fin(filename, std::ios::in | std::ios::binary);
32
33
941
    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
941
    std::string buf;
40
941
    double version;
41
941
    int type;
42
941
    fin >> buf;
43
941
    if (buf != "$MeshFormat") { throw std::runtime_error("Unexpected .msh format"); }
44
45
897
    fin >> version >> type >> m_data_size;
46
897
    m_binary = (type == 1);
47
897
    if(version>2.2 || version<2.0)
48
0
    {
49
        // probably unsupported version
50
0
        std::stringstream err_msg;
51
0
        err_msg << "Error: Unsupported file version:" << version << std::endl;
52
0
        throw std::runtime_error(err_msg.str());
53
54
0
    }
55
    // Some sanity check.
56
897
    if (m_data_size != 8) {
57
2
        std::stringstream err_msg;
58
2
        err_msg << "Error: data size must be 8 bytes." << std::endl;
59
2
        throw std::runtime_error(err_msg.str());
60
2
    }
61
895
    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
895
    if (m_binary) {
69
5
        int one;
70
5
        igl::_msh_eat_white_space(fin);
71
5
        fin.read(reinterpret_cast<char*>(&one), sizeof(int));
72
5
        if (one != 1) {
73
5
            std::stringstream err_msg;
74
5
                err_msg << "Binary msh file " << filename
75
5
                << " is saved with different endianness than this machine."
76
5
                << std::endl;
77
5
            throw std::runtime_error(err_msg.str());
78
5
        }
79
5
    }
80
81
890
    fin >> buf;
82
890
    if (buf != "$EndMeshFormat") 
83
15
    { 
84
15
        std::stringstream err_msg;
85
15
        err_msg << "Unexpected contents in the file header." << std::endl;
86
15
        throw std::runtime_error(err_msg.str());
87
15
    }
88
89
12.2k
    while (!fin.eof()) {
90
11.6k
        buf.clear();
91
11.6k
        fin >> buf;
92
11.6k
        if (buf == "$Nodes") {
93
2.16k
            parse_nodes(fin);
94
2.16k
            fin >> buf;
95
2.16k
            if (buf != "$EndNodes") { throw std::runtime_error("Unexpected tag"); }
96
9.50k
        } else if (buf == "$Elements") {
97
230
            parse_elements(fin);
98
230
            fin >> buf;
99
230
            if (buf != "$EndElements") { throw std::runtime_error("Unexpected tag"); }
100
9.27k
        } else if (buf == "$NodeData") {
101
5.37k
            parse_node_field(fin);
102
5.37k
            fin >> buf;
103
5.37k
            if (buf != "$EndNodeData") { throw std::runtime_error("Unexpected tag"); }
104
5.37k
        } else if (buf == "$ElementData") {
105
1.38k
            parse_element_field(fin);
106
1.38k
            fin >> buf;
107
1.38k
            if (buf != "$EndElementData") { throw std::runtime_error("Unexpected tag"); }
108
2.52k
        } else if (fin.eof()) {
109
37
            break;
110
2.48k
        } else {
111
2.48k
            parse_unknown_field(fin, buf);
112
2.48k
        }
113
11.6k
    }
114
647
    fin.close();
115
647
}
116
117
56
IGL_INLINE int igl::MshLoader::node_dense_index(int node_tag) const {
118
56
    const auto it = m_node_tag_to_dense.find(node_tag);
119
56
    if (it == m_node_tag_to_dense.end()) {
120
56
        std::stringstream err_msg;
121
56
        err_msg << "Unknown node tag: " << node_tag;
122
56
        throw std::runtime_error(err_msg.str());
123
56
    }
124
0
  return it->second;
125
56
}
126
127
57
IGL_INLINE int igl::MshLoader::element_dense_index(int elem_tag) const {
128
57
  const auto it = m_element_tag_to_dense.find(elem_tag);
129
57
    if (it == m_element_tag_to_dense.end()) {
130
57
    std::stringstream err_msg;
131
57
        err_msg << "Unknown element tag: " << elem_tag;
132
57
    throw std::runtime_error(err_msg.str());
133
57
    }
134
0
  return it->second;
135
57
}
136
137
2.16k
IGL_INLINE void igl::MshLoader::parse_nodes(std::ifstream& fin) {
138
2.16k
    size_t num_nodes;
139
2.16k
    fin >> num_nodes;
140
2.16k
    m_nodes.resize(num_nodes*3);
141
2.16k
    m_node_tag_to_dense.clear();
142
143
2.16k
    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
2.16k
    } else {
167
2.16k
        int node_tag;
168
2.81k
        for (size_t i=0; i<num_nodes; i++) {
169
676
            fin >> node_tag;
170
171
676
            if (node_tag <= 0) {
172
11
        throw std::runtime_error("Invalid node tag");
173
11
            }
174
665
            if (m_node_tag_to_dense.find(node_tag) != m_node_tag_to_dense.end()) {
175
20
                throw std::runtime_error("Duplicate node tag");
176
20
            }
177
645
            m_node_tag_to_dense[node_tag] = static_cast<int>(i);
178
            // here it's 3D node explicitly
179
645
            fin >> m_nodes[i*3]
180
645
                >> m_nodes[i*3+1]
181
645
                >> m_nodes[i*3+2];
182
645
        }
183
2.16k
    }
184
2.16k
}
185
186
230
IGL_INLINE void igl::MshLoader::parse_elements(std::ifstream& fin) {
187
230
    m_elements_tags.resize(2); //hardcoded to have 2 tags
188
230
    size_t num_elements;
189
230
    fin >> num_elements;
190
230
    m_element_tag_to_dense.clear();
191
192
230
    size_t nodes_per_element;
193
194
230
    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
230
    } else {
248
278
        for (size_t i=0; i<num_elements; i++) {
249
            // Parse per element header
250
64
            int elem_tag, elem_type, num_tags;
251
64
            fin >> elem_tag >> elem_type >> num_tags;
252
253
64
            if (elem_tag <= 0) {
254
16
                throw std::runtime_error("Invalid element tag");
255
16
            }
256
48
            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
48
            m_element_tag_to_dense[elem_tag] = static_cast<int>(m_elements_ids.size());
260
261
            // read tags.
262
35.5G
            for (size_t j=0; j<num_tags; j++) {
263
35.5G
                int tag;
264
35.5G
                fin >> tag;
265
35.5G
                if(j<2) m_elements_tags[j].push_back(tag);
266
35.5G
            }
267
97
            for (size_t j=num_tags; j<2; j++) 
268
49
                m_elements_tags[j].push_back(-1); // fill up tags if less then 2
269
            
270
48
            nodes_per_element = num_nodes_per_elem_type(elem_type);
271
48
            m_elements_types.push_back(elem_type);
272
48
            m_elements_lengths.push_back(nodes_per_element);
273
274
48
            elem_tag -= 1;
275
48
            m_elements_ids.push_back(elem_tag);
276
48
            m_elements_nodes_idx.push_back(m_elements.size());
277
            // Parse node idx.
278
69
            for (size_t j=0; j<nodes_per_element; j++) {
279
21
                int node_tag;
280
21
                fin >> node_tag;
281
21
                m_elements.push_back(node_dense_index(node_tag)); // msh index starts from 1.
282
21
            }
283
48
        }
284
230
    }
285
    // debug
286
230
    assert(m_elements_types.size()   == m_elements_ids.size());
287
214
    assert(m_elements_tags[0].size() == m_elements_ids.size());
288
166
    assert(m_elements_tags[1].size() == m_elements_ids.size());
289
166
    assert(m_elements_lengths.size() == m_elements_ids.size());
290
166
}
291
292
5.37k
IGL_INLINE void igl::MshLoader::parse_node_field( std::ifstream& fin ) {
293
5.37k
    size_t num_string_tags;
294
5.37k
    size_t num_real_tags;
295
5.37k
    size_t num_int_tags;
296
297
5.37k
    fin >> num_string_tags;
298
5.37k
    std::vector<std::string> str_tags(num_string_tags);
299
300
8.17M
    for (size_t i=0; i<num_string_tags; i++) {
301
8.17M
        igl::_msh_eat_white_space(fin);
302
8.17M
        if (fin.peek() == '\"') {
303
            // Handle field name between quotes.
304
1.44k
            char buf[128];
305
1.44k
            fin.get(); // remove the quote at the beginning.
306
1.44k
            fin.getline(buf, 128, '\"');
307
1.44k
            str_tags[i] = std::string(buf);
308
8.17M
        } else {
309
8.17M
            fin >> str_tags[i];
310
8.17M
        }
311
8.17M
    }
312
313
5.37k
    fin >> num_real_tags;
314
5.37k
    std::vector<Float> real_tags(num_real_tags);
315
15.7M
    for (size_t i=0; i<num_real_tags; i++)
316
15.7M
        fin >> real_tags[i];
317
318
5.37k
    fin >> num_int_tags;
319
5.37k
    std::vector<int> int_tags(num_int_tags);
320
447k
    for (size_t i=0; i<num_int_tags; i++)
321
441k
        fin >> int_tags[i];
322
323
5.37k
    if (num_string_tags <= 0 || num_int_tags <= 2) {
324
8
        throw std::runtime_error("Unexpected number of field tags");
325
8
    }
326
5.36k
    std::string fieldname = str_tags[0];
327
5.36k
    int num_components    = int_tags[1];
328
5.36k
    int num_entries       = int_tags[2];
329
330
5.36k
    std::vector<Float> field((m_nodes.size()/3)*num_components);
331
332
5.36k
    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
5.36k
    } else {
348
5.36k
        int node_tag;
349
5.39k
        for (size_t i=0; i<num_entries; i++) {
350
35
            fin >> node_tag;
351
35
            const int node_idx = node_dense_index(node_tag);
352
35
            for (size_t j=0; j<num_components; j++) {
353
0
                fin >> field[node_idx*num_components+j];
354
0
            }
355
35
        }
356
5.36k
    }
357
    
358
5.36k
    m_node_fields_names.push_back(fieldname);
359
5.36k
    m_node_fields.push_back(field);
360
5.36k
    m_node_fields_components.push_back(num_components);
361
5.36k
}
362
363
1.38k
IGL_INLINE void igl::MshLoader::parse_element_field(std::ifstream& fin) {
364
1.38k
    size_t num_string_tags;
365
1.38k
    size_t num_real_tags;
366
1.38k
    size_t num_int_tags;
367
368
1.38k
    fin >> num_string_tags;
369
1.38k
    std::vector<std::string> str_tags(num_string_tags);
370
85.8k
    for (size_t i=0; i<num_string_tags; i++) {
371
84.4k
        igl::_msh_eat_white_space(fin);
372
84.4k
        if (fin.peek() == '\"') {
373
            // Handle field name between quoates.
374
206
            char buf[128];
375
206
            fin.get(); // remove the quote at the beginning.
376
206
            fin.getline(buf, 128, '\"');
377
206
            str_tags[i] = buf;
378
84.2k
        } else {
379
84.2k
            fin >> str_tags[i];
380
84.2k
        }
381
84.4k
    }
382
383
1.38k
    fin >> num_real_tags;
384
1.38k
    std::vector<Float> real_tags(num_real_tags);
385
1.46k
    for (size_t i=0; i<num_real_tags; i++)
386
83
        fin >> real_tags[i];
387
388
1.38k
    fin >> num_int_tags;
389
1.38k
    std::vector<int> int_tags(num_int_tags);
390
62.4M
    for (size_t i=0; i<num_int_tags; i++)
391
62.4M
        fin >> int_tags[i];
392
393
1.38k
    if (num_string_tags <= 0 || num_int_tags <= 2) {
394
2
        throw std::runtime_error("Invalid file format");
395
2
    }
396
1.38k
    std::string fieldname = str_tags[0];
397
1.38k
    int num_components = int_tags[1];
398
1.38k
    int num_entries = int_tags[2];
399
1.38k
    std::vector<Float> field(m_elements_ids.size()*num_components);
400
401
1.38k
    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.38k
    } else {
417
1.38k
        int elem_tag;
418
1.44k
        for (size_t i=0; i<num_entries; i++) {
419
57
            fin >> elem_tag;
420
57
            const int elem_idx = element_dense_index(elem_tag);
421
57
            for (size_t j=0; j<num_components; j++) {
422
0
                fin >> field[elem_idx*num_components+j];
423
0
            }
424
57
        }
425
1.38k
    }
426
1.38k
    m_element_fields_names.push_back(fieldname);
427
1.38k
    m_element_fields.push_back(field);
428
1.38k
    m_element_fields_components.push_back(num_components);
429
1.38k
}
430
431
IGL_INLINE void igl::MshLoader::parse_unknown_field(std::ifstream& fin,
432
2.48k
        const std::string& fieldname) {
433
2.48k
    std::cerr << "Warning: \"" << fieldname << "\" not supported yet.  Ignored." << std::endl;
434
2.48k
    std::string endmark = fieldname.substr(0,1) + "End"
435
2.48k
        + fieldname.substr(1,fieldname.size()-1);
436
437
2.48k
    std::string buf("");
438
10.8k
    while (buf != endmark && !fin.eof()) {
439
8.33k
        fin >> buf;
440
8.33k
    }
441
2.48k
}
442
443
48
IGL_INLINE int igl::MshLoader::num_nodes_per_elem_type(int elem_type) {
444
48
    int nodes_per_element = 0;
445
48
    switch (elem_type) {
446
9
        case ELEMENT_LINE:         // 2-node line
447
9
            nodes_per_element = 2; 
448
9
            break;
449
5
        case ELEMENT_TRI:
450
5
            nodes_per_element = 3; // 3-node triangle
451
5
            break;
452
5
        case ELEMENT_QUAD:
453
5
            nodes_per_element = 4; // 5-node quad
454
5
            break;
455
0
        case ELEMENT_TET:
456
0
            nodes_per_element = 4; // 4-node tetrahedra
457
0
            break;
458
0
        case ELEMENT_HEX:          // 8-node hexahedron
459
0
            nodes_per_element = 8; 
460
0
            break;
461
0
        case ELEMENT_PRISM:        // 6-node prism
462
0
            nodes_per_element = 6; 
463
0
            break;
464
2
        case ELEMENT_LINE_2ND_ORDER: 
465
2
            nodes_per_element = 3;
466
2
            break;
467
0
        case ELEMENT_TRI_2ND_ORDER: 
468
0
            nodes_per_element = 6;
469
0
            break;
470
0
        case ELEMENT_QUAD_2ND_ORDER: 
471
0
            nodes_per_element = 9;
472
0
            break;
473
0
        case ELEMENT_TET_2ND_ORDER: 
474
0
            nodes_per_element = 10;
475
0
            break;
476
0
        case ELEMENT_HEX_2ND_ORDER: 
477
0
            nodes_per_element = 27;
478
0
            break;
479
0
        case ELEMENT_PRISM_2ND_ORDER: 
480
0
            nodes_per_element = 18;
481
0
            break;
482
0
        case ELEMENT_PYRAMID_2ND_ORDER: 
483
0
            nodes_per_element = 14;
484
0
            break;
485
0
        case ELEMENT_POINT:        // 1-node point
486
0
            nodes_per_element = 1; 
487
0
            break;
488
27
        default:
489
27
            std::stringstream err_msg;
490
27
                err_msg << "Element type (" << elem_type << ") is not supported yet."
491
27
                << std::endl;
492
27
            throw std::runtime_error(err_msg.str());
493
48
    }
494
21
    return nodes_per_element;
495
48
}
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
}