2018-04-06 16:11:50 +02:00
|
|
|
/*
|
|
|
|
MDAL - Mesh Data Abstraction Library (MIT License)
|
2018-05-16 11:20:25 +02:00
|
|
|
Copyright (C) 2018 Lutra Consulting Ltd.
|
2018-04-06 16:11:50 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include <stddef.h>
|
|
|
|
#include <iosfwd>
|
|
|
|
#include <iostream>
|
|
|
|
#include <fstream>
|
|
|
|
#include <sstream>
|
|
|
|
#include <string>
|
|
|
|
#include <vector>
|
|
|
|
#include <map>
|
|
|
|
#include <cassert>
|
2019-03-18 14:05:41 +01:00
|
|
|
#include <limits>
|
2019-04-17 11:22:45 +02:00
|
|
|
#include <algorithm>
|
2018-04-06 16:11:50 +02:00
|
|
|
|
|
|
|
#include "mdal_2dm.hpp"
|
|
|
|
#include "mdal.h"
|
|
|
|
#include "mdal_utils.hpp"
|
|
|
|
|
2019-01-04 18:18:34 +01:00
|
|
|
#define DRIVER_NAME "2DM"
|
|
|
|
|
2018-12-04 17:28:05 +01:00
|
|
|
MDAL::Mesh2dm::Mesh2dm( size_t verticesCount,
|
|
|
|
size_t facesCount,
|
|
|
|
size_t faceVerticesMaximumCount,
|
|
|
|
MDAL::BBox extent,
|
|
|
|
const std::string &uri,
|
|
|
|
const std::map<size_t, size_t> vertexIDtoIndex )
|
2019-01-04 18:18:34 +01:00
|
|
|
: MemoryMesh( DRIVER_NAME,
|
|
|
|
verticesCount,
|
|
|
|
facesCount,
|
|
|
|
faceVerticesMaximumCount,
|
|
|
|
extent,
|
|
|
|
uri )
|
2018-12-04 17:28:05 +01:00
|
|
|
, mVertexIDtoIndex( vertexIDtoIndex )
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
MDAL::Mesh2dm::~Mesh2dm() = default;
|
|
|
|
|
|
|
|
bool _parse_vertex_id_gaps( std::map<size_t, size_t> &vertexIDtoIndex, size_t vertexIndex, size_t vertexID, MDAL_Status *status )
|
|
|
|
{
|
|
|
|
if ( vertexIndex == vertexID )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
std::map<size_t, size_t>::iterator search = vertexIDtoIndex.find( vertexID );
|
|
|
|
if ( search != vertexIDtoIndex.end() )
|
|
|
|
{
|
|
|
|
if ( status ) *status = MDAL_Status::Warn_ElementNotUnique;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
vertexIDtoIndex[vertexID] = vertexIndex;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t MDAL::Mesh2dm::vertexIndex( size_t vertexID ) const
|
|
|
|
{
|
|
|
|
auto ni2i = mVertexIDtoIndex.find( vertexID );
|
|
|
|
if ( ni2i != mVertexIDtoIndex.end() )
|
|
|
|
{
|
|
|
|
return ni2i->second; // convert from ID to index
|
|
|
|
}
|
|
|
|
return vertexID;
|
|
|
|
}
|
|
|
|
|
2019-04-17 11:22:45 +02:00
|
|
|
size_t MDAL::Mesh2dm::maximumVertexId() const
|
|
|
|
{
|
|
|
|
size_t maxIndex = verticesCount() - 1;
|
|
|
|
if ( mVertexIDtoIndex.empty() )
|
|
|
|
return maxIndex;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// std::map is sorted!
|
|
|
|
size_t maxID = mVertexIDtoIndex.rbegin()->first;
|
|
|
|
return std::max( maxIndex, maxID );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-14 14:59:53 +01:00
|
|
|
MDAL::Driver2dm::Driver2dm():
|
2019-01-04 18:18:34 +01:00
|
|
|
Driver( DRIVER_NAME,
|
2018-12-14 14:59:53 +01:00
|
|
|
"2DM Mesh File",
|
|
|
|
"*.2dm",
|
2019-10-14 09:19:14 +02:00
|
|
|
Capability::ReadMesh | Capability::SaveMesh
|
2018-12-14 14:59:53 +01:00
|
|
|
)
|
2018-04-06 16:11:50 +02:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2018-12-14 14:59:53 +01:00
|
|
|
MDAL::Driver2dm *MDAL::Driver2dm::create()
|
2018-04-06 16:11:50 +02:00
|
|
|
{
|
2018-12-14 14:59:53 +01:00
|
|
|
return new Driver2dm();
|
|
|
|
}
|
|
|
|
|
|
|
|
MDAL::Driver2dm::~Driver2dm() = default;
|
|
|
|
|
2019-11-29 15:15:01 +01:00
|
|
|
bool MDAL::Driver2dm::canReadMesh( const std::string &uri )
|
2018-12-14 14:59:53 +01:00
|
|
|
{
|
|
|
|
std::ifstream in( uri, std::ifstream::in );
|
|
|
|
std::string line;
|
|
|
|
if ( !std::getline( in, line ) || !startsWith( line, "MESH2D" ) )
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::unique_ptr<MDAL::Mesh> MDAL::Driver2dm::load( const std::string &meshFile, MDAL_Status *status )
|
|
|
|
{
|
|
|
|
mMeshFile = meshFile;
|
|
|
|
|
2018-04-19 16:42:01 +02:00
|
|
|
if ( status ) *status = MDAL_Status::None;
|
2018-04-06 16:11:50 +02:00
|
|
|
|
|
|
|
std::ifstream in( mMeshFile, std::ifstream::in );
|
|
|
|
std::string line;
|
|
|
|
if ( !std::getline( in, line ) || !startsWith( line, "MESH2D" ) )
|
|
|
|
{
|
2018-04-19 16:42:01 +02:00
|
|
|
if ( status ) *status = MDAL_Status::Err_UnknownFormat;
|
2018-05-16 11:20:25 +02:00
|
|
|
return nullptr;
|
2018-04-06 16:11:50 +02:00
|
|
|
}
|
|
|
|
|
2018-05-16 11:20:25 +02:00
|
|
|
size_t faceCount = 0;
|
|
|
|
size_t vertexCount = 0;
|
2018-04-06 16:11:50 +02:00
|
|
|
|
|
|
|
// Find out how many nodes and elements are contained in the .2dm mesh file
|
|
|
|
while ( std::getline( in, line ) )
|
|
|
|
{
|
|
|
|
if ( startsWith( line, "E4Q" ) ||
|
|
|
|
startsWith( line, "E3T" ) )
|
|
|
|
{
|
2018-05-16 11:20:25 +02:00
|
|
|
faceCount++;
|
2018-04-06 16:11:50 +02:00
|
|
|
}
|
|
|
|
else if ( startsWith( line, "ND" ) )
|
|
|
|
{
|
2018-05-16 11:20:25 +02:00
|
|
|
vertexCount++;
|
2018-04-06 16:11:50 +02:00
|
|
|
}
|
|
|
|
else if ( startsWith( line, "E2L" ) ||
|
|
|
|
startsWith( line, "E3L" ) ||
|
|
|
|
startsWith( line, "E6T" ) ||
|
|
|
|
startsWith( line, "E8Q" ) ||
|
|
|
|
startsWith( line, "E9Q" ) )
|
|
|
|
{
|
2018-04-19 16:42:01 +02:00
|
|
|
if ( status ) *status = MDAL_Status::Warn_UnsupportedElement;
|
2018-05-16 11:20:25 +02:00
|
|
|
faceCount += 1; // We still count them as elements
|
2018-04-06 16:11:50 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Allocate memory
|
2018-05-16 11:20:25 +02:00
|
|
|
std::vector<Vertex> vertices( vertexCount );
|
|
|
|
std::vector<Face> faces( faceCount );
|
2018-04-06 16:11:50 +02:00
|
|
|
|
2019-03-18 14:05:41 +01:00
|
|
|
// Basement 3.x supports definition of elevation for cell centers
|
|
|
|
std::vector<double> elementCenteredElevation;
|
|
|
|
|
2018-04-06 16:11:50 +02:00
|
|
|
in.clear();
|
|
|
|
in.seekg( 0, std::ios::beg );
|
|
|
|
|
|
|
|
std::vector<std::string> chunks;
|
|
|
|
|
2018-05-16 11:20:25 +02:00
|
|
|
size_t faceIndex = 0;
|
|
|
|
size_t vertexIndex = 0;
|
|
|
|
std::map<size_t, size_t> vertexIDtoIndex;
|
2019-04-17 11:22:45 +02:00
|
|
|
size_t lastVertexID = 0;
|
2018-04-06 16:11:50 +02:00
|
|
|
|
|
|
|
while ( std::getline( in, line ) )
|
|
|
|
{
|
2019-03-18 14:05:41 +01:00
|
|
|
if ( startsWith( line, "E4Q" ) ||
|
|
|
|
startsWith( line, "E3T" )
|
|
|
|
)
|
2018-04-06 16:11:50 +02:00
|
|
|
{
|
2019-01-24 09:21:57 +01:00
|
|
|
chunks = split( line, ' ' );
|
2018-05-16 11:20:25 +02:00
|
|
|
assert( faceIndex < faceCount );
|
2018-04-06 16:11:50 +02:00
|
|
|
|
2019-03-18 14:05:41 +01:00
|
|
|
const size_t faceVertexCount = MDAL::toSizeT( line[1] );
|
|
|
|
assert( ( faceVertexCount == 3 ) || ( faceVertexCount == 4 ) );
|
|
|
|
|
2018-05-16 11:20:25 +02:00
|
|
|
Face &face = faces[faceIndex];
|
2019-03-18 14:05:41 +01:00
|
|
|
face.resize( faceVertexCount );
|
|
|
|
|
|
|
|
// chunks format here
|
|
|
|
// E** id vertex_id1, vertex_id2, ... material_id (elevation - optional)
|
|
|
|
// vertex ids are numbered from 1
|
2018-04-06 16:11:50 +02:00
|
|
|
// Right now we just store node IDs here - we will convert them to node indices afterwards
|
2019-03-18 14:05:41 +01:00
|
|
|
assert( chunks.size() > faceVertexCount + 1 );
|
2018-04-06 16:11:50 +02:00
|
|
|
|
2019-03-18 14:05:41 +01:00
|
|
|
for ( size_t i = 0; i < faceVertexCount; ++i )
|
|
|
|
face[i] = MDAL::toSizeT( chunks[i + 2] ) - 1; // 2dm is numbered from 1
|
2018-04-06 16:11:50 +02:00
|
|
|
|
2019-03-18 14:05:41 +01:00
|
|
|
// OK, now find out if there is optional cell elevation (BASEMENT 3.x)
|
|
|
|
if ( chunks.size() == faceVertexCount + 4 )
|
2018-04-06 16:11:50 +02:00
|
|
|
{
|
2019-03-18 14:05:41 +01:00
|
|
|
|
|
|
|
// initialize dataset if it is still empty
|
|
|
|
if ( elementCenteredElevation.empty() )
|
|
|
|
{
|
|
|
|
elementCenteredElevation = std::vector<double>( faceCount, std::numeric_limits<double>::quiet_NaN() );
|
|
|
|
}
|
|
|
|
|
|
|
|
// add Bed Elevation (Face) value
|
|
|
|
elementCenteredElevation[faceIndex] = MDAL::toDouble( chunks[ faceVertexCount + 3 ] );
|
2018-04-06 16:11:50 +02:00
|
|
|
}
|
|
|
|
|
2018-05-16 11:20:25 +02:00
|
|
|
faceIndex++;
|
2018-04-06 16:11:50 +02:00
|
|
|
}
|
|
|
|
else if ( startsWith( line, "E2L" ) ||
|
|
|
|
startsWith( line, "E3L" ) ||
|
|
|
|
startsWith( line, "E6T" ) ||
|
|
|
|
startsWith( line, "E8Q" ) ||
|
|
|
|
startsWith( line, "E9Q" ) )
|
|
|
|
{
|
|
|
|
// We do not yet support these elements
|
2019-01-24 09:21:57 +01:00
|
|
|
chunks = split( line, ' ' );
|
2018-05-16 11:20:25 +02:00
|
|
|
assert( faceIndex < faceCount );
|
2018-04-06 16:11:50 +02:00
|
|
|
|
2018-12-04 17:28:05 +01:00
|
|
|
//size_t elemID = toSizeT( chunks[1] );
|
2018-04-06 16:11:50 +02:00
|
|
|
assert( false ); //TODO mark element as unusable
|
|
|
|
|
2018-05-16 11:20:25 +02:00
|
|
|
faceIndex++;
|
2018-04-06 16:11:50 +02:00
|
|
|
}
|
|
|
|
else if ( startsWith( line, "ND" ) )
|
|
|
|
{
|
2019-01-24 09:21:57 +01:00
|
|
|
chunks = split( line, ' ' );
|
2019-04-17 11:22:45 +02:00
|
|
|
size_t nodeID = toSizeT( chunks[1] );
|
|
|
|
|
|
|
|
if ( nodeID != 0 )
|
|
|
|
{
|
|
|
|
// specification of 2DM states that ID should be positive integer numbered from 1
|
|
|
|
// but it seems some formats do not respect that
|
|
|
|
if ( ( lastVertexID != 0 ) && ( nodeID <= lastVertexID ) )
|
|
|
|
{
|
|
|
|
// the algorithm requires that the file has NDs orderer by index
|
|
|
|
if ( status ) *status = MDAL_Status::Err_InvalidData;
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
lastVertexID = nodeID;
|
|
|
|
}
|
|
|
|
nodeID -= 1; // 2dm is numbered from 1
|
|
|
|
|
2018-12-04 17:28:05 +01:00
|
|
|
_parse_vertex_id_gaps( vertexIDtoIndex, vertexIndex, nodeID, status );
|
2018-05-16 11:20:25 +02:00
|
|
|
assert( vertexIndex < vertexCount );
|
|
|
|
Vertex &vertex = vertices[vertexIndex];
|
2018-04-06 16:11:50 +02:00
|
|
|
vertex.x = toDouble( chunks[2] );
|
|
|
|
vertex.y = toDouble( chunks[3] );
|
2018-08-29 12:10:03 +02:00
|
|
|
vertex.z = toDouble( chunks[4] );
|
2018-05-16 11:20:25 +02:00
|
|
|
vertexIndex++;
|
2018-04-06 16:11:50 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for ( std::vector<Face>::iterator it = faces.begin(); it != faces.end(); ++it )
|
|
|
|
{
|
|
|
|
Face &face = *it;
|
|
|
|
for ( Face::size_type nd = 0; nd < face.size(); ++nd )
|
|
|
|
{
|
|
|
|
size_t nodeID = face[nd];
|
|
|
|
|
2018-05-16 11:20:25 +02:00
|
|
|
std::map<size_t, size_t>::iterator ni2i = vertexIDtoIndex.find( nodeID );
|
|
|
|
if ( ni2i != vertexIDtoIndex.end() )
|
2018-04-06 16:11:50 +02:00
|
|
|
{
|
|
|
|
face[nd] = ni2i->second; // convert from ID to index
|
|
|
|
}
|
2018-12-04 17:28:05 +01:00
|
|
|
else if ( vertices.size() < nodeID )
|
2018-04-06 16:11:50 +02:00
|
|
|
{
|
2018-04-19 16:42:01 +02:00
|
|
|
if ( status ) *status = MDAL_Status::Warn_ElementWithInvalidNode;
|
2018-04-06 16:11:50 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
//TODO check validity of the face
|
|
|
|
//check that we have distinct nodes
|
|
|
|
}
|
|
|
|
|
2018-12-06 09:27:01 +01:00
|
|
|
std::unique_ptr< Mesh2dm > mesh(
|
2018-12-04 17:28:05 +01:00
|
|
|
new Mesh2dm(
|
|
|
|
vertices.size(),
|
|
|
|
faces.size(),
|
2019-10-14 09:19:14 +02:00
|
|
|
MAX_VERTICES_PER_FACE_2DM,
|
2018-12-04 17:28:05 +01:00
|
|
|
computeExtent( vertices ),
|
|
|
|
mMeshFile,
|
|
|
|
vertexIDtoIndex
|
|
|
|
)
|
|
|
|
);
|
2018-04-06 16:11:50 +02:00
|
|
|
mesh->faces = faces;
|
|
|
|
mesh->vertices = vertices;
|
2019-03-18 14:05:41 +01:00
|
|
|
|
|
|
|
// Add Bed Elevations
|
|
|
|
MDAL::addFaceScalarDatasetGroup( mesh.get(), elementCenteredElevation, "Bed Elevation (Face)" );
|
2018-12-06 09:27:01 +01:00
|
|
|
MDAL::addBedElevationDatasetGroup( mesh.get(), vertices );
|
2019-03-18 14:05:41 +01:00
|
|
|
|
2018-12-06 09:27:01 +01:00
|
|
|
return std::unique_ptr<Mesh>( mesh.release() );
|
2018-04-06 16:11:50 +02:00
|
|
|
}
|
2019-10-14 09:19:14 +02:00
|
|
|
|
|
|
|
void MDAL::Driver2dm::save( const std::string &uri, MDAL::Mesh *mesh, MDAL_Status *status )
|
|
|
|
{
|
|
|
|
if ( status ) *status = MDAL_Status::None;
|
|
|
|
|
|
|
|
std::ofstream file( uri, std::ofstream::out );
|
|
|
|
|
|
|
|
if ( !file.is_open() )
|
|
|
|
{
|
|
|
|
if ( status ) *status = MDAL_Status::Err_FailToWriteToDisk;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string line = "MESH2D";
|
|
|
|
file << line << std::endl;
|
|
|
|
|
|
|
|
//write vertices
|
|
|
|
std::unique_ptr<MDAL::MeshVertexIterator> vertexIterator = mesh->readVertices();
|
|
|
|
double vertex[3];
|
|
|
|
for ( size_t i = 0; i < mesh->verticesCount(); ++i )
|
|
|
|
{
|
|
|
|
vertexIterator->next( 1, vertex );
|
|
|
|
line = "ND ";
|
|
|
|
line.append( std::to_string( i + 1 ) );
|
|
|
|
for ( size_t j = 0; j < 2; ++j )
|
|
|
|
{
|
|
|
|
line.append( " " );
|
|
|
|
line.append( MDAL::coordinateToString( vertex[j] ) );
|
|
|
|
}
|
|
|
|
line.append( " " );
|
|
|
|
line.append( MDAL::doubleToString( vertex[2] ) );
|
|
|
|
|
|
|
|
file << line << std::endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
//write faces
|
|
|
|
std::unique_ptr<MDAL::MeshFaceIterator> faceIterator = mesh->readFaces();
|
|
|
|
for ( size_t i = 0; i < mesh->facesCount(); ++i )
|
|
|
|
{
|
|
|
|
int faceOffsets[1];
|
|
|
|
int vertexIndices[4]; //max 4 vertices for a face
|
|
|
|
faceIterator->next( 1, faceOffsets, 4, vertexIndices );
|
|
|
|
|
|
|
|
if ( faceOffsets[0] > 2 && faceOffsets[0] < 5 )
|
|
|
|
{
|
|
|
|
if ( faceOffsets[0] == 3 )
|
|
|
|
line = "E3T ";
|
|
|
|
if ( faceOffsets[0] == 4 )
|
|
|
|
line = "E4Q ";
|
|
|
|
|
|
|
|
line.append( std::to_string( i + 1 ) );
|
|
|
|
|
|
|
|
for ( int j = 0; j < faceOffsets[0]; ++j )
|
|
|
|
{
|
|
|
|
line.append( " " );
|
|
|
|
line.append( std::to_string( vertexIndices[j] + 1 ) );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
file << line << std::endl;
|
|
|
|
|
|
|
|
}
|
|
|
|
file.close();
|
|
|
|
}
|