Files
ResInsight/ApplicationLibCode/ProjectDataModel/Intersections/RimIjkIntersection.cpp
T

534 lines
20 KiB
C++

/////////////////////////////////////////////////////////////////////////////////
//
// 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 <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////
#include "RimIjkIntersection.h"
#include "RigMainGrid.h"
#include "Rim3dView.h"
#include "RimEclipseView.h"
#include "RivIjkIntersectionPartMgr.h"
#include "cafCmdFeatureMenuBuilder.h"
#include "cafPdmUiSliderEditor.h"
#include "cafPdmUiTreeOrdering.h"
#include <algorithm>
namespace caf
{
template <>
void AppEnum<RimIjkIntersection::GridAxis>::setUp()
{
addItem( RimIjkIntersection::GridAxis::AXIS_I, "AXIS_I", "I" );
addItem( RimIjkIntersection::GridAxis::AXIS_J, "AXIS_J", "J" );
addItem( RimIjkIntersection::GridAxis::AXIS_K, "AXIS_K", "K" );
setDefault( RimIjkIntersection::GridAxis::AXIS_I );
}
} // namespace caf
CAF_PDM_SOURCE_INIT( RimIjkIntersection, "IjkIntersection" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimIjkIntersection::RimIjkIntersection()
{
CAF_PDM_InitObject( "Intersection I/J/K", ":/IntersectionBox16x16.png" );
CAF_PDM_InitField( &m_name, "UserDescription", QString( "Intersection I/J/K" ), "Name" );
CAF_PDM_InitFieldNoDefault( &m_axis, "Axis", "Axis" );
CAF_PDM_InitField( &m_useNegativeFace, "UseNegativeFace", false, "Use Back Pillar Face" );
CAF_PDM_InitField( &m_fixedIndex, "FixedIndex", 1, "Index" );
m_fixedIndex.uiCapability()->setUiEditorTypeName( caf::PdmUiSliderEditor::uiEditorTypeName() );
CAF_PDM_InitField( &m_iMin, "IMin", 1, "Min" );
CAF_PDM_InitField( &m_iMax, "IMax", 1, "Max" );
CAF_PDM_InitField( &m_jMin, "JMin", 1, "Min" );
CAF_PDM_InitField( &m_jMax, "JMax", 1, "Max" );
CAF_PDM_InitField( &m_kMin, "KMin", 1, "Min" );
CAF_PDM_InitField( &m_kMax, "KMax", 1, "Max" );
for ( caf::PdmField<int>* field : { &m_iMin, &m_iMax, &m_jMin, &m_jMax, &m_kMin, &m_kMax } )
{
field->uiCapability()->setUiEditorTypeName( caf::PdmUiSliderEditor::uiEditorTypeName() );
}
setDeletable( true );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimIjkIntersection::~RimIjkIntersection()
{
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
caf::PdmFieldHandle* RimIjkIntersection::userDescriptionField()
{
return &m_name;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimIjkIntersection::name() const
{
return m_name();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimIjkIntersection::setName( const QString& newName )
{
m_name = newName;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimIjkIntersection::GridAxis RimIjkIntersection::axis() const
{
return m_axis();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RimIjkIntersection::useNegativeFace() const
{
return m_useNegativeFace();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
int RimIjkIntersection::fixedIndex() const
{
return m_fixedIndex() - 1;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RigBoundingBoxIjk<caf::VecIjk0> RimIjkIntersection::ijkRange() const
{
// Guard against non-positive values from hand-edited project files before converting to unsigned
auto zeroBased = []( int oneBasedValue ) { return static_cast<size_t>( std::max( 1, oneBasedValue ) - 1 ); };
caf::VecIjk0 min( zeroBased( m_iMin() ), zeroBased( m_jMin() ), zeroBased( m_kMin() ) );
caf::VecIjk0 max( zeroBased( m_iMax() ), zeroBased( m_jMax() ), zeroBased( m_kMax() ) );
return RigBoundingBoxIjk<caf::VecIjk0>( min, max );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimIjkIntersection::setAxis( GridAxis axis )
{
m_axis = axis;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimIjkIntersection::setUseNegativeFace( bool useNegativeFace )
{
m_useNegativeFace = useNegativeFace;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimIjkIntersection::setFixedIndex( int fixedIndex )
{
m_fixedIndex = fixedIndex + 1;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimIjkIntersection::setIjkRange( const RigBoundingBoxIjk<caf::VecIjk0>& range )
{
m_iMin = static_cast<int>( range.min().i() ) + 1;
m_iMax = static_cast<int>( range.max().i() ) + 1;
m_jMin = static_cast<int>( range.min().j() ) + 1;
m_jMax = static_cast<int>( range.max().j() ) + 1;
m_kMin = static_cast<int>( range.min().k() ) + 1;
m_kMax = static_cast<int>( range.max().k() ) + 1;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimIjkIntersection::setToDefaultValues()
{
RigMainGrid* grid = mainGrid();
if ( !grid ) return;
int ni = static_cast<int>( grid->cellCountI() );
int nj = static_cast<int>( grid->cellCountJ() );
int nk = static_cast<int>( grid->cellCountK() );
m_axis = GridAxis::AXIS_I;
m_iMin = 1;
m_iMax = ni;
m_jMin = 1;
m_jMax = nj;
m_kMin = 1;
m_kMax = nk;
m_fixedIndex = ni / 2 + 1;
}
//--------------------------------------------------------------------------------------------------
/// The I and J axes give a vertical curtain. A K slice is horizontal, and would require the surface
/// to be contoured instead of projected along a vertical ray.
//--------------------------------------------------------------------------------------------------
bool RimIjkIntersection::supportsSurfaceIntersectionCurves() const
{
return m_axis() != GridAxis::AXIS_K;
}
//--------------------------------------------------------------------------------------------------
/// Walk the cells of the index plane along the varying axis and pick the cell face corners. Each
/// pillar runs from the top of the first k layer to the bottom of the last k layer, so a tilted
/// pillar gives a curve that lies on the curtain rather than on a vertical plane through it.
//--------------------------------------------------------------------------------------------------
RimIntersectionCurtain RimIjkIntersection::surfaceCurtain() const
{
if ( !supportsSurfaceIntersectionCurves() ) return {};
RigMainGrid* grid = mainGrid();
if ( !grid ) return {};
const auto cellRange = clampedCellRange( grid );
if ( !cellRange ) return {};
const bool alongJ = m_axis() == GridAxis::AXIS_I;
// Corner indices of the cell face, see the hex vertex numbering in StructGridInterface::cellFaceVertexIndices().
// The first corner is at the start of the varying axis, the second at the end of the same axis. Corners 0-3 are
// the low k side of the cell, and adding 4 gives the corner on the same pillar at the high k side.
const int cornersPerKSide = 4;
int startCorner = 0;
int endCorner = 0;
if ( alongJ )
{
startCorner = m_useNegativeFace() ? 0 : 1;
endCorner = m_useNegativeFace() ? 3 : 2;
}
else
{
startCorner = m_useNegativeFace() ? 0 : 3;
endCorner = m_useNegativeFace() ? 1 : 2;
}
// The fixed axis is collapsed to a single index by clampedCellRange()
const size_t fixedI = cellRange->min().i();
const size_t fixedJ = cellRange->min().j();
const size_t firstK = cellRange->min().k();
const size_t lastK = cellRange->max().k();
const size_t varyingMin = alongJ ? cellRange->min().j() : cellRange->min().i();
const size_t varyingMax = alongJ ? cellRange->max().j() : cellRange->max().i();
RimIntersectionCurtain curtain;
// The pillar at one position is spanned between the first and the last cell of the k range. The
// top of the pillar is used as the trace, so the curve is resampled along the top of the curtain.
auto appendPillar = [&]( size_t i, size_t j, int corner )
{
const size_t topCellIndex = grid->cellIndexFromIJK( i, j, firstK );
const size_t bottomCellIndex = grid->cellIndexFromIJK( i, j, lastK );
if ( topCellIndex == cvf::UNDEFINED_SIZE_T || bottomCellIndex == cvf::UNDEFINED_SIZE_T ) return;
if ( grid->cell( topCellIndex ).isInvalid() || grid->cell( bottomCellIndex ).isInvalid() ) return;
const auto topCorners = grid->cellCornerVertices( topCellIndex );
const auto bottomCorners = grid->cellCornerVertices( bottomCellIndex );
curtain.trace.push_back( topCorners[corner] );
curtain.pillars.emplace_back( topCorners[corner], bottomCorners[corner + cornersPerKSide] );
};
for ( size_t varying = varyingMin; varying <= varyingMax; ++varying )
{
const size_t i = alongJ ? fixedI : varying;
const size_t j = alongJ ? varying : fixedJ;
appendPillar( i, j, startCorner );
if ( varying == varyingMax ) appendPillar( i, j, endCorner );
}
return curtain;
}
//--------------------------------------------------------------------------------------------------
/// The index range of the visible cells, with the fixed axis collapsed to the fixed index
//--------------------------------------------------------------------------------------------------
std::optional<RigBoundingBoxIjk<caf::VecIjk0>> RimIjkIntersection::clampedCellRange( const RigMainGrid* grid ) const
{
if ( !grid ) return {};
const size_t ni = grid->cellCountI();
const size_t nj = grid->cellCountJ();
const size_t nk = grid->cellCountK();
if ( ni == 0 || nj == 0 || nk == 0 ) return {};
const auto range = ijkRange();
caf::VecIjk0 min = range.min();
caf::VecIjk0 max = range.max();
const size_t fixed = static_cast<size_t>( std::max( 0, fixedIndex() ) );
switch ( m_axis() )
{
case GridAxis::AXIS_I:
min.x() = max.x() = std::min( fixed, ni - 1 );
break;
case GridAxis::AXIS_J:
min.y() = max.y() = std::min( fixed, nj - 1 );
break;
case GridAxis::AXIS_K:
default:
min.z() = max.z() = std::min( fixed, nk - 1 );
break;
}
const RigBoundingBoxIjk<caf::VecIjk0> gridBounds( caf::VecIjk0::ZERO, caf::VecIjk0( ni - 1, nj - 1, nk - 1 ) );
return RigBoundingBoxIjk<caf::VecIjk0>( min, max ).clamp( gridBounds );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RivIjkIntersectionPartMgr* RimIjkIntersection::intersectionPartMgr()
{
if ( m_intersectionPartMgr.isNull() ) m_intersectionPartMgr = new RivIjkIntersectionPartMgr( this );
return m_intersectionPartMgr.p();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimIjkIntersection::clearGeometry()
{
m_intersectionPartMgr = nullptr;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
const RivIntersectionGeometryGeneratorInterface* RimIjkIntersection::intersectionGeometryGenerator() const
{
if ( m_intersectionPartMgr.notNull() ) return m_intersectionPartMgr->intersectionGeometryGenerator();
return nullptr;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RigMainGrid* RimIjkIntersection::mainGrid() const
{
auto eclipseView = firstAncestorOrThisOfType<RimEclipseView>();
if ( eclipseView ) return eclipseView->mainGrid();
return nullptr;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
int RimIjkIntersection::axisCellCount() const
{
RigMainGrid* grid = mainGrid();
if ( !grid ) return 0;
switch ( m_axis() )
{
case GridAxis::AXIS_I:
return static_cast<int>( grid->cellCountI() );
case GridAxis::AXIS_J:
return static_cast<int>( grid->cellCountJ() );
case GridAxis::AXIS_K:
return static_cast<int>( grid->cellCountK() );
}
return 0;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimIjkIntersection::defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering )
{
uiOrdering.add( &m_name );
{
caf::PdmUiGroup* group = uiOrdering.addNewGroup( "Options" );
group->add( &m_axis );
group->add( &m_useNegativeFace );
group->add( &m_showInactiveCells );
QString axisLabel = caf::AppEnum<GridAxis>::uiText( m_axis() );
m_fixedIndex.uiCapability()->setUiName( axisLabel + " Index" );
group->add( &m_fixedIndex );
}
RigMainGrid* grid = mainGrid();
int ni = grid ? static_cast<int>( grid->cellCountI() ) : 0;
int nj = grid ? static_cast<int>( grid->cellCountJ() ) : 0;
int nk = grid ? static_cast<int>( grid->cellCountK() ) : 0;
if ( m_axis() != GridAxis::AXIS_I )
{
caf::PdmUiGroup* group = uiOrdering.addNewGroup( QString( "I Range [1 .. %1]" ).arg( std::max( 1, ni ) ) );
group->add( &m_iMin );
group->add( &m_iMax );
}
if ( m_axis() != GridAxis::AXIS_J )
{
caf::PdmUiGroup* group = uiOrdering.addNewGroup( QString( "J Range [1 .. %1]" ).arg( std::max( 1, nj ) ) );
group->add( &m_jMin );
group->add( &m_jMax );
}
if ( m_axis() != GridAxis::AXIS_K )
{
caf::PdmUiGroup* group = uiOrdering.addNewGroup( QString( "K Range [1 .. %1]" ).arg( std::max( 1, nk ) ) );
group->add( &m_kMin );
group->add( &m_kMax );
}
defineSeparateDataSourceUi( uiConfigName, uiOrdering );
if ( grid )
{
auto setRange = []( caf::PdmFieldHandle& field, int maximum )
{ caf::PdmUiSliderEditor::setRange( field.uiCapability(), 1, std::max( 1, maximum ) ); };
setRange( m_fixedIndex, axisCellCount() );
setRange( m_iMin, ni );
setRange( m_iMax, ni );
setRange( m_jMin, nj );
setRange( m_jMax, nj );
setRange( m_kMin, nk );
setRange( m_kMax, nk );
}
else
{
for ( auto* field : { &m_fixedIndex, &m_iMin, &m_iMax, &m_jMin, &m_jMax, &m_kMin, &m_kMax } )
{
caf::PdmUiSliderEditor::clearRange( field->uiCapability() );
}
}
uiOrdering.skipRemainingFields( true );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimIjkIntersection::appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const
{
appendCommonMenuItems( menuBuilder );
menuBuilder << "RicCopyIntersectionsToAllViewsInCaseFeature";
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimIjkIntersection::defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering, QString uiConfigName )
{
appendSurfaceIntersectionsToTreeOrdering( uiTreeOrdering );
uiTreeOrdering.skipRemainingChildren( true );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimIjkIntersection::fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue )
{
RigMainGrid* grid = mainGrid();
if ( grid )
{
int ni = static_cast<int>( grid->cellCountI() );
int nj = static_cast<int>( grid->cellCountJ() );
int nk = static_cast<int>( grid->cellCountK() );
auto clamp = []( caf::PdmField<int>& f, int hi ) { f = std::max( 1, std::min( f(), hi ) ); };
clamp( m_iMin, ni );
clamp( m_iMax, ni );
clamp( m_jMin, nj );
clamp( m_jMax, nj );
clamp( m_kMin, nk );
clamp( m_kMax, nk );
clamp( m_fixedIndex, std::max( 1, axisCellCount() ) );
// Keep each min/max pair ordered by limiting the changed field to the other one
if ( changedField == &m_iMin ) m_iMin = std::min( m_iMin(), m_iMax() );
if ( changedField == &m_iMax ) m_iMax = std::max( m_iMin(), m_iMax() );
if ( changedField == &m_jMin ) m_jMin = std::min( m_jMin(), m_jMax() );
if ( changedField == &m_jMax ) m_jMax = std::max( m_jMin(), m_jMax() );
if ( changedField == &m_kMin ) m_kMin = std::min( m_kMin(), m_kMax() );
if ( changedField == &m_kMax ) m_kMax = std::max( m_kMin(), m_kMax() );
}
if ( changedField == &m_axis )
{
updateConnectedEditors();
}
if ( changedField != &m_name )
{
rebuildGeometryAndScheduleCreateDisplayModel();
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimIjkIntersection::rebuildGeometryAndScheduleCreateDisplayModel()
{
m_intersectionPartMgr = nullptr;
auto rimView = firstAncestorOrThisOfType<Rim3dView>();
if ( rimView )
{
rimView->scheduleCreateDisplayModelAndRedraw();
}
}