///////////////////////////////////////////////////////////////////////////////// // // 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 "RimContourMapInView.h" #include "ContourMap/RigContourMapGrid.h" #include "ContourMap/RigContourMapTopography.h" #include "RigEclipseCaseData.h" #include "RigMainGrid.h" #include "Surface/RigSurface.h" #include "ContourMap/RimContourMapProjection.h" #include "ContourMap/RimEclipseContourMapView.h" #include "Rim3dView.h" #include "RimEclipseCase.h" #include "RimEclipseView.h" #include "RimGridView.h" #include "RimRegularLegendConfig.h" #include "RimViewNameConfig.h" #include "Surfaces/RimSurface.h" #include "Surfaces/RimSurfaceInView.h" #include "Surfaces/RimSurfaceInViewCollection.h" #include "RivContourMapElevationProvider.h" #include "RivContourMapProjectionPartMgr.h" #include "Riu3DMainWindowTools.h" #include "RiuViewer.h" #include "cvfCamera.h" #include "cvfModelBasicList.h" CAF_PDM_SOURCE_INIT( RimContourMapInView, "RimContourMapInView" ); namespace caf { template <> void AppEnum::setUp() { addItem( RimContourMapInView::MapPosition::TOP_OF_CASE, "TOP_OF_CASE", "Top of Case" ); addItem( RimContourMapInView::MapPosition::BOTTOM_OF_CASE, "BOTTOM_OF_CASE", "Bottom of Case" ); addItem( RimContourMapInView::MapPosition::USER_DEFINED_DEPTH, "USER_DEFINED_DEPTH", "User Defined Depth" ); setDefault( RimContourMapInView::MapPosition::TOP_OF_CASE ); } template <> void AppEnum::setUp() { addItem( RimContourMapInView::LineColorMode::CONTRAST_TO_MAP, "CONTRAST_TO_MAP", "Contrast to Map" ); addItem( RimContourMapInView::LineColorMode::SINGLE_COLOR, "SINGLE_COLOR", "Single Color" ); setDefault( RimContourMapInView::LineColorMode::CONTRAST_TO_MAP ); } } // namespace caf //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimContourMapInView::RimContourMapInView() { CAF_PDM_InitObject( "Contour Map", ":/2DMap16x16.png" ); CAF_PDM_InitFieldNoDefault( &m_contourMapView, "ContourMapView", "Contour Map" ); m_contourMapView.uiCapability()->setUiHidden( true ); CAF_PDM_InitFieldNoDefault( &m_nameProxy, "ContourMapName", "Name" ); m_nameProxy.registerGetMethod( this, &RimContourMapInView::name ); m_nameProxy.uiCapability()->setUiReadOnly( true ); CAF_PDM_InitFieldNoDefault( &m_mapPosition, "MapPosition", "Map Position" ); CAF_PDM_InitField( &m_depthOffset, "DepthOffset", 0.0, "Depth Offset" ); m_depthOffset.uiCapability()->setUiToolTip( "Positive values move the map upwards." ); CAF_PDM_InitField( &m_userDefinedDepth, "UserDefinedDepth", 0.0, "Depth" ); CAF_PDM_InitField( &m_showMapSurface, "ShowMapSurface", true, "Show Map Surface" ); CAF_PDM_InitField( &m_showContourLines, "ShowContourLines", true, "Show Contour Lines" ); CAF_PDM_InitField( &m_projectSurfaceOnGeometry, "ProjectSurfaceOnGeometry", false, "Project Surface on Geometry" ); m_projectSurfaceOnGeometry.uiCapability()->setUiToolTip( "Follow the top of the visible geometry instead of drawing the map surface on the map plane." ); CAF_PDM_InitField( &m_projectLinesOnGeometry, "ProjectLinesOnGeometry", false, "Project Lines on Geometry" ); m_projectLinesOnGeometry.uiCapability()->setUiToolTip( "Follow the top of the visible geometry instead of drawing the contour lines on the map plane." ); CAF_PDM_InitField( &m_showContourLabels, "ShowContourLabels", false, "Show Contour Labels" ); CAF_PDM_InitField( &m_labelFontSize, "LabelFontSize", RiaFontCache::FontSizeEnum( RiaFontCache::FontSize::FONT_SIZE_10 ), "Label Font Size" ); CAF_PDM_InitField( &m_lineColorMode, "LineColorMode", caf::AppEnum( LineColorMode::CONTRAST_TO_MAP ), "Line Color Mode" ); m_lineColorMode.uiCapability()->setUiToolTip( "Contrast to Map gives every contour level a color contrasting the map underneath it." ); CAF_PDM_InitField( &m_lineColor, "LineColor", cvf::Color3f( cvf::Color3f::BLACK ), "Line Color" ); CAF_PDM_InitField( &m_lineThickness, "LineThickness", 1, "Line Thickness" ); m_lineThickness.setRange( 1, 10 ); nameField()->uiCapability()->setUiHidden( true ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimContourMapInView::~RimContourMapInView() { } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimEclipseContourMapView* RimContourMapInView::contourMapView() const { return m_contourMapView(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimEclipseContourMapView* RimContourMapInView::sourceItem() const { return contourMapView(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimContourMapInView::setContourMapView( RimEclipseContourMapView* contourMapView ) { m_contourMapView = contourMapView; clearGeometry(); updateUiIconFromToggleField(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- QString RimContourMapInView::name() const { if ( !m_contourMapView() ) return "Contour Map"; // Use the auto-generated name (custom name part plus generated tags) instead of only the custom name part. // Some contour map view types (e.g. RimStatisticsContourMapView) intentionally leave the custom name part // empty and rely fully on the generated tags, which would otherwise result in an empty name here. return m_contourMapView()->nameConfig()->name(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- caf::PdmFieldHandle* RimContourMapInView::userDescriptionField() { return &m_nameProxy; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimContourMapProjection* RimContourMapInView::contourMapProjection() const { if ( !m_contourMapView() ) return nullptr; return m_contourMapView()->contourMapProjection(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RivContourMapProjectionPartMgr* RimContourMapInView::partMgr() { if ( m_partMgr.isNull() ) m_partMgr = new RivContourMapProjectionPartMgr( contourMapProjection() ); return m_partMgr.p(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimContourMapInView::clearPartMgr() { m_partMgr = nullptr; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimContourMapInView::clearGeometry() { clearPartMgr(); m_topography = nullptr; m_topographyMapGrid = nullptr; m_topographyMainGrid = nullptr; m_topographyCellVisibility = nullptr; m_topographySurfaces.clear(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::optional RimContourMapInView::mapElevation() const { auto projection = contourMapProjection(); if ( !projection ) return {}; const RigContourMapGrid* mapGrid = projection->mapGrid(); if ( !mapGrid ) return {}; double elevation = 0.0; switch ( m_mapPosition() ) { case MapPosition::TOP_OF_CASE: elevation = mapGrid->expandedBoundingBox().max().z(); break; case MapPosition::BOTTOM_OF_CASE: elevation = mapGrid->origin3d().z(); break; case MapPosition::USER_DEFINED_DEPTH: elevation = -m_userDefinedDepth(); break; } return elevation + m_depthOffset(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- double RimContourMapInView::mapSampleSpacing() const { auto projection = contourMapProjection(); if ( !projection || !projection->mapGrid() ) return 0.0; return projection->mapGrid()->sampleSpacing(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- double RimContourMapInView::surfaceDrapeLift() const { // Draped straight onto the geometry the map surface would be coplanar with it and the two would // z-fight. Lift it clear instead. Scales with the model, and is small enough to be invisible at any // reasonable zoom level. return 0.01 * mapSampleSpacing(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- double RimContourMapInView::contourLineLift() const { // Twice the lift of the map surface, so the lines stay on top of it whether it is draped or flat return 2.0 * surfaceDrapeLift(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::shared_ptr RimContourMapInView::topography() { auto projection = contourMapProjection(); if ( !projection || !projection->mapGrid() ) return nullptr; // The drape follows the geometry of the view the contour map is shown in, not the geometry of the // case the contour map was computed from. auto hostView = firstAncestorOfType(); if ( !hostView || !hostView->eclipseCase() || !hostView->eclipseCase()->eclipseCaseData() ) return nullptr; RigMainGrid* mainGrid = hostView->eclipseCase()->eclipseCaseData()->mainGrid(); if ( !mainGrid ) return nullptr; const RigContourMapGrid* mapGrid = projection->mapGrid(); cvf::ref cellVisibility = hostView->currentTotalCellVisibility(); // A view can be showing surfaces and no grid cells at all, so both are drape targets std::vector surfaces; if ( auto surfaceCollection = hostView->surfaceInViewCollection() ) { for ( RimSurfaceInView* surfaceInView : surfaceCollection->visibleSurfacesInView() ) { if ( !surfaceInView->surface() ) continue; if ( RigSurface* surfaceData = surfaceInView->surface()->surfaceData() ) surfaces.push_back( surfaceData ); } } // Cell filters and property filters make the view produce a new visibility array, a resolution // change makes the projection produce a new map grid, reloading the case produces a new main grid, // and surfaces come and go as they are checked. Any of those invalidates the raster. if ( m_topography && m_topographyMapGrid == mapGrid && m_topographyMainGrid == mainGrid && m_topographyCellVisibility.p() == cellVisibility.p() && m_topographySurfaces == surfaces ) { return m_topography; } m_topography = std::make_shared( *mapGrid, *mainGrid, cellVisibility.p(), surfaces ); m_topographyMapGrid = mapGrid; m_topographyMainGrid = mainGrid; m_topographyCellVisibility = cellVisibility.p(); m_topographySurfaces = surfaces; return m_topography; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimContourMapInView::appendPartsToModel( cvf::ModelBasicList* model, const caf::DisplayCoordTransform* displayCoordTransform, const cvf::Camera* camera ) { if ( !isChecked() || !model || !displayCoordTransform ) return; auto sourceView = m_contourMapView(); if ( !sourceView || !sourceView->eclipseCase() || !sourceView->eclipseCase()->eclipseCaseData() ) return; auto projection = contourMapProjection(); if ( !projection || !projection->isChecked() ) return; // The projection is owned by the contour map view and shared with it, so it is generated for the // time step that view is on. Driving it to the host view's time step instead would leave the contour // map view holding results for a step it is not showing, and make the two views recompute the whole // projection against each other on every redraw. Generating is still needed here, since the contour // map view may never have been opened. projection->generateResultsIfNecessary( sourceView->currentTimeStep() ); projection->generateGeometryIfNecessary(); if ( !projection->mapGrid() ) return; auto* mapLegendConfig = projection->legendConfig(); if ( !mapLegendConfig || !mapLegendConfig->scalarMapper() ) return; auto elevation = mapElevation(); if ( !elevation ) return; // Falls back to the map plane when the topography cannot be built, for instance when the host view // has no loaded grid std::shared_ptr mapTopography; if ( m_projectSurfaceOnGeometry() || m_projectLinesOnGeometry() ) mapTopography = topography(); RivContourMapFlatElevation flatElevation( *elevation ); if ( m_showMapSurface() ) { std::unique_ptr surfaceTopography; if ( m_projectSurfaceOnGeometry() && mapTopography ) { // Lifted clear of the geometry it follows, otherwise the two are coplanar and z-fight surfaceTopography = std::make_unique( mapTopography, m_depthOffset() + surfaceDrapeLift() ); } const RivContourMapElevationProvider* surfaceElevation = surfaceTopography ? static_cast( surfaceTopography.get() ) : &flatElevation; partMgr()->appendProjectionToModel( model, displayCoordTransform, projection->trianglesWithVertexValues(), *projection->mapGrid(), sourceView->backgroundColor(), mapLegendConfig->scalarMapper(), surfaceElevation ); } if ( !m_showContourLines() || !camera ) return; // The lines are drawn as line primitives, which OpenGL polygon offset does not apply to, so they // have to be lifted above the surface they follow to win the depth test against it. const double lineLift = contourLineLift(); std::unique_ptr lineTopography; if ( m_projectLinesOnGeometry() && mapTopography ) { lineTopography = std::make_unique( mapTopography, m_depthOffset() + lineLift ); } RivContourMapFlatElevation flatLineElevation( *elevation + lineLift ); const RivContourMapElevationProvider* lineElevation = lineTopography ? static_cast( lineTopography.get() ) : &flatLineElevation; RivContourLineAppearance lineAppearance; if ( m_lineColorMode() == LineColorMode::SINGLE_COLOR ) lineAppearance.color = m_lineColor(); lineAppearance.lineWidth = static_cast( m_lineThickness() ); lineAppearance.labelFontSize = m_labelFontSize(); // The camera moves freely here, unlike in the 2d contour map views, so labels are kept upright lineAppearance.alignLabelsWithCamera = true; // The labels are anchored in 3d and re-projected to the screen every frame, so they stay readable // and correctly placed while navigating. Only the layout decisions, which labels were dropped for // overlapping and how the text is rotated, are made from the camera as it is right now. partMgr()->appendContourLinesToModel( camera, model, displayCoordTransform, projection->contourPolygons(), *projection->mapGrid(), mapLegendConfig->scalarMapper(), true, m_showContourLabels(), mapLegendConfig->tickNumberFormat(), mapLegendConfig->significantDigitsInData(), lineElevation, lineAppearance ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RimRegularLegendConfig* RimContourMapInView::legendConfig() const { auto projection = contourMapProjection(); if ( !projection ) return nullptr; return projection->legendConfig(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimContourMapInView::updateLegendRangesTextAndVisibility( RiuViewer* nativeOrOverrideViewer, bool isUsingOverrideViewer ) { if ( !isChecked() || !nativeOrOverrideViewer ) return; auto projection = contourMapProjection(); if ( !projection || !projection->isChecked() ) return; // Sets both the ranges and the title projection->updateLegend(); auto* mapLegendConfig = projection->legendConfig(); if ( !mapLegendConfig || !mapLegendConfig->showLegend() ) return; nativeOrOverrideViewer->addColorLegendToBottomLeftCorner( mapLegendConfig->titledOverlayFrame(), isUsingOverrideViewer ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimContourMapInView::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) { uiOrdering.add( &m_nameProxy ); if ( m_contourMapView() ) { uiOrdering.addNewButton( "Go to Contour Map", [this]() { Riu3DMainWindowTools::selectAsCurrentItem( m_contourMapView() ); } ); } caf::PdmUiGroup* positionGroup = uiOrdering.addNewGroup( "Position" ); positionGroup->add( &m_mapPosition ); if ( m_mapPosition() == MapPosition::USER_DEFINED_DEPTH ) { positionGroup->add( &m_userDefinedDepth ); } positionGroup->add( &m_depthOffset ); caf::PdmUiGroup* surfaceGroup = uiOrdering.addNewGroup( "Map Surface" ); surfaceGroup->add( &m_showMapSurface ); surfaceGroup->add( &m_projectSurfaceOnGeometry ); m_projectSurfaceOnGeometry.uiCapability()->setUiReadOnly( !m_showMapSurface() ); caf::PdmUiGroup* contourLineGroup = uiOrdering.addNewGroup( "Contour Lines" ); contourLineGroup->add( &m_showContourLines ); contourLineGroup->add( &m_projectLinesOnGeometry ); contourLineGroup->add( &m_lineColorMode ); if ( m_lineColorMode() == LineColorMode::SINGLE_COLOR ) { contourLineGroup->add( &m_lineColor ); } contourLineGroup->add( &m_lineThickness ); contourLineGroup->add( &m_showContourLabels ); contourLineGroup->add( &m_labelFontSize ); // The toggle itself stays editable, everything it controls does not const bool linesDisabled = !m_showContourLines(); m_projectLinesOnGeometry.uiCapability()->setUiReadOnly( linesDisabled ); m_lineColorMode.uiCapability()->setUiReadOnly( linesDisabled ); m_lineColor.uiCapability()->setUiReadOnly( linesDisabled ); m_lineThickness.uiCapability()->setUiReadOnly( linesDisabled ); m_showContourLabels.uiCapability()->setUiReadOnly( linesDisabled ); m_labelFontSize.uiCapability()->setUiReadOnly( linesDisabled || !m_showContourLabels() ); uiOrdering.skipRemainingFields( true ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimContourMapInView::fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) { updateUiIconFromToggleField(); if ( changedField == &m_mapPosition || changedField == &m_projectSurfaceOnGeometry || changedField == &m_projectLinesOnGeometry || changedField == &m_lineColorMode || changedField == &m_showContourLines || changedField == &m_showMapSurface || changedField == &m_showContourLabels ) { updateConnectedEditors(); } if ( auto view = firstAncestorOfType() ) { view->scheduleCreateDisplayModelAndRedraw(); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimContourMapInView::initAfterRead() { updateUiIconFromToggleField(); }