Coverage Report

Created: 2026-07-13 06:58

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
331k
    void inline _msh_eat_white_space(std::ifstream& fin) {
22
331k
        char next = fin.peek();
23
336k
        while (next == '\n' || next == ' ' || next == '\t' || next == '\r') {
24
5.32k
            fin.get();
25
5.32k
            next = fin.peek();
26
5.32k
        }
27
331k
    }
28
}
29
30
385
IGL_INLINE igl::MshLoader::MshLoader(const std::string &filename) {
31
385
    std::ifstream fin(filename, std::ios::in | std::ios::binary);
32
33
385
    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
385
    std::string buf;
40
385
    double version;
41
385
    int type;
42
385
    fin >> buf;
43
385
    if (buf != "$MeshFormat") { throw std::runtime_error("Unexpected .msh format"); }
44
45
364
    fin >> version >> type >> m_data_size;
46
364
    m_binary = (type == 1);
47
364
    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
364
    if (m_data_size != 8) {
57
3
        std::stringstream err_msg;
58
3
        err_msg << "Error: data size must be 8 bytes." << std::endl;
59
3
        throw std::runtime_error(err_msg.str());
60
3
    }
61
361
    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
361
    if (m_binary) {
69
0
        int one;
70
0
        igl::_msh_eat_white_space(fin);
71
0
        fin.read(reinterpret_cast<char*>(&one), sizeof(int));
72
0
        if (one != 1) {
73
0
            std::stringstream err_msg;
74
0
                err_msg << "Binary msh file " << filename
75
0
                << " is saved with different endianness than this machine."
76
0
                << std::endl;
77
0
            throw std::runtime_error(err_msg.str());
78
0
        }
79
0
    }
80
81
361
    fin >> buf;
82
361
    if (buf != "$EndMeshFormat") 
83
10
    { 
84
10
        std::stringstream err_msg;
85
10
        err_msg << "Unexpected contents in the file header." << std::endl;
86
10
        throw std::runtime_error(err_msg.str());
87
10
    }
88
89
19.8k
    while (!fin.eof()) {
90
19.5k
        buf.clear();
91
19.5k
        fin >> buf;
92
19.5k
        if (buf == "$Nodes") {
93
2.64k
            parse_nodes(fin);
94
2.64k
            fin >> buf;
95
2.64k
            if (buf != "$EndNodes") { throw std::runtime_error("Unexpected tag"); }
96
16.9k
        } else if (buf == "$Elements") {
97
121
            parse_elements(fin);
98
121
            fin >> buf;
99
121
            if (buf != "$EndElements") { throw std::runtime_error("Unexpected tag"); }
100
16.8k
        } else if (buf == "$NodeData") {
101
5.16k
            parse_node_field(fin);
102
5.16k
            fin >> buf;
103
5.16k
            if (buf != "$EndNodeData") { throw std::runtime_error("Unexpected tag"); }
104
11.6k
        } else if (buf == "$ElementData") {
105
1.78k
            parse_element_field(fin);
106
1.78k
            fin >> buf;
107
1.78k
            if (buf != "$EndElementData") { throw std::runtime_error("Unexpected tag"); }
108
9.88k
        } else if (fin.eof()) {
109
23
            break;
110
9.86k
        } else {
111
9.86k
            parse_unknown_field(fin, buf);
112
9.86k
        }
113
19.5k
    }
114
246
    fin.close();
115
246
}
116
117
11
IGL_INLINE int igl::MshLoader::node_dense_index(int node_tag) const {
118
11
    const auto it = m_node_tag_to_dense.find(node_tag);
119
11
    if (it == m_node_tag_to_dense.end()) {
120
11
        std::stringstream err_msg;
121
11
        err_msg << "Unknown node tag: " << node_tag;
122
11
        throw std::runtime_error(err_msg.str());
123
11
    }
124
0
  return it->second;
125
11
}
126
127
7
IGL_INLINE int igl::MshLoader::element_dense_index(int elem_tag) const {
128
7
  const auto it = m_element_tag_to_dense.find(elem_tag);
129
7
    if (it == m_element_tag_to_dense.end()) {
130
7
    std::stringstream err_msg;
131
7
        err_msg << "Unknown element tag: " << elem_tag;
132
7
    throw std::runtime_error(err_msg.str());
133
7
    }
134
0
  return it->second;
135
7
}
136
137
2.64k
IGL_INLINE void igl::MshLoader::parse_nodes(std::ifstream& fin) {
138
2.64k
    size_t num_nodes;
139
2.64k
    fin >> num_nodes;
140
2.64k
    m_nodes.resize(num_nodes*3);
141
2.64k
    m_node_tag_to_dense.clear();
142
143
2.64k
    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.64k
    } else {
167
2.64k
        int node_tag;
168
3.87k
        for (size_t i=0; i<num_nodes; i++) {
169
1.24k
            fin >> node_tag;
170
171
1.24k
            if (node_tag <= 0) {
172
6
        throw std::runtime_error("Invalid node tag");
173
6
            }
174
1.23k
            if (m_node_tag_to_dense.find(node_tag) != m_node_tag_to_dense.end()) {
175
4
                throw std::runtime_error("Duplicate node tag");
176
4
            }
177
1.23k
            m_node_tag_to_dense[node_tag] = static_cast<int>(i);
178
            // here it's 3D node explicitly
179
1.23k
            fin >> m_nodes[i*3]
180
1.23k
                >> m_nodes[i*3+1]
181
1.23k
                >> m_nodes[i*3+2];
182
1.23k
        }
183
2.64k
    }
184
2.64k
}
185
186
121
IGL_INLINE void igl::MshLoader::parse_elements(std::ifstream& fin) {
187
121
    m_elements_tags.resize(2); //hardcoded to have 2 tags
188
121
    size_t num_elements;
189
121
    fin >> num_elements;
190
121
    m_element_tag_to_dense.clear();
191
192
121
    size_t nodes_per_element;
193
194
121
    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
121
    } else {
248
123
        for (size_t i=0; i<num_elements; i++) {
249
            // Parse per element header
250
3
            int elem_tag, elem_type, num_tags;
251
3
            fin >> elem_tag >> elem_type >> num_tags;
252
253
3
            if (elem_tag <= 0) {
254
1
                throw std::runtime_error("Invalid element tag");
255
1
            }
256
2
            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
2
            m_element_tag_to_dense[elem_tag] = static_cast<int>(m_elements_ids.size());
260
261
            // read tags.
262
244M
            for (size_t j=0; j<num_tags; j++) {
263
244M
                int tag;
264
244M
                fin >> tag;
265
244M
                if(j<2) m_elements_tags[j].push_back(tag);
266
244M
            }
267
4
            for (size_t j=num_tags; j<2; j++) 
268
2
                m_elements_tags[j].push_back(-1); // fill up tags if less then 2
269
            
270
2
            nodes_per_element = num_nodes_per_elem_type(elem_type);
271
2
            m_elements_types.push_back(elem_type);
272
2
            m_elements_lengths.push_back(nodes_per_element);
273
274
2
            elem_tag -= 1;
275
2
            m_elements_ids.push_back(elem_tag);
276
2
            m_elements_nodes_idx.push_back(m_elements.size());
277
            // Parse node idx.
278
3
            for (size_t j=0; j<nodes_per_element; j++) {
279
1
                int node_tag;
280
1
                fin >> node_tag;
281
1
                m_elements.push_back(node_dense_index(node_tag)); // msh index starts from 1.
282
1
            }
283
2
        }
284
121
    }
285
    // debug
286
121
    assert(m_elements_types.size()   == m_elements_ids.size());
287
120
    assert(m_elements_tags[0].size() == m_elements_ids.size());
288
118
    assert(m_elements_tags[1].size() == m_elements_ids.size());
289
118
    assert(m_elements_lengths.size() == m_elements_ids.size());
290
118
}
291
292
5.16k
IGL_INLINE void igl::MshLoader::parse_node_field( std::ifstream& fin ) {
293
5.16k
    size_t num_string_tags;
294
5.16k
    size_t num_real_tags;
295
5.16k
    size_t num_int_tags;
296
297
5.16k
    fin >> num_string_tags;
298
5.16k
    std::vector<std::string> str_tags(num_string_tags);
299
300
332k
    for (size_t i=0; i<num_string_tags; i++) {
301
327k
        igl::_msh_eat_white_space(fin);
302
327k
        if (fin.peek() == '\"') {
303
            // Handle field name between quotes.
304
975
            char buf[128];
305
975
            fin.get(); // remove the quote at the beginning.
306
975
            fin.getline(buf, 128, '\"');
307
975
            str_tags[i] = std::string(buf);
308
326k
        } else {
309
326k
            fin >> str_tags[i];
310
326k
        }
311
327k
    }
312
313
5.16k
    fin >> num_real_tags;
314
5.16k
    std::vector<Float> real_tags(num_real_tags);
315
5.56k
    for (size_t i=0; i<num_real_tags; i++)
316
397
        fin >> real_tags[i];
317
318
5.16k
    fin >> num_int_tags;
319
5.16k
    std::vector<int> int_tags(num_int_tags);
320
21.6k
    for (size_t i=0; i<num_int_tags; i++)
321
16.4k
        fin >> int_tags[i];
322
323
5.16k
    if (num_string_tags <= 0 || num_int_tags <= 2) {
324
5
        throw std::runtime_error("Unexpected number of field tags");
325
5
    }
326
5.16k
    std::string fieldname = str_tags[0];
327
5.16k
    int num_components    = int_tags[1];
328
5.16k
    int num_entries       = int_tags[2];
329
330
5.16k
    std::vector<Float> field((m_nodes.size()/3)*num_components);
331
332
5.16k
    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.16k
    } else {
348
5.16k
        int node_tag;
349
5.17k
        for (size_t i=0; i<num_entries; i++) {
350
10
            fin >> node_tag;
351
10
            const int node_idx = node_dense_index(node_tag);
352
10
            for (size_t j=0; j<num_components; j++) {
353
0
                fin >> field[node_idx*num_components+j];
354
0
            }
355
10
        }
356
5.16k
    }
357
    
358
5.16k
    m_node_fields_names.push_back(fieldname);
359
5.16k
    m_node_fields.push_back(field);
360
5.16k
    m_node_fields_components.push_back(num_components);
361
5.16k
}
362
363
1.78k
IGL_INLINE void igl::MshLoader::parse_element_field(std::ifstream& fin) {
364
1.78k
    size_t num_string_tags;
365
1.78k
    size_t num_real_tags;
366
1.78k
    size_t num_int_tags;
367
368
1.78k
    fin >> num_string_tags;
369
1.78k
    std::vector<std::string> str_tags(num_string_tags);
370
5.89k
    for (size_t i=0; i<num_string_tags; i++) {
371
4.11k
        igl::_msh_eat_white_space(fin);
372
4.11k
        if (fin.peek() == '\"') {
373
            // Handle field name between quoates.
374
128
            char buf[128];
375
128
            fin.get(); // remove the quote at the beginning.
376
128
            fin.getline(buf, 128, '\"');
377
128
            str_tags[i] = buf;
378
3.98k
        } else {
379
3.98k
            fin >> str_tags[i];
380
3.98k
        }
381
4.11k
    }
382
383
1.78k
    fin >> num_real_tags;
384
1.78k
    std::vector<Float> real_tags(num_real_tags);
385
184M
    for (size_t i=0; i<num_real_tags; i++)
386
184M
        fin >> real_tags[i];
387
388
1.78k
    fin >> num_int_tags;
389
1.78k
    std::vector<int> int_tags(num_int_tags);
390
448k
    for (size_t i=0; i<num_int_tags; i++)
391
446k
        fin >> int_tags[i];
392
393
1.78k
    if (num_string_tags <= 0 || num_int_tags <= 2) {
394
3
        throw std::runtime_error("Invalid file format");
395
3
    }
396
1.77k
    std::string fieldname = str_tags[0];
397
1.77k
    int num_components = int_tags[1];
398
1.77k
    int num_entries = int_tags[2];
399
1.77k
    std::vector<Float> field(m_elements_ids.size()*num_components);
400
401
1.77k
    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.77k
    } else {
417
1.77k
        int elem_tag;
418
1.78k
        for (size_t i=0; i<num_entries; i++) {
419
7
            fin >> elem_tag;
420
7
            const int elem_idx = element_dense_index(elem_tag);
421
7
            for (size_t j=0; j<num_components; j++) {
422
0
                fin >> field[elem_idx*num_components+j];
423
0
            }
424
7
        }
425
1.77k
    }
426
1.77k
    m_element_fields_names.push_back(fieldname);
427
1.77k
    m_element_fields.push_back(field);
428
1.77k
    m_element_fields_components.push_back(num_components);
429
1.77k
}
430
431
IGL_INLINE void igl::MshLoader::parse_unknown_field(std::ifstream& fin,
432
9.86k
        const std::string& fieldname) {
433
9.86k
    std::cerr << "Warning: \"" << fieldname << "\" not supported yet.  Ignored." << std::endl;
434
9.86k
    std::string endmark = fieldname.substr(0,1) + "End"
435
9.86k
        + fieldname.substr(1,fieldname.size()-1);
436
437
9.86k
    std::string buf("");
438
37.1k
    while (buf != endmark && !fin.eof()) {
439
27.2k
        fin >> buf;
440
27.2k
    }
441
9.86k
}
442
443
2
IGL_INLINE int igl::MshLoader::num_nodes_per_elem_type(int elem_type) {
444
2
    int nodes_per_element = 0;
445
2
    switch (elem_type) {
446
0
        case ELEMENT_LINE:         // 2-node line
447
0
            nodes_per_element = 2; 
448
0
            break;
449
0
        case ELEMENT_TRI:
450
0
            nodes_per_element = 3; // 3-node triangle
451
0
            break;
452
1
        case ELEMENT_QUAD:
453
1
            nodes_per_element = 4; // 5-node quad
454
1
            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
0
        case ELEMENT_LINE_2ND_ORDER: 
465
0
            nodes_per_element = 3;
466
0
            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
1
        default:
489
1
            std::stringstream err_msg;
490
1
                err_msg << "Element type (" << elem_type << ") is not supported yet."
491
1
                << std::endl;
492
1
            throw std::runtime_error(err_msg.str());
493
2
    }
494
1
    return nodes_per_element;
495
2
}
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
}