Commit ffd445b0 authored by Martin Heistermann's avatar Martin Heistermann
Browse files

IO: improved .ovmb v1 with kaiten struct file format specification.

parent 9c3a676d
Pipeline #18950 failed with stage
in 11 minutes and 40 seconds
meta:
id: ovmb
title: OVMB (OpenVolumeMesh binary file format)
file-extension:
- ovmb
license: CC0-1.0
encoding: ASCII
endian: le
doc:
OVMB files describe volumetric meshes and associated properties.
doc-ref: http://openvolumemesh.org
seq:
- id: file_header
type: file_header
- id: chunks
type: chunk
repeat: eos
enums:
int_encoding:
0: none
1: u8
2: u16
4: u32
vertex_encoding:
0: none
1: float
2: double
property_entity:
0: vertex
1: edge
2: face
3: cell
4: halfedge
5: halfface
6: mesh
topo_type:
0: polyhedral
1: tetrahedral
2: hexahedral
topo_entity:
1: edge
2: face
3: cell
types:
file_header:
seq:
- id: magic
contents: ['O', 'V', 'M', 'B', 0xa, 0xd, 0xa, 0xff]
- id: file_version
type: u1
- id: header_version
type: u1
- id: vertex_dim
type: u1
- id: topo_type
type: u1
enum: topo_type
- id: reserved
contents: [0, 0, 0, 0]
- id: n_vertices
type: u8
- id: n_edges
type: u8
- id: n_faces
type: u8
- id: n_cells
type: u8
chunk:
seq:
- id: type
type: str
size: 4
- id: version
type: u1
- id: padding_bytes
type: u1
- id: compression
type: u1
- id: flags
type: u1
- id: file_length
type: u8
- id: body
size: file_length - padding_bytes
type:
switch-on: type
cases:
'"VERT"': vert_chunk
'"PROP"': prop_chunk
'"TOPO"': topo_chunk
'"DIRP"': propdir_chunk
- id: padding
size: padding_bytes
array_span:
seq:
- id: base
type: u8
- id: count
type: u4
vert_chunk:
seq:
- id: span
type: array_span
- id: vertex_encoding
type: u1
enum: vertex_encoding
- id: coordinates
repeat: expr
repeat-expr: span.count * _root.file_header.vertex_dim
type:
switch-on: vertex_encoding
cases:
'vertex_encoding::float': f4
'vertex_encoding::double': f8
topo_chunk:
seq:
- id: span
type: array_span
- id: entity
type: u1
enum: topo_entity
- id: valence
type: u1
doc: 0 for variable valence
- id: valence_encoding
type: u1
enum: int_encoding
- id: handle_encoding
type: u1
enum: int_encoding
- id: handle_offset
type: u8
- id: data
type:
switch-on: valence
cases:
0: topo_data_variable
_: topo_data_fixed
topo_data_fixed:
seq:
- id: handles
repeat: expr
repeat-expr: _parent.span.count * _parent.valence
type:
switch-on: _parent.handle_encoding
cases:
'int_encoding::u8': u1
'int_encoding::u16': u2
'int_encoding::u32': u4
topo_data_variable:
seq:
- id: valences
repeat: expr
repeat-expr: _parent.span.count
type:
switch-on: _parent.valence_encoding
cases:
'int_encoding::u8': u1
'int_encoding::u16': u2
'int_encoding::u32': u4
- id: handles
type:
switch-on: _parent.handle_encoding
cases:
'int_encoding::u8': u1
'int_encoding::u16': u2
'int_encoding::u32': u4
prop_chunk:
seq:
- id: span
type: array_span
- id: prop_idx
type: u4
propdir_chunk:
seq:
- id: entry
type: propdir_entry
repeat: eos
string4:
seq:
- id: len
type: u4
- id: data
size: len
propdir_entry:
seq:
- id: entity
type: u1
enum: property_entity
- id: name
type: string4
- id: data_type_name
type: string4
- id: serialized_default
type: string4
......@@ -10,6 +10,7 @@
#include <cassert>
#include <numeric>
#include <iostream>
#include <string>
namespace OpenVolumeMesh {
class TetrahedralMeshTopologyKernel;
......@@ -18,175 +19,172 @@ class HexahedralMeshTopologyKernel;
namespace OpenVolumeMesh::IO::detail {
template<typename HandleT, typename ReadFunc, typename AddFunc>
void BinaryFileReader::readFacesOrCells(
Decoder &reader,
TopoChunkHeader const &header,
uint8_t fixed_valence,
IntEncoding valence_enc,
uint64_t n,
ReadFunc read_handle,
AddFunc add_entity)
{
std::vector<HandleT> handles;
if (header.span.count == 0) {
state_ = ReadState::ErrorEmptyList;
return;
}
auto add_all = [&](auto get_valence)
{
for (uint64_t i = 0; i < header.span.count; ++i) {
auto val = get_valence(i);
handles.resize(val);
for (uint64_t h = 0; h < val; ++h) {
size_t idx = read_handle(reader) + header.span.base;
if (idx < n) {
handles[h] = HandleT::from_unsigned(idx);
} else {
state_ = ReadState::ErrorHandleRange;
return;
}
}
add_entity(handles);
}
};
auto esize = elem_size(header.enc);
if (fixed_valence == 0) {
auto valences = read_valences(reader, valence_enc, header.span.count);
auto max_valence = *std::max_element(valences.begin(), valences.end());
auto total_handles = std::accumulate(valences.begin(), valences.end(), 0ULL);
if (reader.remaining_bytes() != total_handles * esize) {
state_ = ReadState::Error;
return;
}
handles.reserve(max_valence);
add_all([&](size_t idx){return valences[idx];});
} else {
if (reader.remaining_bytes() != header.span.count * fixed_valence * esize) {
state_ = ReadState::Error;
return;
}
handles.reserve(fixed_valence);
add_all([val=fixed_valence](size_t){return val;});
}
}
bool BinaryFileReader::read_header()
{
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;
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;
}
}
return true;
} catch (parse_error &e) {
state_ = ReadState::ErrorInvalidFile;
error_msg_ = std::string("parse_error: ") + e.what();
return false;
}
return true;
}
bool BinaryFileReader::validate_span(uint64_t total, uint64_t read, ArraySpan const&span)
{
if (span.base != read) {
if (span.first != read) {
state_ = ReadState::Error;
error_msg_ = "Invalid span start, must resume where the last chunk of the same topo type left off";
return false;
}
if (total - read < span.count) {
state_ = ReadState::Error;
error_msg_ = "Invalid span, end exceeds total entity count";
return false;
}
return true;
}
std::vector<uint32_t> BinaryFileReader::read_valences(Decoder &reader, IntEncoding enc, size_t count)
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)
{
if (!is_valid(enc)) {
state_ = ReadState::ErrorInvalidEncoding;
return {};
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;
}
std::vector<uint32_t> valences;
valences.reserve(count);
out_vec.clear();
out_vec.reserve(count);
auto read_all = [&](auto read_one)
{
if (reader.remaining_bytes() < count * elem_size(enc)) {
state_ = ReadState::Error;
return;
}
for (size_t i = 0; i < count; ++i) {
valences.push_back(read_one(reader));
out_vec.push_back(make_t(read_one(reader)));
}
};
call_with_decoder(enc, read_all);
return valences;
return true;
}
void BinaryFileReader::readPropChunk(Decoder &reader)
void BinaryFileReader::read_topo_chunk(Decoder &reader)
{
PropChunkHeader header;
TopoChunkHeader header;
read(reader, header);
if (header.idx >= props_.size()) {
state_ = ReadState::Error; // TODO more specific error
if (header.span.count == 0) {
state_ = ReadState::ErrorEmptyList;
error_msg_ = "TOPO chunk contains no data";
return;
}
auto &prop = props_[header.idx];
if (!prop.decoder) {
reader.skip();
if (!is_valid(header.handle_encoding)) {
state_ = ReadState::ErrorInvalidEncoding;
error_msg_ = "TOPO chunk: invalid handle encoding";
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 (!is_valid(header.entity)) {
state_ = ReadState::ErrorInvalidFile;
error_msg_ = "TOPO chunk: Invalid topology entity " + std::to_string(static_cast<size_t>(header.entity));
return;
}
if (header.span.base >= n || n - header.span.base < header.span.count) {
state_ = ReadState::ErrorHandleRange;
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);
return;
}
prop.decoder->deserialize(prop.prop.get(),
reader,
static_cast<size_t>(header.span.base),
static_cast<size_t>(header.span.base + header.span.count));
}
void BinaryFileReader::readEdgesChunk(Decoder &reader)
void BinaryFileReader::read_edges(Decoder &reader, const TopoChunkHeader &header)
{
TopoChunkHeader header;
read(reader, header);
if (!is_valid(header.enc)) {
state_ = ReadState::ErrorInvalidEncoding;
if (!validate_span(file_header_.n_edges, n_edges_read_, header.span)) {
return;
}
if (!validate_span(file_header_.n_edges, n_edges_read_, header.span))
if (header.valence != 2) {
state_ = ReadState::ErrorInvalidFile;
error_msg_ = "TOPO edge chunk: valence must be 2";
return;
}
auto read_all = [&](auto read_one)
{
if (reader.remaining_bytes() != header.span.count * 2 * elem_size(header.enc)) {
std::cerr << "edge chunk size " << std::endl;
state_ = ReadState::ErrorInvalidChunkSize;
return;
}
for (size_t i = 0; i < header.span.count; ++i) {
uint64_t src = read_one(reader) + header.span.base;
uint64_t dst = read_one(reader) + header.span.base;
uint64_t src = read_one(reader) + header.span.first;
uint64_t dst = read_one(reader) + header.span.first;
if (src >= n_verts_read_ || dst >= n_verts_read_) {
state_ = ReadState::ErrorHandleRange;
} else {
......@@ -197,113 +195,134 @@ void BinaryFileReader::readEdgesChunk(Decoder &reader)
}
};
call_with_decoder(header.enc, read_all);
call_with_decoder(header.handle_encoding, read_all);
if (state_ == ReadState::ReadingChunks) {
n_edges_read_ += header.span.count;
}
}
void BinaryFileReader::readFacesChunk(Decoder &reader)
void BinaryFileReader::read_faces(Decoder &reader, const TopoChunkHeader &header, const ValenceVec &_valences)
{
TopoChunkHeader header;
read(reader, header);
uint32_t fixed_valence = reader.u8();
IntEncoding valence_enc; read(reader, valence_enc);
reader.reserved<2>();
if (!is_valid(header.enc)) {
state_ = ReadState::ErrorInvalidEncoding;
return;
}
if (!validate_span(file_header_.n_faces, n_faces_read_, header.span))
return;
if (file_header_.topo_type == TopoType::Tetrahedral && fixed_valence != 3) {
if (file_header_.topo_type == TopoType::Tetrahedral && header.valence != 3) {
state_ = ReadState::ErrorInvalidTopoType;
error_msg_ = "TOPO chunk: Faces of tetrahedral meshes must have a fixed valence of 3";
return;
}
if (file_header_.topo_type == TopoType::Hexahedral && fixed_valence != 4) {
if (file_header_.topo_type == TopoType::Hexahedral && header.valence != 4) {
state_ = ReadState::ErrorInvalidTopoType;
error_msg_ = "TOPO chunk: Faces of hexahedral meshes must have a fixed valence of 4";
return;
}
auto read_all = [&](auto read_one)
assert(header.valence != 0 || _valences.size() == header.span.count);
for (uint64_t i = 0; i < header.span.count; ++i)
{
auto add_face = [&](auto halfedges)
{
return mesh_->add_face(std::move(halfedges), options_.topology_check);
};
readFacesOrCells<HalfEdgeHandle>(
reader, header, fixed_valence, valence_enc, n_edges_read_ * 2,
read_one, add_face);
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,
[&](uint32_t x){return HEH::from_unsigned(x + header.handle_offset);});
if (!success) break;
mesh_->add_face(std::move(halfedges), options_.topology_check);
};
call_with_decoder(header.enc, read_all);
if (state_ == ReadState::ReadingChunks) {
n_faces_read_ += header.span.count;
}
}
void BinaryFileReader::readCellsChunk(Decoder &reader)
void BinaryFileReader::read_cells(Decoder &reader, const TopoChunkHeader &header, const ValenceVec &_valences)
{
TopoChunkHeader header;
read(reader, header);
uint32_t fixed_valence = reader.u8();
IntEncoding valence_enc; read(reader, valence_enc);
reader.reserved<2>();
if (!is_valid(header.enc)) {
state_ = ReadState::ErrorInvalidEncoding;
return;
}
if (!validate_span(file_header_.n_cells, n_cells_read_, header.span))
return;
if (file_header_.topo_type == TopoType::Tetrahedral && fixed_valence != 4) {
if (file_header_.topo_type == TopoType::Tetrahedral && header.valence != 4) {
state_ = ReadState::ErrorInvalidTopoType;