BinaryFileReader.cc 16.6 KB
Newer Older
Martin Heistermann's avatar
Martin Heistermann committed
1
2
#include <OpenVolumeMesh/IO/detail/BinaryFileReader.hh>
#include <OpenVolumeMesh/IO/detail/BinaryFileReader_impl.hh>
3
#include <OpenVolumeMesh/IO/PropertyCodecs.hh>
Martin Heistermann's avatar
Martin Heistermann committed
4
5
#include <OpenVolumeMesh/IO/detail/ovmb_format.hh>
#include <OpenVolumeMesh/IO/detail/ovmb_codec.hh>
6
#include <OpenVolumeMesh/IO/detail/exceptions.hh>
Martin Heistermann's avatar
Martin Heistermann committed
7
8
9
10
11
12
#include <OpenVolumeMesh/Core/Handles.hh>

#include <istream>
#include <cassert>
#include <numeric>
#include <iostream>
13
#include <string>
Martin Heistermann's avatar
Martin Heistermann committed
14
15
16
17
18
19
20
21

namespace OpenVolumeMesh {
class TetrahedralMeshTopologyKernel;
class HexahedralMeshTopologyKernel;
}

namespace OpenVolumeMesh::IO::detail {

Martin Heistermann's avatar
Martin Heistermann committed
22
bool BinaryFileReader::read_header()
Martin Heistermann's avatar
Martin Heistermann committed
23
{
24
25
26
27
28
29
30
31
32
33
34
35
    try {
        if (state_ == ReadState::Init) {
            //stream_.seek(0);
            auto decoder = stream_.make_decoder(ovmb_size<FileHeader>);
            auto ok = read(decoder, file_header_);
            if (ok) {
                state_ = ReadState::HeaderRead;
                return true;
            } else {
                state_ = ReadState::ErrorInvalidFile;
                return false;
            }
Martin Heistermann's avatar
Martin Heistermann committed
36
        }
37
38
39
40
41
        return true;
    } catch (parse_error &e) {
        state_ = ReadState::ErrorInvalidFile;
        error_msg_ = std::string("parse_error: ") + e.what();
        return false;
Martin Heistermann's avatar
Martin Heistermann committed
42
43
44
    }
}

Martin Heistermann's avatar
Martin Heistermann committed
45
46

bool BinaryFileReader::validate_span(uint64_t total, uint64_t read, ArraySpan const&span)
Martin Heistermann's avatar
Martin Heistermann committed
47
{
48
    if (span.first != read) {
Martin Heistermann's avatar
Martin Heistermann committed
49
        state_ = ReadState::Error;
50
        error_msg_ = "Invalid span start, must resume where the last chunk of the same topo type left off";
Martin Heistermann's avatar
Martin Heistermann committed
51
52
53
54
        return false;
    }
    if (total - read < span.count) {
        state_ = ReadState::Error;
55
        error_msg_ = "Invalid span, end exceeds total entity count";
Martin Heistermann's avatar
Martin Heistermann committed
56
57
58
59
        return false;
    }
    return true;
}
Martin Heistermann's avatar
Martin Heistermann committed
60

61
62
63
64
65
66
67
template<typename T, typename FuncMakeT>
bool
BinaryFileReader::read_n_ints(Decoder &reader,
                              IntEncoding enc,
                              std::vector<T> &out_vec,
                              size_t count,
                              FuncMakeT make_t)
Martin Heistermann's avatar
Martin Heistermann committed
68
69
{
    if (!is_valid(enc)) {
Martin Heistermann's avatar
Martin Heistermann committed
70
        state_ = ReadState::ErrorInvalidEncoding;
71
72
73
74
75
76
77
78
79
80
        return false;
    }
    auto needed = count * elem_size(enc);
    if (reader.remaining_bytes() < needed)
    {
        state_ = ReadState::ErrorInvalidFile;
        error_msg_ = "read_n_ints: not enough data in chunk, need " +
                std::to_string(needed) + ", have " +
                std::to_string(reader.remaining_bytes());
        return false;
Martin Heistermann's avatar
Martin Heistermann committed
81
82
    }

83
84
85
    out_vec.clear();
    out_vec.reserve(count);

Martin Heistermann's avatar
Martin Heistermann committed
86
87
    auto read_all = [&](auto read_one)
    {
Martin Heistermann's avatar
Martin Heistermann committed
88
        for (size_t i = 0; i < count; ++i) {
89
            out_vec.push_back(make_t(read_one(reader)));
Martin Heistermann's avatar
Martin Heistermann committed
90
91
92
        }
    };

Martin Heistermann's avatar
Martin Heistermann committed
93
    call_with_decoder(enc, read_all);
Martin Heistermann's avatar
Martin Heistermann committed
94

95
    return true;
Martin Heistermann's avatar
Martin Heistermann committed
96
97
}

98
void BinaryFileReader::read_topo_chunk(Decoder &reader)
Martin Heistermann's avatar
Martin Heistermann committed
99
{
100
    TopoChunkHeader header;
Martin Heistermann's avatar
Martin Heistermann committed
101
    read(reader, header);
102
103
104
105

    if (header.span.count == 0) {
        state_ = ReadState::ErrorEmptyList;
        error_msg_ = "TOPO chunk contains no data";
Martin Heistermann's avatar
Martin Heistermann committed
106
107
        return;
    }
108
109
110
111

    if (!is_valid(header.handle_encoding)) {
        state_ = ReadState::ErrorInvalidEncoding;
        error_msg_ = "TOPO chunk: invalid handle encoding";
Martin Heistermann's avatar
Martin Heistermann committed
112
113
        return;
    }
114
115
116
117
118

    if (!is_valid(header.entity)) {
        state_ = ReadState::ErrorInvalidFile;
        error_msg_ = "TOPO chunk: Invalid topology entity " + std::to_string(static_cast<size_t>(header.entity));
        return;
Martin Heistermann's avatar
Martin Heistermann committed
119
    }
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167

    if (header.valence != 0 && header.valence_encoding != IntEncoding::None) {
        state_ = ReadState::ErrorInvalidFile;
        error_msg_ = "TOPO edge chunk: valence encoding must be None for fixed valences";
        return;
    }


    ValenceVec valences;

    uint64_t total_handles = header.valence * header.span.count;

    if (header.valence == 0)
    {
        if (header.valence_encoding == IntEncoding::None) {
            state_ = ReadState::ErrorInvalidFile;
            error_msg_ = "TOPO edge chunk: valence encoding must not be None for variable valences";
            return;
        }
        auto success = read_n_ints(reader,
                                   header.valence_encoding,
                                   valences,
                                   header.span.count,
                                   [](uint32_t x){return x;});
        if (!success) {
            return;
        }
        assert(header.span.count == valences.size());
        total_handles = std::accumulate(valences.begin(), valences.end(), 0ULL);
    }
    auto handle_size = elem_size(header.handle_encoding);
    auto expected_bytes = total_handles * handle_size;
    if (reader.remaining_bytes() != expected_bytes) {
        state_ = ReadState::Error;
        error_msg_ = "TOPO chunk: number of remaining bytes incorrect, expecting "
+   std::to_string(expected_bytes) + ", have " + std::to_string(reader.remaining_bytes());
        return;
    }

    switch (header.entity)  {
    case OpenVolumeMesh::IO::detail::TopoEntity::Edge:
        read_edges(reader, header);
        return;
    case OpenVolumeMesh::IO::detail::TopoEntity::Face:
        read_faces(reader, header, valences);
        return;
    case OpenVolumeMesh::IO::detail::TopoEntity::Cell:
        read_cells(reader, header, valences);
Martin Heistermann's avatar
Martin Heistermann committed
168
        return;
Martin Heistermann's avatar
Martin Heistermann committed
169
170
171
    }
}

172
void BinaryFileReader::read_edges(Decoder &reader, const TopoChunkHeader &header)
Martin Heistermann's avatar
Martin Heistermann committed
173
{
174
    if (!validate_span(file_header_.n_edges, n_edges_read_, header.span)) {
Martin Heistermann's avatar
Martin Heistermann committed
175
176
        return;
    }
177
178
179
    if (header.valence != 2) {
        state_ = ReadState::ErrorInvalidFile;
        error_msg_ = "TOPO edge chunk: valence must be 2";
Martin Heistermann's avatar
Martin Heistermann committed
180
        return;
181
    }
Martin Heistermann's avatar
Martin Heistermann committed
182
183
184
185

    auto read_all = [&](auto read_one)
    {
        for (size_t i = 0; i < header.span.count; ++i) {
186
187
            uint64_t src = read_one(reader) + header.span.first;
            uint64_t dst = read_one(reader) + header.span.first;
Martin Heistermann's avatar
Martin Heistermann committed
188
189
            if (src >= n_verts_read_ || dst >= n_verts_read_) {
                state_ = ReadState::ErrorHandleRange;
190
                return;
Martin Heistermann's avatar
Martin Heistermann committed
191
192
193
            } else {
                auto vh_src = VertexHandle::from_unsigned(src);
                auto vh_dst = VertexHandle::from_unsigned(dst);
Martin Heistermann's avatar
Martin Heistermann committed
194
                mesh_->add_edge(vh_src, vh_dst, true);
Martin Heistermann's avatar
Martin Heistermann committed
195
196
197
198
            }
        }
    };

199
    call_with_decoder(header.handle_encoding, read_all);
Martin Heistermann's avatar
Martin Heistermann committed
200
201
202
203
204
205

    if (state_ == ReadState::ReadingChunks) {
        n_edges_read_ += header.span.count;
    }
}

206
void BinaryFileReader::read_faces(Decoder &reader, const TopoChunkHeader &header, const ValenceVec &_valences)
Martin Heistermann's avatar
Martin Heistermann committed
207
208
209
{
    if (!validate_span(file_header_.n_faces, n_faces_read_, header.span))
        return;
210
211

    if (file_header_.topo_type == TopoType::Tetrahedral && header.valence != 3) {
Martin Heistermann's avatar
Martin Heistermann committed
212
        state_ = ReadState::ErrorInvalidTopoType;
213
        error_msg_ = "TOPO chunk: Faces of tetrahedral meshes must have a fixed valence of 3";
Martin Heistermann's avatar
Martin Heistermann committed
214
215
        return;
    }
216
    if (file_header_.topo_type == TopoType::Hexahedral && header.valence != 4) {
Martin Heistermann's avatar
Martin Heistermann committed
217
        state_ = ReadState::ErrorInvalidTopoType;
218
        error_msg_ = "TOPO chunk: Faces of hexahedral meshes must have a fixed valence of 4";
Martin Heistermann's avatar
Martin Heistermann committed
219
220
        return;
    }
221
    assert(header.valence != 0 || _valences.size() == header.span.count);
222
223
224
225
226
227
228
229
230

    auto read_heh = [&](uint32_t x)
    {
        auto idx = x + header.handle_offset;
        if (idx >= 2 * n_edges_read_) {
            throw parse_error("Invalid Halfedge handle");
        }
        return HEH::from_unsigned(idx);
    };
231
    for (uint64_t i = 0; i < header.span.count; ++i)
Martin Heistermann's avatar
Martin Heistermann committed
232
    {
233
234
235
236
237
238
239
        uint32_t valence = header.valence == 0 ? _valences[i] : header.valence;
        std::vector<HEH> halfedges;
        halfedges.reserve(valence);
        auto success = read_n_ints(reader,
                                   header.handle_encoding,
                                   halfedges,
                                   valence,
240
                                   read_heh);
241
242
        if (!success) break;
        mesh_->add_face(std::move(halfedges), options_.topology_check);
Martin Heistermann's avatar
Martin Heistermann committed
243
244
245
246
247
248
249
    };

    if (state_ == ReadState::ReadingChunks) {
        n_faces_read_ += header.span.count;
    }
}

250
void BinaryFileReader::read_cells(Decoder &reader, const TopoChunkHeader &header, const ValenceVec &_valences)
Martin Heistermann's avatar
Martin Heistermann committed
251
252
253
254
{
    if (!validate_span(file_header_.n_cells, n_cells_read_, header.span))
        return;

255
    if (file_header_.topo_type == TopoType::Tetrahedral && header.valence != 4) {
Martin Heistermann's avatar
Martin Heistermann committed
256
        state_ = ReadState::ErrorInvalidTopoType;
257
        error_msg_ = "TOPO chunk: Cells of tetrahedral meshes must have a fixed valence of 4";
Martin Heistermann's avatar
Martin Heistermann committed
258
259
260
        return;
    }

261
    if (file_header_.topo_type == TopoType::Hexahedral && header.valence != 6) {
Martin Heistermann's avatar
Martin Heistermann committed
262
        state_ = ReadState::ErrorInvalidTopoType;
263
        error_msg_ = "TOPO chunk: Cells of hexahedral meshes must have a fixed valence of 6";
Martin Heistermann's avatar
Martin Heistermann committed
264
265
        return;
    }
266

267
268
269
270
271
272
273
274
275
276
277
    assert(header.valence != 0 || _valences.size() == header.span.count);

    auto read_hfh = [&](uint32_t x)
    {
        auto idx = x + header.handle_offset;
        if (idx >= 2 * n_faces_read_) {
            throw parse_error("Invalid Halfface handle");
        }
        return HFH::from_unsigned(idx);
    };

278
    for (uint64_t i = 0; i < header.span.count; ++i)
Martin Heistermann's avatar
Martin Heistermann committed
279
    {
280
281
282
283
284
285
286
        uint32_t valence = header.valence == 0 ? _valences[i] : header.valence;
        std::vector<HFH> halffaces;
        halffaces.reserve(valence);
        auto success = read_n_ints(reader,
                                   header.handle_encoding,
                                   halffaces,
                                   valence,
287
                                   read_hfh);
288
289
        if (!success) break;
        mesh_->add_cell(std::move(halffaces), options_.topology_check);
Martin Heistermann's avatar
Martin Heistermann committed
290
291
292
293
294
    };

    if (state_ == ReadState::ReadingChunks) {
        n_cells_read_ += header.span.count;
    }
295

Martin Heistermann's avatar
Martin Heistermann committed
296
297
}

298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
void BinaryFileReader::read_prop_chunk(Decoder &reader)
{
    PropChunkHeader header;
    read(reader, header);
    if (header.idx >= props_.size()) {
        state_ = ReadState::Error; // TODO more specific error
        return;
    }
    auto &prop = props_[header.idx];
    if (!prop.decoder) {
        reader.skip();
        return;
    }
    uint64_t n = 0;
    switch (prop.entity) {
        case PropertyEntity::Vertex:   n = n_verts_read_; break;
        case PropertyEntity::Edge:     n = n_edges_read_; break;
        case PropertyEntity::Face:     n = n_faces_read_; break;
        case PropertyEntity::Cell:     n = n_cells_read_; break;
        case PropertyEntity::HalfEdge: n = 2 * n_edges_read_; break;
        case PropertyEntity::HalfFace: n = 2 * n_faces_read_; break;
        case PropertyEntity::Mesh:     n = 1; break;
    }
    if (header.span.first >= n || n - header.span.first < header.span.count) {
        state_ = ReadState::ErrorHandleRange;
        return;
    }
    prop.decoder->deserialize(prop.prop.get(),
            reader,
            static_cast<size_t>(header.span.first),
            static_cast<size_t>(header.span.first + header.span.count));
}


Martin Heistermann's avatar
Martin Heistermann committed
332
333
void
BinaryFileReader::
334
read_vertices_chunk(Decoder &reader)
Martin Heistermann's avatar
Martin Heistermann committed
335
{
Martin Heistermann's avatar
Martin Heistermann committed
336
337
338
    VertexChunkHeader header;
    read(reader, header);

339
    if (!is_valid(header.vertex_encoding)) {
Martin Heistermann's avatar
Martin Heistermann committed
340
        state_ = ReadState::ErrorInvalidEncoding;
Martin Heistermann's avatar
Martin Heistermann committed
341
342
        return;
    }
Martin Heistermann's avatar
Martin Heistermann committed
343
344
    if (!validate_span(file_header_.n_verts, n_verts_read_, header.span))
        return;
Martin Heistermann's avatar
Martin Heistermann committed
345

346
    auto pos_size = elem_size(header.vertex_encoding) * file_header_.vertex_dim;
Martin Heistermann's avatar
Martin Heistermann committed
347
348
349
350
351
352
353
354
355
356
    if (reader.remaining_bytes() != header.span.count  * pos_size) {
#if 0
        std::cerr << "vert chunk size" << std::endl;
        std::cerr << "remaining: " << reader.remaining_bytes() << std::endl;
        std::cerr << "expected: " << header.span.count *  pos_size << std::endl;
#endif
        state_ = ReadState::ErrorInvalidChunkSize;
        return;
    }

357
    geometry_reader_->read(reader, header.vertex_encoding, header.span.first, header.span.count);
Martin Heistermann's avatar
Martin Heistermann committed
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385

    if (state_ == ReadState::ReadingChunks) {
        n_verts_read_ += header.span.count;
    }
}

std::optional<TopoType> BinaryFileReader::topo_type()
{
    if (!read_header())
        return {};
    return file_header_.topo_type;
}

std::optional<uint8_t> BinaryFileReader::vertex_dim()
{
    if (!read_header())
        return {};
    return file_header_.vertex_dim;
}


ReadResult BinaryFileReader::internal_read_file(TopologyKernel &out)
{
    mesh_ = &out;
    if (file_header_.n_verts > max_handle_idx
            || file_header_.n_edges > max_handle_idx
            || file_header_.n_faces > max_handle_idx
            || file_header_.n_cells > max_handle_idx)
Martin Heistermann's avatar
Martin Heistermann committed
386
    {
Martin Heistermann's avatar
Martin Heistermann committed
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
        return ReadResult::IncompatibleMesh;
    }

    out.clear();
    out.enable_bottom_up_incidences(false);

    out.add_n_vertices(static_cast<int>(file_header_.n_verts));
    out.reserve_edges(static_cast<int>(file_header_.n_edges));
    out.reserve_faces(static_cast<int>(file_header_.n_faces));
    out.reserve_cells(static_cast<int>(file_header_.n_cells));

    state_ = ReadState::ReadingChunks;
    while (stream_.remaining_bytes() > 0) {
        read_chunk();
        if (state_ != ReadState::ReadingChunks) {
            return ReadResult::InvalidFile;
Martin Heistermann's avatar
Martin Heistermann committed
403
        }
Martin Heistermann's avatar
Martin Heistermann committed
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
    }
    if (stream_.remaining_bytes() != 0) {
        state_ = ReadState::ErrorEndNotReached;
        return ReadResult::InvalidFile;
    }
    if (!reached_eof_chunk) {
        state_ = ReadState::ErrorEndNotReached;
    }
    if (file_header_.n_verts != out.n_vertices()
            || file_header_.n_edges != out.n_edges()
            || file_header_.n_faces != out.n_faces()
            || file_header_.n_cells != out.n_cells())
    {
        state_ = ReadState::ErrorMissingData;
        return ReadResult::InvalidFile;
    }
    state_ = ReadState::Ok;
421
    if (options_.bottom_up_incidences) {
Martin Heistermann's avatar
Martin Heistermann committed
422
423
424
425
        out.enable_bottom_up_incidences(true);
    }
    return ReadResult::Ok;
}
Martin Heistermann's avatar
Martin Heistermann committed
426
427


Martin Heistermann's avatar
Martin Heistermann committed
428
429
430
431
432
void
BinaryFileReader::
read_chunk()
{
    // TODO: enforce chunk alignment!
Martin Heistermann's avatar
Martin Heistermann committed
433

Martin Heistermann's avatar
Martin Heistermann committed
434
435
436
437
438
439
440
441
442
443
444
445
446
    ChunkHeader header;
    auto decoder = stream_.make_decoder(ovmb_size<ChunkHeader>);
    read(decoder, header);
    if (header.file_length > stream_.remaining_bytes()) {
        state_ = ReadState::ErrorChunkTooBig;
        return;
    }
    assert(header.compression == 0);
    assert(header.version == 0);
    auto chunk_reader = stream_.make_decoder(header.payload_length);
    if (header.version != 0) {
        if (header.isMandatory()) {
            state_ = ReadState::ErrorUnsupportedChunkVersion;
Martin Heistermann's avatar
Martin Heistermann committed
447
            return;
Martin Heistermann's avatar
Martin Heistermann committed
448
449
        } else {
            chunk_reader.skip();
Martin Heistermann's avatar
Martin Heistermann committed
450
451
        }
    } else {
Martin Heistermann's avatar
Martin Heistermann committed
452
453
454
455
456
457
458
459
460
461
462
463
464
        using ChunkType = ChunkType;
        switch(header.type)
        {
        case ChunkType::EndOfFile:
            if (header.payload_length != 0) {
                state_ = ReadState::Error;
            }
            if (reached_eof_chunk) {
                state_ = ReadState::Error;
            }
            reached_eof_chunk = true;
            break;
        case ChunkType::PropertyDirectory:
465
            read_propdir_chunk(chunk_reader);
Martin Heistermann's avatar
Martin Heistermann committed
466
467
            break;
        case ChunkType::Property:
468
            read_prop_chunk(chunk_reader);
Martin Heistermann's avatar
Martin Heistermann committed
469
470
            break;
        case ChunkType::Vertices:
471
472
473
474
            read_vertices_chunk(chunk_reader);
            break;
        case ChunkType::Topo:
            read_topo_chunk(chunk_reader);
Martin Heistermann's avatar
Martin Heistermann committed
475
476
477
478
479
480
481
482
            break;
        default:
            if (header.isMandatory()) {
                state_ = ReadState::ErrorUnsupportedChunkType;
            } else {
                chunk_reader.skip();
            }
            break;
Martin Heistermann's avatar
Martin Heistermann committed
483
484
        }
    }
Martin Heistermann's avatar
Martin Heistermann committed
485
486
    if (state_ != ReadState::ReadingChunks)
        return;
487
488
489
490
491
492
493
    if (!chunk_reader.finished()) {
        state_ = ReadState::ErrorInvalidFile;
        error_msg_ = "Extra data at end of chunk, remaining bytes:" +
                std::to_string(chunk_reader.remaining_bytes());
        return;

    }
Martin Heistermann's avatar
Martin Heistermann committed
494
495
496
    assert(chunk_reader.finished());
    // TODO: this is ugly
    stream_.make_decoder(header.padding_bytes).padding(header.padding_bytes);
Martin Heistermann's avatar
Martin Heistermann committed
497
498
}

Martin Heistermann's avatar
Martin Heistermann committed
499

Martin Heistermann's avatar
Martin Heistermann committed
500
void
Martin Heistermann's avatar
Martin Heistermann committed
501
BinaryFileReader::
502
read_propdir_chunk(Decoder &reader)
Martin Heistermann's avatar
Martin Heistermann committed
503
504
505
506
507
508
509
510
511
512
{
    if (props_.size() != 0) {
        // we can only have one property directory!
        state_ = ReadState::Error; // TODO more specific error
        return;
    }
    while (reader.remaining_bytes() > 0)
    {
        PropertyInfo prop_info;
        read(reader, prop_info);
Martin Heistermann's avatar
Martin Heistermann committed
513
        auto prop_decoder = prop_codecs_.get_decoder(prop_info.data_type_name);
Martin Heistermann's avatar
Martin Heistermann committed
514
515
516
517
518
519
520
        if (!prop_decoder) {
            std::cerr << "Could not find decoder for type " << prop_info.data_type_name
                      << ", ignoring."
                      << std::endl;
            props_.emplace_back(); // important to have the right indices
            continue;
        }
521
522
523
524
525
526
527
528
529

        EntityType entity_type;
        try {
            entity_type = as_entity_type(prop_info.entity_type);
        } catch (std::runtime_error &e) {
            throw parse_error(e.what());
        }

        auto prop = prop_decoder->request_property(*mesh_, entity_type, prop_info.name, prop_info.serialized_default);
Martin Heistermann's avatar
Martin Heistermann committed
530
531
532
533
534
535
        props_.emplace_back(prop_info.entity_type, std::move(prop), prop_decoder);
    }
}



Martin Heistermann's avatar
Martin Heistermann committed
536

537

Martin Heistermann's avatar
Martin Heistermann committed
538
} // namespace OpenVolumeMesh::IO::detail