ResInsight/ApplicationLibCode/ModelVisualization/RivContourMapProjectionPartMgr.cpp
Magne Sjaastad d4086ee371
Allow linking of contour map views
* Place copy of contour map in correct collection
* Allow linking of contour maps
* Improve handling of contour views and 3D views
* Guard nullptr
* Guard assert seen on Linux
* Zoom all on contour map when unlinked to reset zoom
If a contour map is linked to a 3D view, the view can get distorted. Call ZoomAll to reset to sensible defaults.

* Remove default rainbow legend
2022-11-04 15:48:25 +01:00

604 lines
27 KiB
C++

#include "RivContourMapProjectionPartMgr.h"
#include "RiaColorTools.h"
#include "RiaFontCache.h"
#include "RiaWeightedMeanCalculator.h"
#include "RigCellGeometryTools.h"
#include "RivMeshLinesSourceInfo.h"
#include "RivPartPriority.h"
#include "RivScalarMapperUtils.h"
#include "RimContourMapProjection.h"
#include "RimGridView.h"
#include "RimRegularLegendConfig.h"
#include "cafCategoryMapper.h"
#include "cafEffectGenerator.h"
#include "cafFixedAtlasFont.h"
#include "cvfCamera.h"
#include "cvfDrawableText.h"
#include "cvfGeometryBuilderFaceList.h"
#include "cvfGeometryTools.h"
#include "cvfGeometryUtils.h"
#include "cvfMeshEdgeExtractor.h"
#include "cvfPart.h"
#include "cvfPrimitiveSetIndexedUInt.h"
#include "cvfRay.h"
#include "cvfScalarMapper.h"
#include "cvfqtUtils.h"
#include <cmath>
#include <QDebug>
#include <QString>
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RivContourMapProjectionPartMgr::RivContourMapProjectionPartMgr( RimContourMapProjection* contourMapProjection,
RimGridView* contourMap )
{
m_contourMapProjection = contourMapProjection;
m_parentContourMap = contourMap;
m_labelEffect = new cvf::Effect;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RivContourMapProjectionPartMgr::createProjectionGeometry()
{
m_contourMapProjection->generateGeometryIfNecessary();
m_contourLinePolygons = m_contourMapProjection->contourPolygons();
m_contourMapTriangles = m_contourMapProjection->trianglesWithVertexValues();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RivContourMapProjectionPartMgr::appendProjectionToModel( cvf::ModelBasicList* model,
const caf::DisplayCoordTransform* displayCoordTransform ) const
{
cvf::ref<cvf::Part> mapPart = createProjectionMapPart( displayCoordTransform );
if ( mapPart.notNull() )
{
model->addPart( mapPart.p() );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RivContourMapProjectionPartMgr::appendPickPointVisToModel( cvf::ModelBasicList* model,
const caf::DisplayCoordTransform* displayCoordTransform ) const
{
cvf::ref<cvf::DrawableGeo> drawable = createPickPointVisDrawable( displayCoordTransform );
if ( drawable.notNull() && drawable->boundingBox().isValid() )
{
caf::MeshEffectGenerator meshEffectGen( cvf::Color3::MAGENTA );
meshEffectGen.setLineWidth( 1.0f );
meshEffectGen.createAndConfigurePolygonOffsetRenderState( caf::PO_2 );
cvf::ref<cvf::Effect> effect = meshEffectGen.generateCachedEffect();
cvf::ref<cvf::Part> part = new cvf::Part;
part->setName( "RivContourMapProjectionPartMgr::appendPickPointVisToModel" );
part->setDrawable( drawable.p() );
part->setEffect( effect.p() );
part->setSourceInfo( new RivMeshLinesSourceInfo( m_contourMapProjection.p() ) );
model->addPart( part.p() );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
cvf::ref<cvf::Vec2fArray> RivContourMapProjectionPartMgr::createTextureCoords( const std::vector<double>& values ) const
{
cvf::ref<cvf::Vec2fArray> textureCoords = new cvf::Vec2fArray( values.size() );
#pragma omp parallel for
for ( int i = 0; i < (int)values.size(); ++i )
{
if ( values[i] != std::numeric_limits<double>::infinity() )
{
cvf::Vec2f textureCoord =
m_contourMapProjection->legendConfig()->scalarMapper()->mapToTextureCoord( values[i] );
textureCoord.y() = 0.0;
( *textureCoords )[i] = textureCoord;
}
else
{
( *textureCoords )[i] = cvf::Vec2f( 0.0, 1.0 );
}
}
return textureCoords;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RivContourMapProjectionPartMgr::appendContourLinesToModel( const cvf::Camera* camera,
cvf::ModelBasicList* model,
const caf::DisplayCoordTransform* displayCoordTransform )
{
if ( m_contourMapProjection->showContourLines() )
{
cvf::ScalarMapper* mapper = m_contourMapProjection->legendConfig()->scalarMapper();
std::vector<std::vector<cvf::BoundingBox>> labelBBoxes;
std::vector<cvf::ref<cvf::Drawable>> labelDrawables;
if ( m_contourMapProjection->showContourLabels() )
{
labelDrawables = createContourLabels( camera, displayCoordTransform, &labelBBoxes );
}
std::vector<std::vector<cvf::ref<cvf::Drawable>>> contourDrawablesForAllLevels =
createContourPolygons( displayCoordTransform, labelBBoxes );
std::vector<double> tickValues;
mapper->majorTickValues( &tickValues );
for ( size_t i = 0; i < contourDrawablesForAllLevels.size(); ++i )
{
std::vector<cvf::ref<cvf::Drawable>> contourDrawables = contourDrawablesForAllLevels[i];
cvf::Color3f backgroundColor( mapper->mapToColor( tickValues[i] ) );
cvf::Color3f lineColor = RiaColorTools::contrastColor( backgroundColor );
for ( cvf::ref<cvf::Drawable> contourDrawable : contourDrawables )
{
if ( contourDrawable.notNull() && contourDrawable->boundingBox().isValid() )
{
caf::MeshEffectGenerator meshEffectGen( lineColor );
meshEffectGen.setLineWidth( 1.0f );
meshEffectGen.createAndConfigurePolygonOffsetRenderState( caf::PO_1 );
cvf::ref<cvf::Effect> effect = meshEffectGen.generateCachedEffect();
cvf::ref<cvf::Part> part = new cvf::Part;
part->setName( "RivContourMapProjectionPartMgr::contourDrawable_mesh" );
part->setDrawable( contourDrawable.p() );
part->setEffect( effect.p() );
part->setPriority( RivPartPriority::MeshLines );
part->setSourceInfo( new RivMeshLinesSourceInfo( m_contourMapProjection.p() ) );
model->addPart( part.p() );
}
}
}
if ( m_contourMapProjection->showContourLabels() )
{
for ( auto labelDrawableRef : labelDrawables )
{
cvf::ref<cvf::Part> part = new cvf::Part;
part->setName( "RivContourMapProjectionPartMgr::labelDrawableRef" );
part->setDrawable( labelDrawableRef.p() );
part->setEffect( m_labelEffect.p() );
part->setPriority( RivPartPriority::Text );
part->setSourceInfo( new RivMeshLinesSourceInfo( m_contourMapProjection.p() ) );
model->addPart( part.p() );
}
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
cvf::ref<cvf::DrawableText> RivContourMapProjectionPartMgr::createTextLabel( const cvf::Color3f& textColor,
const cvf::Color3f& backgroundColor )
{
auto font = RiaFontCache::getFont( RiaFontCache::FontSize::FONT_SIZE_10 );
cvf::ref<cvf::DrawableText> labelDrawable = new cvf::DrawableText();
labelDrawable->setFont( font.p() );
labelDrawable->setCheckPosVisible( false );
labelDrawable->setUseDepthBuffer( true );
labelDrawable->setDrawBorder( false );
labelDrawable->setDrawBackground( false );
labelDrawable->setBackgroundColor( backgroundColor );
labelDrawable->setVerticalAlignment( cvf::TextDrawer::CENTER );
labelDrawable->setTextColor( textColor );
labelDrawable->setBorderColor( textColor );
return labelDrawable;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
cvf::ref<cvf::Part>
RivContourMapProjectionPartMgr::createProjectionMapPart( const caf::DisplayCoordTransform* displayCoordTransform ) const
{
const std::vector<cvf::Vec4d>& vertices = m_contourMapTriangles;
if ( vertices.size() < 3u )
{
return cvf::ref<cvf::Part>();
}
cvf::ref<cvf::Vec3fArray> vertexArray = new cvf::Vec3fArray( vertices.size() );
cvf::ref<cvf::UIntArray> faceList = new cvf::UIntArray( vertices.size() );
std::vector<double> values( vertices.size() );
for ( uint i = 0; i < vertices.size(); ++i )
{
cvf::Vec3d globalVertex = cvf::Vec3d( vertices[i].x(), vertices[i].y(), vertices[i].z() ) +
m_contourMapProjection->origin3d();
cvf::Vec3f displayVertexPos( displayCoordTransform->transformToDisplayCoord( globalVertex ) );
( *vertexArray )[i] = displayVertexPos;
( *faceList )[i] = i;
values[i] = vertices[i].w();
}
cvf::ref<cvf::PrimitiveSetIndexedUInt> indexUInt =
new cvf::PrimitiveSetIndexedUInt( cvf::PrimitiveType::PT_TRIANGLES, faceList.p() );
cvf::ref<cvf::DrawableGeo> geo = new cvf::DrawableGeo;
geo->addPrimitiveSet( indexUInt.p() );
geo->setVertexArray( vertexArray.p() );
cvf::ref<cvf::Part> part = new cvf::Part;
part->setName( "RivContourMapProjectionPartMgr::createProjectionMapPart" );
part->setDrawable( geo.p() );
cvf::ScalarMapper* mapper = m_contourMapProjection->legendConfig()->scalarMapper();
cvf::ref<cvf::Vec2fArray> textureCoords = createTextureCoords( values );
RivScalarMapperUtils::applyTextureResultsToPart( part.p(),
textureCoords.p(),
mapper,
1.0f,
caf::FC_NONE,
true,
m_parentContourMap->backgroundColor() );
part->setSourceInfo( new RivObjectSourceInfo( m_contourMapProjection.p() ) );
part->setPriority( RivPartPriority::BaseLevel );
return part;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<std::vector<cvf::ref<cvf::Drawable>>>
RivContourMapProjectionPartMgr::createContourPolygons( const caf::DisplayCoordTransform* displayCoordTransform,
const std::vector<std::vector<cvf::BoundingBox>>& labelBBoxes ) const
{
const cvf::ScalarMapper* mapper = m_contourMapProjection->legendConfig()->scalarMapper();
std::vector<double> tickValues;
mapper->majorTickValues( &tickValues );
std::vector<std::vector<cvf::ref<cvf::Drawable>>> contourDrawablesForAllLevels;
contourDrawablesForAllLevels.resize( tickValues.size() );
for ( size_t i = 1; i < m_contourLinePolygons.size(); ++i )
{
std::vector<cvf::ref<cvf::Drawable>> contourDrawables;
for ( size_t j = 0; j < m_contourLinePolygons[i].size(); ++j )
{
if ( m_contourLinePolygons[i][j].vertices.empty() ) continue;
// cvf::String::number does not allow precision on 'g' formats, so use Qt.
QString qLabelText = QString::number( m_contourLinePolygons[i][j].value, 'g', 2 );
cvf::String labelText = cvfqt::Utils::toString( qLabelText );
size_t nVertices = m_contourLinePolygons[i][j].vertices.size();
std::vector<cvf::Vec3f> displayLines;
displayLines.reserve( nVertices * 2 );
for ( size_t v = 0; v < nVertices; ++v )
{
cvf::Vec3d globalVertex1 = m_contourLinePolygons[i][j].vertices[v] + m_contourMapProjection->origin3d();
cvf::Vec3d displayVertex1 = displayCoordTransform->transformToDisplayCoord( globalVertex1 );
cvf::Vec3d globalVertex2;
if ( v < nVertices - 1 )
globalVertex2 = m_contourLinePolygons[i][j].vertices[v + 1] + m_contourMapProjection->origin3d();
else
globalVertex2 = m_contourLinePolygons[i][j].vertices[0] + m_contourMapProjection->origin3d();
cvf::Vec3d displayVertex2 = displayCoordTransform->transformToDisplayCoord( globalVertex2 );
cvf::BoundingBox lineBBox;
lineBBox.add( displayVertex1 );
lineBBox.add( displayVertex2 );
bool addOriginalSegment = true;
if ( labelBBoxes.size() > 0 )
{
for ( const cvf::BoundingBox& existingBBox : labelBBoxes[i] )
{
if ( lineBBox.intersects( existingBBox ) )
{
if ( existingBBox.contains( displayVertex1 ) && existingBBox.contains( displayVertex2 ) )
{
addOriginalSegment = false;
}
else
{
cvf::Vec3d dir = displayVertex2 - displayVertex1;
cvf::Ray ray;
ray.setOrigin( displayVertex1 );
ray.setDirection( dir.getNormalized() );
ray.setMaximumDistance( dir.length() );
if ( !existingBBox.contains( displayVertex1 ) )
{
cvf::Vec3d intersection;
bool hit = ray.boxIntersect( existingBBox, &intersection );
if ( hit )
{
displayLines.push_back( cvf::Vec3f( displayVertex1 ) );
displayLines.push_back( cvf::Vec3f( intersection ) );
addOriginalSegment = false;
}
}
if ( !existingBBox.contains( displayVertex2 ) )
{
ray.setOrigin( displayVertex2 );
ray.setDirection( -ray.direction() );
cvf::Vec3d intersection;
bool hit = ray.boxIntersect( existingBBox, &intersection );
if ( hit )
{
displayLines.push_back( cvf::Vec3f( intersection ) );
displayLines.push_back( cvf::Vec3f( displayVertex2 ) );
addOriginalSegment = false;
}
}
}
}
}
}
if ( addOriginalSegment )
{
displayLines.push_back( cvf::Vec3f( displayVertex1 ) );
displayLines.push_back( cvf::Vec3f( displayVertex2 ) );
}
}
cvf::ref<cvf::Vec3fArray> vertexArray = new cvf::Vec3fArray( displayLines );
std::vector<cvf::uint> indices;
indices.reserve( vertexArray->size() );
for ( cvf::uint k = 0; k < vertexArray->size(); ++k )
{
indices.push_back( k );
}
cvf::ref<cvf::PrimitiveSetIndexedUInt> indexedUInt =
new cvf::PrimitiveSetIndexedUInt( cvf::PrimitiveType::PT_LINES );
cvf::ref<cvf::UIntArray> indexArray = new cvf::UIntArray( indices );
indexedUInt->setIndices( indexArray.p() );
cvf::ref<cvf::DrawableGeo> geo = new cvf::DrawableGeo;
geo->addPrimitiveSet( indexedUInt.p() );
geo->setVertexArray( vertexArray.p() );
contourDrawables.push_back( geo );
}
if ( !contourDrawables.empty() )
{
contourDrawablesForAllLevels[i] = contourDrawables;
}
}
return contourDrawablesForAllLevels;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<cvf::ref<cvf::Drawable>>
RivContourMapProjectionPartMgr::createContourLabels( const cvf::Camera* camera,
const caf::DisplayCoordTransform* displayCoordTransform,
std::vector<std::vector<cvf::BoundingBox>>* labelBBoxes ) const
{
CVF_ASSERT( camera && displayCoordTransform && labelBBoxes );
std::vector<cvf::ref<cvf::Drawable>> labelDrawables;
labelBBoxes->clear();
labelBBoxes->resize( m_contourLinePolygons.size() );
const cvf::ScalarMapper* mapper = m_contourMapProjection->legendConfig()->scalarMapper();
if ( dynamic_cast<const caf::CategoryMapper*>( mapper ) != nullptr ) return labelDrawables;
std::vector<double> tickValues;
mapper->majorTickValues( &tickValues );
const RimContourMapProjection::ContourPolygons* previousLevel = nullptr;
for ( int64_t i = (int64_t)m_contourLinePolygons.size() - 1; i > 0; --i )
{
cvf::Color3f backgroundColor( mapper->mapToColor( tickValues[i] ) );
cvf::Color3f textColor = RiaColorTools::contrastColor( backgroundColor );
cvf::ref<cvf::DrawableText> label = createTextLabel( textColor, backgroundColor );
for ( size_t j = 0; j < m_contourLinePolygons[i].size(); ++j )
{
if ( m_contourLinePolygons[i][j].vertices.empty() ) continue;
QString qLabelText = m_contourMapProjection->legendConfig()->valueToText( m_contourLinePolygons[i][j].value );
cvf::String labelText = cvfqt::Utils::toString( qLabelText );
size_t nVertices = m_contourLinePolygons[i][j].vertices.size();
size_t nLabels = nVertices;
double distanceSinceLastLabel = std::numeric_limits<double>::infinity();
for ( size_t l = 0; l < nLabels; ++l )
{
size_t nVertex = ( nVertices * l ) / nLabels;
size_t nextVertex = ( nVertex + 1 ) % nVertices;
const cvf::Vec3d& localVertex1 = m_contourLinePolygons[i][j].vertices[nVertex];
const cvf::Vec3d& localVertex2 = m_contourLinePolygons[i][j].vertices[nextVertex];
cvf::Vec3d lineCenter = ( localVertex1 + localVertex2 ) * 0.5;
if ( previousLevel && lineOverlapsWithPreviousContourLevel( lineCenter, previousLevel ) )
{
continue;
}
cvf::Vec3d globalVertex1 = localVertex1 + m_contourMapProjection->origin3d();
cvf::Vec3d globalVertex2 = localVertex2 + m_contourMapProjection->origin3d();
cvf::Vec3d globalVertex = 0.5 * ( globalVertex1 + globalVertex2 );
cvf::Vec3d segment = globalVertex2 - globalVertex1;
cvf::Vec3d displayVertex = displayCoordTransform->transformToDisplayCoord( globalVertex );
cvf::Vec3d windowVertex;
camera->project( displayVertex, &windowVertex );
CVF_ASSERT( !windowVertex.isUndefined() );
displayVertex.z() += 10.0f;
cvf::BoundingBox windowBBox =
label->textBoundingBox( labelText, cvf::Vec3f::ZERO, cvf::Vec3f( segment.getNormalized() ) );
cvf::Vec3d displayBBoxMin, displayBBoxMax;
camera->unproject( windowBBox.min() + windowVertex, &displayBBoxMin );
camera->unproject( windowBBox.max() + windowVertex, &displayBBoxMax );
CVF_ASSERT( !displayBBoxMin.isUndefined() );
CVF_ASSERT( !displayBBoxMax.isUndefined() );
cvf::BoundingBox displayBBox( displayBBoxMin - cvf::Vec3d::Z_AXIS * 20.0,
displayBBoxMax + cvf::Vec3d::Z_AXIS * 20.0 );
cvf::Vec3d currentExtent = displayBBoxMax - displayBBoxMin;
bool overlaps = false;
if ( distanceSinceLastLabel < currentExtent.length() * 10.0 )
{
overlaps = true;
}
if ( !overlaps )
{
for ( auto boxVector : *labelBBoxes )
{
for ( const cvf::BoundingBox& existingBBox : boxVector )
{
// Assert on invalid bounding box seen on Linux
if ( !displayBBox.isValid() || !existingBBox.isValid() ) continue;
double dist = ( displayBBox.center() - existingBBox.center() ).length();
if ( dist < segment.length() || existingBBox.intersects( displayBBox ) )
{
overlaps = true;
break;
}
}
}
}
if ( !overlaps )
{
cvf::Vec3f displayVertexV( displayVertex );
CVF_ASSERT( !displayVertex.isUndefined() );
label->addText( labelText, displayVertexV, cvf::Vec3f( segment.getNormalized() ) );
labelBBoxes->at( i ).push_back( displayBBox );
distanceSinceLastLabel = 0.0;
}
else
{
distanceSinceLastLabel += segment.length();
}
}
}
if ( label->numberOfTexts() != 0u )
{
labelDrawables.push_back( label );
}
previousLevel = &m_contourLinePolygons[i];
}
return labelDrawables;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
cvf::ref<cvf::DrawableGeo>
RivContourMapProjectionPartMgr::createPickPointVisDrawable( const caf::DisplayCoordTransform* displayCoordTransform ) const
{
std::vector<cvf::Vec3d> pickPointPolygon = m_contourMapProjection->generatePickPointPolygon();
if ( pickPointPolygon.empty() )
{
return nullptr;
}
cvf::ref<cvf::Vec3fArray> vertexArray = new cvf::Vec3fArray( pickPointPolygon.size() );
for ( size_t i = 0; i < pickPointPolygon.size(); ++i )
{
cvf::Vec3d globalPoint = pickPointPolygon[i] + m_contourMapProjection->origin3d();
cvf::Vec3f displayPoint( displayCoordTransform->transformToDisplayCoord( globalPoint ) );
( *vertexArray )[i] = displayPoint;
}
cvf::ref<cvf::DrawableGeo> geo = nullptr;
if ( vertexArray->size() > 0u )
{
std::vector<cvf::uint> indices;
indices.reserve( vertexArray->size() );
for ( cvf::uint j = 0; j < vertexArray->size(); ++j )
{
indices.push_back( j );
}
cvf::ref<cvf::PrimitiveSetIndexedUInt> indexedUInt =
new cvf::PrimitiveSetIndexedUInt( cvf::PrimitiveType::PT_LINES );
cvf::ref<cvf::UIntArray> indexArray = new cvf::UIntArray( indices );
indexedUInt->setIndices( indexArray.p() );
geo = new cvf::DrawableGeo;
geo->addPrimitiveSet( indexedUInt.p() );
geo->setVertexArray( vertexArray.p() );
}
return geo;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RivContourMapProjectionPartMgr::lineOverlapsWithPreviousContourLevel(
const cvf::Vec3d& lineCenter,
const RimContourMapProjection::ContourPolygons* previousLevel ) const
{
const int64_t jump = 50;
CVF_ASSERT( previousLevel );
double tolerance = 1.0e-2 * m_contourMapProjection->sampleSpacing();
for ( const RimContourMapProjection::ContourPolygon& edgePolygon : *previousLevel )
{
std::pair<int64_t, double> closestIndex( 0, std::numeric_limits<double>::infinity() );
for ( int64_t i = 0; i < (int64_t)edgePolygon.vertices.size(); i += jump )
{
const cvf::Vec3d& edgeVertex1 = edgePolygon.vertices[i];
const cvf::Vec3d& edgeVertex2 = edgePolygon.vertices[( i + 1 ) % edgePolygon.vertices.size()];
double dist1 = cvf::GeometryTools::linePointSquareDist( edgeVertex1, edgeVertex2, lineCenter );
if ( dist1 < tolerance )
{
return true;
}
if ( dist1 < closestIndex.second )
{
closestIndex = std::make_pair( i, dist1 );
}
}
for ( int64_t i = std::max( (int64_t)1, closestIndex.first - jump + 1 );
i < std::min( (int64_t)edgePolygon.vertices.size(), closestIndex.first + jump );
++i )
{
const cvf::Vec3d& edgeVertex1 = edgePolygon.vertices[i];
const cvf::Vec3d& edgeVertex2 = edgePolygon.vertices[( i + 1 ) % edgePolygon.vertices.size()];
double dist1 = cvf::GeometryTools::linePointSquareDist( edgeVertex1, edgeVertex2, lineCenter );
if ( dist1 < tolerance )
{
return true;
}
}
}
return false;
}