diff --git a/ApplicationLibCode/ModelVisualization/CMakeLists_files.cmake b/ApplicationLibCode/ModelVisualization/CMakeLists_files.cmake index 3ca6b0c6a5..ec3265d3b3 100644 --- a/ApplicationLibCode/ModelVisualization/CMakeLists_files.cmake +++ b/ApplicationLibCode/ModelVisualization/CMakeLists_files.cmake @@ -36,6 +36,7 @@ set(SOURCE_GROUP_SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/RivSimWellConnectionSourceInfo.cpp ${CMAKE_CURRENT_LIST_DIR}/Riv3dWellLogDrawSurfaceGenerator.cpp ${CMAKE_CURRENT_LIST_DIR}/RivMeshLinesSourceInfo.cpp + ${CMAKE_CURRENT_LIST_DIR}/RivContourMapElevationProvider.cpp ${CMAKE_CURRENT_LIST_DIR}/RivContourMapProjectionPartMgr.cpp ${CMAKE_CURRENT_LIST_DIR}/RivAnnotationsPartMgr.cpp ${CMAKE_CURRENT_LIST_DIR}/RivTextAnnotationPartMgr.cpp diff --git a/ApplicationLibCode/ModelVisualization/RivContourMapElevationProvider.cpp b/ApplicationLibCode/ModelVisualization/RivContourMapElevationProvider.cpp new file mode 100644 index 0000000000..c1a825975f --- /dev/null +++ b/ApplicationLibCode/ModelVisualization/RivContourMapElevationProvider.cpp @@ -0,0 +1,102 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2026- Equinor ASA +// +// ResInsight is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// See the GNU General Public License at +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#include "RivContourMapElevationProvider.h" + +#include "ContourMap/RigContourMapTopography.h" + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RivContourMapElevationProvider::~RivContourMapElevationProvider() +{ +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +double RivContourMapElevationProvider::resamplingDistance() const +{ + return 0.0; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RivContourMapFlatElevation::RivContourMapFlatElevation( double domainElevation ) + : m_domainElevation( domainElevation ) +{ +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RivContourMapFlatElevation::~RivContourMapFlatElevation() +{ +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::optional RivContourMapFlatElevation::domainElevation( const cvf::Vec2d& localPos2d ) const +{ + return m_domainElevation; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RivContourMapTopographyElevation::RivContourMapTopographyElevation( std::shared_ptr topography, double offset ) + : m_topography( topography ) + , m_offset( offset ) +{ +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RivContourMapTopographyElevation::~RivContourMapTopographyElevation() +{ +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::optional RivContourMapTopographyElevation::domainElevation( const cvf::Vec2d& localPos2d ) const +{ + if ( !m_topography ) return {}; + + auto elevation = m_topography->elevationAtLocalPos( localPos2d ); + if ( !elevation ) return {}; + + return *elevation + m_offset; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +double RivContourMapTopographyElevation::resamplingDistance() const +{ + if ( !m_topography ) return 0.0; + + // Several samples per raster cell, so a polyline picks up the shape of the geometry between the + // raster vertices instead of only at them + const double samplesPerRasterCell = 8.0; + + return m_topography->sampleSpacing() / samplesPerRasterCell; +} diff --git a/ApplicationLibCode/ModelVisualization/RivContourMapElevationProvider.h b/ApplicationLibCode/ModelVisualization/RivContourMapElevationProvider.h new file mode 100644 index 0000000000..0fad3e132b --- /dev/null +++ b/ApplicationLibCode/ModelVisualization/RivContourMapElevationProvider.h @@ -0,0 +1,90 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2026- Equinor ASA +// +// ResInsight is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// See the GNU General Public License at +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "cvfVector2.h" + +#include +#include + +class RigContourMapTopography; + +//================================================================================================== +/// +/// Supplies the domain z coordinate used when contour map geometry is placed in a scene. +/// +/// Contour map geometry is generated in a flat, contour map local coordinate space where z is +/// always zero. Consumers normally lift it to RigContourMapGrid::origin3d(), which places the map +/// at the minimum z of the expanded bounding box. In a 3d view the map must be placed elsewhere, +/// and the contour lines may be draped on the visible grid geometry. This interface is the single +/// point where that decision is made. +/// +//================================================================================================== +class RivContourMapElevationProvider +{ +public: + virtual ~RivContourMapElevationProvider(); + + // localPos2d is relative to RigContourMapGrid::origin2d(). Returns the domain z to use, or + // nothing when no elevation is defined at the given position. + virtual std::optional domainElevation( const cvf::Vec2d& localPos2d ) const = 0; + + // How closely a polyline has to be sampled for the elevation to be followed faithfully. Contour + // polygons are simplified, so a single segment can span a long distance and would otherwise cut + // straight through everything between its end points. Zero means the elevation does not vary, so + // the end points are enough. + virtual double resamplingDistance() const; +}; + +//================================================================================================== +/// +/// Places the whole contour map on a single horizontal plane. +/// +//================================================================================================== +class RivContourMapFlatElevation : public RivContourMapElevationProvider +{ +public: + explicit RivContourMapFlatElevation( double domainElevation ); + ~RivContourMapFlatElevation() override; + + std::optional domainElevation( const cvf::Vec2d& localPos2d ) const override; + +private: + double m_domainElevation; +}; + +//================================================================================================== +/// +/// Follows the top of the visible grid geometry, lifted by a constant offset. Returns nothing where +/// no visible geometry is found, which makes the consumer drop the affected geometry. +/// +//================================================================================================== +class RivContourMapTopographyElevation : public RivContourMapElevationProvider +{ +public: + RivContourMapTopographyElevation( std::shared_ptr topography, double offset ); + ~RivContourMapTopographyElevation() override; + + std::optional domainElevation( const cvf::Vec2d& localPos2d ) const override; + double resamplingDistance() const override; + +private: + std::shared_ptr m_topography; + double m_offset; +}; diff --git a/ApplicationLibCode/ModelVisualization/RivContourMapProjectionPartMgr.cpp b/ApplicationLibCode/ModelVisualization/RivContourMapProjectionPartMgr.cpp index 50e6aa0d0a..8b8351560f 100644 --- a/ApplicationLibCode/ModelVisualization/RivContourMapProjectionPartMgr.cpp +++ b/ApplicationLibCode/ModelVisualization/RivContourMapProjectionPartMgr.cpp @@ -24,6 +24,7 @@ #include "ContourMap/RigContourMapGrid.h" #include "ContourMap/RigContourPolygonsTools.h" +#include "RivContourMapElevationProvider.h" #include "RivMeshLinesSourceInfo.h" #include "RivPartPriority.h" #include "RivScalarMapperUtils.h" @@ -39,10 +40,13 @@ #include "cvfPart.h" #include "cvfPrimitiveSetIndexedUInt.h" #include "cvfRay.h" +#include "cvfRenderStatePolygonOffset.h" #include "cvfScalarMapper.h" #include "cvfViewport.h" #include "cvfqtUtils.h" +#include +#include #include #include @@ -61,14 +65,112 @@ RivContourMapProjectionPartMgr::RivContourMapProjectionPartMgr( caf::PdmObject* //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -void RivContourMapProjectionPartMgr::appendProjectionToModel( cvf::ModelBasicList* model, - const caf::DisplayCoordTransform* displayCoordTransform, - const std::vector& vertices, - const RigContourMapGrid& contourMapGrid, - const cvf::Color3f& backgroundColor, - cvf::ScalarMapper* scalarMapper ) const +cvf::Color3f RivContourLineAppearance::colorForLevel( const cvf::Color3f& levelColor ) const { - cvf::ref mapPart = createProjectionMapPart( displayCoordTransform, vertices, contourMapGrid, backgroundColor, scalarMapper ); + return color.value_or( RiaColorTools::contrastColor( levelColor ) ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::optional RivContourMapProjectionPartMgr::toDomainCoord( const cvf::Vec3d& localVertex, + const RigContourMapGrid& contourMapGrid, + const RivContourMapElevationProvider* elevationProvider ) +{ + cvf::Vec3d domainVertex = localVertex + contourMapGrid.origin3d(); + + if ( elevationProvider ) + { + auto elevation = elevationProvider->domainElevation( cvf::Vec2d( localVertex.x(), localVertex.y() ) ); + if ( !elevation ) return {}; + + domainVertex.z() = *elevation; + } + + return domainVertex; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::optional RivContourMapProjectionPartMgr::toDisplayCoord( const cvf::Vec3d& localVertex, + const RigContourMapGrid& contourMapGrid, + const caf::DisplayCoordTransform* displayCoordTransform, + const RivContourMapElevationProvider* elevationProvider ) +{ + auto domainVertex = toDomainCoord( localVertex, contourMapGrid, elevationProvider ); + if ( !domainVertex ) return {}; + + return displayCoordTransform->transformToDisplayCoord( *domainVertex ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector> + RivContourMapProjectionPartMgr::resampledDisplaySegments( const std::vector& localVertices, + const RigContourMapGrid& contourMapGrid, + const caf::DisplayCoordTransform* displayCoordTransform, + const RivContourMapElevationProvider* elevationProvider ) +{ + std::vector> segments; + + const size_t nVertices = localVertices.size(); + if ( nVertices < 2 ) return segments; + + const double resamplingDistance = elevationProvider ? elevationProvider->resamplingDistance() : 0.0; + + // Keeps a pathologically long segment from producing an unbounded number of samples + const int maxSamplesPerSegment = 200; + + segments.reserve( nVertices ); + + for ( size_t v = 0; v < nVertices; ++v ) + { + const cvf::Vec3d& localVertex1 = localVertices[v]; + const cvf::Vec3d& localVertex2 = ( v < nVertices - 1 ) ? localVertices[v + 1] : localVertices[0]; + + int sampleCount = 1; + if ( resamplingDistance > 0.0 ) + { + const double segmentLength = ( localVertex2 - localVertex1 ).length(); + + sampleCount = static_cast( std::ceil( segmentLength / resamplingDistance ) ); + sampleCount = std::clamp( sampleCount, 1, maxSamplesPerSegment ); + } + + auto previous = toDisplayCoord( localVertex1, contourMapGrid, displayCoordTransform, elevationProvider ); + + for ( int sample = 1; sample <= sampleCount; ++sample ) + { + const double fraction = static_cast( sample ) / sampleCount; + const cvf::Vec3d localPoint = localVertex1 + ( localVertex2 - localVertex1 ) * fraction; + + auto current = toDisplayCoord( localPoint, contourMapGrid, displayCoordTransform, elevationProvider ); + + // A gap is left wherever there is no elevation, which splits a draped polyline into pieces + if ( previous && current ) segments.emplace_back( *previous, *current ); + + previous = current; + } + } + + return segments; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RivContourMapProjectionPartMgr::appendProjectionToModel( cvf::ModelBasicList* model, + const caf::DisplayCoordTransform* displayCoordTransform, + const std::vector& vertices, + const RigContourMapGrid& contourMapGrid, + const cvf::Color3f& backgroundColor, + cvf::ScalarMapper* scalarMapper, + const RivContourMapElevationProvider* elevationProvider ) const +{ + cvf::ref mapPart = + createProjectionMapPart( displayCoordTransform, vertices, contourMapGrid, backgroundColor, scalarMapper, elevationProvider ); if ( mapPart.notNull() ) { model->addPart( mapPart.p() ); @@ -134,12 +236,14 @@ void RivContourMapProjectionPartMgr::appendContourLinesToModel( const cvf::Camer cvf::ModelBasicList* model, const caf::DisplayCoordTransform* displayCoordTransform, const std::vector& contourLinePolygons, - const RigContourMapGrid& contourMapGrid, - cvf::ScalarMapper* mapper, - bool showContourLines, - bool showContourLabels, - caf::NumberFormatType numberFormat, - int precision ) + const RigContourMapGrid& contourMapGrid, + cvf::ScalarMapper* mapper, + bool showContourLines, + bool showContourLabels, + caf::NumberFormatType numberFormat, + int precision, + const RivContourMapElevationProvider* elevationProvider, + const RivContourLineAppearance& lineAppearance ) { if ( showContourLines ) { @@ -148,12 +252,20 @@ void RivContourMapProjectionPartMgr::appendContourLinesToModel( const cvf::Camer if ( showContourLabels ) { - labelDrawables = - createContourLabels( camera, displayCoordTransform, &labelBBoxes, contourLinePolygons, contourMapGrid, mapper, numberFormat, precision ); + labelDrawables = createContourLabels( camera, + displayCoordTransform, + &labelBBoxes, + contourLinePolygons, + contourMapGrid, + mapper, + numberFormat, + precision, + elevationProvider, + lineAppearance ); } std::vector>> contourDrawablesForAllLevels = - createContourPolygons( displayCoordTransform, labelBBoxes, contourLinePolygons, mapper, contourMapGrid ); + createContourPolygons( displayCoordTransform, labelBBoxes, contourLinePolygons, mapper, contourMapGrid, elevationProvider ); std::vector tickValues; mapper->majorTickValues( &tickValues ); @@ -163,16 +275,19 @@ void RivContourMapProjectionPartMgr::appendContourLinesToModel( const cvf::Camer std::vector> contourDrawables = contourDrawablesForAllLevels[i]; cvf::Color3f backgroundColor( mapper->mapToColor( tickValues[i] ) ); - cvf::Color3f lineColor = RiaColorTools::contrastColor( backgroundColor ); + cvf::Color3f lineColor = lineAppearance.colorForLevel( backgroundColor ); for ( cvf::ref contourDrawable : contourDrawables ) { if ( contourDrawable.notNull() && contourDrawable->boundingBox().isValid() ) { caf::MeshEffectGenerator meshEffectGen( lineColor ); - meshEffectGen.setLineWidth( 1.0f ); - caf::MeshEffectGenerator::createAndConfigurePolygonOffsetRenderState( caf::PO_1 ); + meshEffectGen.setLineWidth( lineAppearance.lineWidth ); + // NB: polygon offset cannot be used to keep these lines clear of the map surface. + // It only applies to polygon primitives, and these are line primitives. Callers that + // draw the lines on top of other geometry have to lift them through the elevation + // provider instead. cvf::ref effect = meshEffectGen.generateCachedEffect(); cvf::ref part = new cvf::Part; @@ -206,9 +321,11 @@ void RivContourMapProjectionPartMgr::appendContourLinesToModel( const cvf::Camer //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -cvf::ref RivContourMapProjectionPartMgr::createTextLabel( const cvf::Color3f& textColor, const cvf::Color3f& backgroundColor ) +cvf::ref RivContourMapProjectionPartMgr::createTextLabel( const cvf::Color3f& textColor, + const cvf::Color3f& backgroundColor, + caf::FontTools::FontSize fontSize ) { - auto font = RiaFontCache::getFont( RiaFontCache::FontSize::FONT_SIZE_10 ); + auto font = RiaFontCache::getFont( fontSize ); cvf::ref labelDrawable = new cvf::DrawableText(); labelDrawable->setFont( font.p() ); @@ -227,26 +344,62 @@ cvf::ref RivContourMapProjectionPartMgr::createTextLabel( con //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -cvf::ref RivContourMapProjectionPartMgr::createProjectionMapPart( const caf::DisplayCoordTransform* displayCoordTransform, - const std::vector& vertices, - const RigContourMapGrid& contourMapGrid, - const cvf::Color3f& backgroundColor, - cvf::ScalarMapper* scalarMapper ) const +cvf::ref RivContourMapProjectionPartMgr::createProjectionMapPart( const caf::DisplayCoordTransform* displayCoordTransform, + const std::vector& vertices, + const RigContourMapGrid& contourMapGrid, + const cvf::Color3f& backgroundColor, + cvf::ScalarMapper* scalarMapper, + const RivContourMapElevationProvider* elevationProvider ) const { if ( vertices.size() < 3u ) { return cvf::ref(); } - cvf::ref vertexArray = new cvf::Vec3fArray( vertices.size() ); - cvf::ref faceList = new cvf::UIntArray( vertices.size() ); - std::vector values( vertices.size() ); - for ( uint i = 0; i < vertices.size(); ++i ) + + // The vertices form a triangle soup, three consecutive entries per triangle. A triangle is dropped + // when the elevation provider has no elevation for one of its vertices. + std::vector displayVertices; + std::vector values; + displayVertices.reserve( vertices.size() ); + values.reserve( vertices.size() ); + + for ( size_t i = 0; i + 2 < vertices.size(); i += 3 ) { - cvf::Vec3d globalVertex = cvf::Vec3d( vertices[i].x(), vertices[i].y(), vertices[i].z() ) + contourMapGrid.origin3d(); - cvf::Vec3f displayVertexPos( displayCoordTransform->transformToDisplayCoord( globalVertex ) ); - ( *vertexArray )[i] = displayVertexPos; - ( *faceList )[i] = i; - values[i] = vertices[i].w(); + std::array triangle; + bool allVerticesDefined = true; + + for ( size_t n = 0; n < 3; ++n ) + { + const cvf::Vec4d& vertex = vertices[i + n]; + auto displayVertex = + toDisplayCoord( cvf::Vec3d( vertex.x(), vertex.y(), vertex.z() ), contourMapGrid, displayCoordTransform, elevationProvider ); + if ( !displayVertex ) + { + allVerticesDefined = false; + break; + } + triangle[n] = *displayVertex; + } + + if ( !allVerticesDefined ) continue; + + for ( size_t n = 0; n < 3; ++n ) + { + displayVertices.push_back( cvf::Vec3f( triangle[n] ) ); + values.push_back( vertices[i + n].w() ); + } + } + + if ( displayVertices.size() < 3u ) + { + return cvf::ref(); + } + + cvf::ref vertexArray = new cvf::Vec3fArray( displayVertices ); + cvf::ref faceList = new cvf::UIntArray( displayVertices.size() ); + for ( uint i = 0; i < displayVertices.size(); ++i ) + { + ( *faceList )[i] = i; } cvf::ref indexUInt = new cvf::PrimitiveSetIndexedUInt( cvf::PrimitiveType::PT_TRIANGLES, faceList.p() ); @@ -275,7 +428,8 @@ std::vector>> const std::vector>& labelBBoxes, const std::vector& contourLinePolygons, cvf::ScalarMapper* scalarMapper, - const RigContourMapGrid& contourMapGrid ) const + const RigContourMapGrid& contourMapGrid, + const RivContourMapElevationProvider* elevationProvider ) const { std::vector tickValues; scalarMapper->majorTickValues( &tickValues ); @@ -295,23 +449,13 @@ std::vector>> QString qLabelText = QString::number( contourLinePolygons[i][j].value, 'g', 2 ); cvf::String labelText = cvfqt::Utils::toString( qLabelText ); - size_t nVertices = contourLinePolygons[i][j].vertices.size(); + const auto displaySegments = + resampledDisplaySegments( contourLinePolygons[i][j].vertices, contourMapGrid, displayCoordTransform, elevationProvider ); std::vector displayLines; - displayLines.reserve( nVertices * 2 ); - for ( size_t v = 0; v < nVertices; ++v ) + displayLines.reserve( displaySegments.size() * 2 ); + for ( const auto& [displayVertex1, displayVertex2] : displaySegments ) { - cvf::Vec3d globalVertex1 = contourLinePolygons[i][j].vertices[v] + contourMapGrid.origin3d(); - cvf::Vec3d displayVertex1 = displayCoordTransform->transformToDisplayCoord( globalVertex1 ); - - cvf::Vec3d globalVertex2; - if ( v < nVertices - 1 ) - globalVertex2 = contourLinePolygons[i][j].vertices[v + 1] + contourMapGrid.origin3d(); - else - globalVertex2 = contourLinePolygons[i][j].vertices[0] + contourMapGrid.origin3d(); - - cvf::Vec3d displayVertex2 = displayCoordTransform->transformToDisplayCoord( globalVertex2 ); - cvf::BoundingBox lineBBox; lineBBox.add( displayVertex1 ); lineBBox.add( displayVertex2 ); @@ -410,7 +554,9 @@ std::vector> const RigContourMapGrid& contourMapGrid, const cvf::ScalarMapper* scalarMapper, caf::NumberFormatType numberFormat, - int precision ) const + int precision, + const RivContourMapElevationProvider* elevationProvider, + const RivContourLineAppearance& lineAppearance ) const { CAF_ASSERT( camera && displayCoordTransform && labelBBoxes ); @@ -430,9 +576,11 @@ std::vector> const RigContourPolygonsTools::ContourPolygons* previousLevel = nullptr; for ( int64_t i = (int64_t)contourLinePolygons.size() - 1; i > 0; --i ) { - cvf::Color3f backgroundColor( scalarMapper->mapToColor( tickValues[i] ) ); - cvf::Color3f textColor = RiaColorTools::contrastColor( backgroundColor ); - cvf::ref label = createTextLabel( textColor, backgroundColor ); + cvf::Color3f backgroundColor( scalarMapper->mapToColor( tickValues[i] ) ); + + // A label belongs to its contour line, so it takes the same color + cvf::Color3f textColor = lineAppearance.colorForLevel( backgroundColor ); + cvf::ref label = createTextLabel( textColor, backgroundColor, lineAppearance.labelFontSize ); for ( size_t j = 0; j < contourLinePolygons[i].size(); ++j ) { @@ -460,8 +608,12 @@ std::vector> continue; } - cvf::Vec3d globalVertex1 = localVertex1 + contourMapGrid.origin3d(); - cvf::Vec3d globalVertex2 = localVertex2 + contourMapGrid.origin3d(); + auto globalVertex1Candidate = toDomainCoord( localVertex1, contourMapGrid, elevationProvider ); + auto globalVertex2Candidate = toDomainCoord( localVertex2, contourMapGrid, elevationProvider ); + if ( !globalVertex1Candidate || !globalVertex2Candidate ) continue; + + cvf::Vec3d globalVertex1 = *globalVertex1Candidate; + cvf::Vec3d globalVertex2 = *globalVertex2Candidate; cvf::Vec3d globalVertex = 0.5 * ( globalVertex1 + globalVertex2 ); @@ -471,7 +623,12 @@ std::vector> camera->project( displayVertex, &windowVertex ); CAF_ASSERT( !windowVertex.isUndefined() ); displayVertex.z() += 10.0f; - cvf::BoundingBox windowBBox = label->textBoundingBox( labelText, cvf::Vec3f::ZERO, cvf::Vec3f( segment.getNormalized() ) ); + + // The text direction is interpreted in screen space by the drawer + const cvf::Vec3f labelDirection = lineAppearance.alignLabelsWithCamera ? cvf::Vec3f::X_AXIS + : cvf::Vec3f( segment.getNormalized() ); + + cvf::BoundingBox windowBBox = label->textBoundingBox( labelText, cvf::Vec3f::ZERO, labelDirection ); cvf::Vec3d displayBBoxMin, displayBBoxMax; camera->unproject( windowBBox.min() + windowVertex, &displayBBoxMin ); camera->unproject( windowBBox.max() + windowVertex, &displayBBoxMax ); @@ -512,7 +669,7 @@ std::vector> { cvf::Vec3f displayVertexV( displayVertex ); CAF_ASSERT( !displayVertex.isUndefined() ); - label->addText( labelText, displayVertexV, cvf::Vec3f( segment.getNormalized() ) ); + label->addText( labelText, displayVertexV, labelDirection ); labelBBoxes->at( i ).push_back( displayBBox ); distanceSinceLastLabel = 0.0; } @@ -549,9 +706,12 @@ cvf::ref RivContourMapProjectionPartMgr::createPickPointVisDra for ( size_t i = 0; i < pickPointPolygon.size(); ++i ) { - cvf::Vec3d globalPoint = pickPointPolygon[i] + contourMapGrid.origin3d(); - cvf::Vec3f displayPoint( displayCoordTransform->transformToDisplayCoord( globalPoint ) ); - ( *vertexArray )[i] = displayPoint; + // The pick point marker is only used by the 2d contour map views, and is always placed on the + // contour map plane. Without an elevation provider a display coordinate is always produced. + auto displayPoint = toDisplayCoord( pickPointPolygon[i], contourMapGrid, displayCoordTransform, nullptr ); + CAF_ASSERT( displayPoint ); + + ( *vertexArray )[i] = cvf::Vec3f( displayPoint.value_or( cvf::Vec3d::ZERO ) ); } cvf::ref geo = nullptr; diff --git a/ApplicationLibCode/ModelVisualization/RivContourMapProjectionPartMgr.h b/ApplicationLibCode/ModelVisualization/RivContourMapProjectionPartMgr.h index e2b2dc0e07..4f6de64dda 100644 --- a/ApplicationLibCode/ModelVisualization/RivContourMapProjectionPartMgr.h +++ b/ApplicationLibCode/ModelVisualization/RivContourMapProjectionPartMgr.h @@ -21,17 +21,22 @@ #include "ContourMap/RigContourPolygonsTools.h" #include "cafDisplayCoordTransform.h" +#include "cafFontTools.h" #include "cafPdmObject.h" #include "cafPdmPointer.h" #include "cafPdmUiNumberFormat.h" +#include "cvfColor3.h" #include "cvfDrawableGeo.h" #include "cvfDrawableText.h" #include "cvfObject.h" #include "cvfVector2.h" #include "cvfVector4.h" +#include + class RigContourMapGrid; +class RivContourMapElevationProvider; namespace cvf { @@ -42,17 +47,47 @@ class ModelBasicList; class Part; } // namespace cvf +//================================================================================================== +/// +/// How the contour lines are drawn. +/// +/// The defaults reproduce the appearance used by the 2d contour map views: one hairline per contour +/// level, coloured to contrast the level colour of the map underneath it. +/// +//================================================================================================== +struct RivContourLineAppearance +{ + // When not set, every contour level gets a colour contrasting its own level colour + std::optional color; + float lineWidth = 1.0f; + + caf::FontTools::FontSize labelFontSize = caf::FontTools::FontSize::FONT_SIZE_10; + + // Labels are drawn along the contour segment they belong to. The direction is a screen space one, + // which only lines up with the segment while the camera looks straight down at the map, so in a 3d + // view the text ends up rotated arbitrarily. Set this to keep the labels upright on screen instead. + bool alignLabelsWithCamera = false; + + // The colour to draw a contour level in. Shared by the lines and their labels so the two cannot + // drift apart. levelColor is the map colour of the level, used when no explicit colour is set. + cvf::Color3f colorForLevel( const cvf::Color3f& levelColor ) const; +}; + class RivContourMapProjectionPartMgr : public cvf::Object { public: RivContourMapProjectionPartMgr( caf::PdmObject* contourMapProjection ); - void appendProjectionToModel( cvf::ModelBasicList* model, - const caf::DisplayCoordTransform* displayCoordTransform, - const std::vector& vertices, - const RigContourMapGrid& contourMapGrid, - const cvf::Color3f& backgroundColor, - cvf::ScalarMapper* scalarMapper ) const; + // elevationProvider controls where the geometry is placed vertically. When null, the geometry is + // placed on the horizontal plane at RigContourMapGrid::origin3d(), which is the behaviour used by + // the 2d contour map views. + void appendProjectionToModel( cvf::ModelBasicList* model, + const caf::DisplayCoordTransform* displayCoordTransform, + const std::vector& vertices, + const RigContourMapGrid& contourMapGrid, + const cvf::Color3f& backgroundColor, + cvf::ScalarMapper* scalarMapper, + const RivContourMapElevationProvider* elevationProvider = nullptr ) const; void appendContourLinesToModel( const cvf::Camera* camera, cvf::ModelBasicList* model, @@ -63,7 +98,9 @@ public: bool showContourLines, bool showContourLabels, caf::NumberFormatType numberFormat, - int precision ); + int precision, + const RivContourMapElevationProvider* elevationProvider = nullptr, + const RivContourLineAppearance& lineAppearance = {} ); void appendPickPointVisToModel( cvf::ModelBasicList* model, const caf::DisplayCoordTransform* displayCoordTransform, @@ -73,28 +110,53 @@ public: cvf::ref createTextureCoords( const std::vector& values, cvf::ScalarMapper* scalarMapper ) const; private: - static cvf::ref createTextLabel( const cvf::Color3f& textColor, const cvf::Color3f& backgroundColor ); - cvf::ref createProjectionMapPart( const caf::DisplayCoordTransform* displayCoordTransform, - const std::vector& vertices, - const RigContourMapGrid& contourMapGrid, - const cvf::Color3f& backgroundColor, - cvf::ScalarMapper* scalarMapper ) const; + // Convert a contour map local vertex to domain coordinates. Without an elevation provider this is + // the vertex offset by RigContourMapGrid::origin3d(). With one, the z component is replaced by the + // elevation reported for the vertex position, and nothing is returned where the provider has no + // elevation to offer. + static std::optional toDomainCoord( const cvf::Vec3d& localVertex, + const RigContourMapGrid& contourMapGrid, + const RivContourMapElevationProvider* elevationProvider ); + + static std::optional toDisplayCoord( const cvf::Vec3d& localVertex, + const RigContourMapGrid& contourMapGrid, + const caf::DisplayCoordTransform* displayCoordTransform, + const RivContourMapElevationProvider* elevationProvider ); + + // The segments of a closed contour polygon in display coordinates, sampled densely enough to follow + // the elevation reported by the provider. Segments where no elevation is defined are left out. + static std::vector> resampledDisplaySegments( const std::vector& localVertices, + const RigContourMapGrid& contourMapGrid, + const caf::DisplayCoordTransform* displayCoordTransform, + const RivContourMapElevationProvider* elevationProvider ); + + static cvf::ref + createTextLabel( const cvf::Color3f& textColor, const cvf::Color3f& backgroundColor, caf::FontTools::FontSize fontSize ); + cvf::ref createProjectionMapPart( const caf::DisplayCoordTransform* displayCoordTransform, + const std::vector& vertices, + const RigContourMapGrid& contourMapGrid, + const cvf::Color3f& backgroundColor, + cvf::ScalarMapper* scalarMapper, + const RivContourMapElevationProvider* elevationProvider ) const; std::vector>> createContourPolygons( const caf::DisplayCoordTransform* displayCoordTransform, const std::vector>& labelBBoxes, const std::vector& contourLinePolygons, cvf::ScalarMapper* scalarMapper, - const RigContourMapGrid& contourMapGrid ) const; + const RigContourMapGrid& contourMapGrid, + const RivContourMapElevationProvider* elevationProvider ) const; std::vector> createContourLabels( const cvf::Camera* camera, const caf::DisplayCoordTransform* displayCoordTransform, std::vector>* labelBBoxes, const std::vector& contourLinePolygons, - const RigContourMapGrid& contourMapGrid, - const cvf::ScalarMapper* scalarMapper, - caf::NumberFormatType numberFormat, - int precision ) const; + const RigContourMapGrid& contourMapGrid, + const cvf::ScalarMapper* scalarMapper, + caf::NumberFormatType numberFormat, + int precision, + const RivContourMapElevationProvider* elevationProvider, + const RivContourLineAppearance& lineAppearance ) const; cvf::ref createPickPointVisDrawable( const caf::DisplayCoordTransform* displayCoordTransform, const cvf::Vec2d& pickPoint,