#14226 intersection along ijk axis

* #14226 Add intersection along i/j/k axis

Add a new persistent intersection type that follows the grid pillars at a
fixed i, j or k index. Unlike the axis-aligned intersection box, the surface
is built from the grid cell faces and therefore steps along faults and curved
pillars, which is useful for structural restoration analysis.

The user selects the axis, the fixed index, the two perpendicular index
ranges and which cell face (front/back pillar) to use. The geometry generator
emits the chosen cell face of every cell in the index plane directly from the
native pillar corners, so cell result colors map exactly.

The feature is available from the Intersections context menu for Eclipse
views and supports 3D-view picking and cell result readout.
This commit is contained in:
Kristian Bendiksen authored and GitHub committed 2026-07-27 11:21:05 +02:00
1 parent 2ebefe73b6
commit 5dc3374733
26 files changed
+1841 -24

No files matched your search

@@ -24,6 +24,7 @@
#include "RimEclipseCase.h"
#include "RimExtrudedCurveIntersection.h"
#include "RimGridView.h"
#include "RimIjkIntersection.h"
#include "RimIntersectionCollection.h"
#include "cafCmdExecCommandManager.h"
@@ -43,14 +44,13 @@ enum SelectionComposition
{
SEL_INVALID,
SEL_COLLECTION,
SEL_INTERSECTIONS,
SEL_INTERSECTION_BOXES,
SEL_BOTH_INTERSECTION_TYPES
SEL_INTERSECTION_ITEMS
};
static RimIntersectionCollection* selectedIntersectionCollection();
static std::vector<RimExtrudedCurveIntersection*> selectedIntersections();
static std::vector<RimBoxIntersection*> selectedIntersectionBoxes();
static std::vector<RimIjkIntersection*> selectedIjkIntersections();
static SelectionComposition selectionComposition();
static RimCase* commonGridCase( std::vector<caf::PdmUiItem*> selectedItems );
@@ -77,18 +77,13 @@ void RicCopyIntersectionsToAllViewsInCaseFeature::onActionTriggered( bool isChec
RimIntersectionCollection* coll = selectedIntersectionCollection();
copyIntersectionsToOtherViews( *gridCase, coll->intersections() );
copyIntersectionBoxesToOtherViews( *gridCase, coll->intersectionBoxes() );
copyIjkIntersectionsToOtherViews( *gridCase, coll->ijkIntersections() );
}
std::vector<RimExtrudedCurveIntersection*> selIntersections = selectedIntersections();
std::vector<RimBoxIntersection*> selIntersectionBoxes = selectedIntersectionBoxes();
if ( compostion == SEL_INTERSECTIONS || compostion == SEL_BOTH_INTERSECTION_TYPES )
else if ( compostion == SEL_INTERSECTION_ITEMS )
{
copyIntersectionsToOtherViews( *gridCase, selIntersections );
}
if ( compostion == SEL_INTERSECTION_BOXES || compostion == SEL_BOTH_INTERSECTION_TYPES )
{
copyIntersectionBoxesToOtherViews( *gridCase, selIntersectionBoxes );
copyIntersectionsToOtherViews( *gridCase, selectedIntersections() );
copyIntersectionBoxesToOtherViews( *gridCase, selectedIntersectionBoxes() );
copyIjkIntersectionsToOtherViews( *gridCase, selectedIjkIntersections() );
}
}
}
@@ -158,6 +153,32 @@ void RicCopyIntersectionsToAllViewsInCaseFeature::copyIntersectionBoxesToOtherVi
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicCopyIntersectionsToAllViewsInCaseFeature::copyIjkIntersectionsToOtherViews( RimCase& gridCase,
std::vector<RimIjkIntersection*> ijkIntersections )
{
for ( RimIjkIntersection* ijkIntersection : ijkIntersections )
{
for ( Rim3dView* const view : gridCase.views() )
{
RimGridView* currGridView = dynamic_cast<RimGridView*>( view );
RimGridView* parentView = ijkIntersection->firstAncestorOrThisOfType<RimGridView>();
if ( currGridView && parentView != nullptr && parentView != currGridView )
{
RimIntersectionCollection* destCollection = currGridView->intersectionCollection();
auto copy = ijkIntersection->copyObject<RimIjkIntersection>();
CVF_ASSERT( copy );
destCollection->appendIjkIntersectionAndUpdate( copy );
}
}
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -183,6 +204,14 @@ std::vector<RimBoxIntersection*> selectedIntersectionBoxes()
return caf::selectedObjectsByType<RimBoxIntersection*>();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<RimIjkIntersection*> selectedIjkIntersections()
{
return caf::selectedObjectsByType<RimIjkIntersection*>();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -193,9 +222,7 @@ SelectionComposition selectionComposition()
RimCase* gridCase = commonGridCase( selectedItems );
if ( gridCase && gridCase->gridViews().size() > 1 )
{
RimIntersectionCollection* selColl = selectedIntersectionCollection();
std::vector<RimExtrudedCurveIntersection*> selIntersections = selectedIntersections();
std::vector<RimBoxIntersection*> selIntersectionBoxes = selectedIntersectionBoxes();
RimIntersectionCollection* selColl = selectedIntersectionCollection();
if ( selColl )
{
@@ -203,12 +230,8 @@ SelectionComposition selectionComposition()
}
else
{
if ( !selIntersections.empty() && !selIntersectionBoxes.empty() )
return SEL_BOTH_INTERSECTION_TYPES;
else if ( !selIntersections.empty() )
return SEL_INTERSECTIONS;
else if ( !selIntersectionBoxes.empty() )
return SEL_INTERSECTION_BOXES;
if ( !selectedIntersections().empty() || !selectedIntersectionBoxes().empty() || !selectedIjkIntersections().empty() )
return SEL_INTERSECTION_ITEMS;
}
}
return SEL_INVALID;
@@ -26,6 +26,7 @@
class RimCase;
class RimExtrudedCurveIntersection;
class RimBoxIntersection;
class RimIjkIntersection;
class RimIntersectionCollection;
//==================================================================================================
@@ -42,4 +43,5 @@ protected:
void copyIntersectionsToOtherViews( RimCase& gridCase, std::vector<RimExtrudedCurveIntersection*> intersections );
void copyIntersectionBoxesToOtherViews( RimCase& gridCase, std::vector<RimBoxIntersection*> intersectionBoxes );
void copyIjkIntersectionsToOtherViews( RimCase& gridCase, std::vector<RimIjkIntersection*> ijkIntersections );
};
@@ -2,6 +2,8 @@ set(SOURCE_GROUP_SOURCE_FILES
${CMAKE_CURRENT_LIST_DIR}/RicIntersectionFeatureImpl.cpp
${CMAKE_CURRENT_LIST_DIR}/RicBoxManipulatorEventHandler.cpp
${CMAKE_CURRENT_LIST_DIR}/RicAppendIntersectionBoxFeature.cpp
${CMAKE_CURRENT_LIST_DIR}/RicAppendIjkIntersectionFeature.cpp
${CMAKE_CURRENT_LIST_DIR}/RicNewIjkIntersection3dviewFeature.cpp
${CMAKE_CURRENT_LIST_DIR}/RicIntersectionBoxXSliceFeature.cpp
${CMAKE_CURRENT_LIST_DIR}/RicIntersectionBoxYSliceFeature.cpp
${CMAKE_CURRENT_LIST_DIR}/RicIntersectionBoxZSliceFeature.cpp
@@ -0,0 +1,91 @@
/////////////////////////////////////////////////////////////////////////////////
//
// 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 "RicAppendIjkIntersectionFeature.h"
#include "RimEclipseView.h"
#include "RimGridView.h"
#include "RimIjkIntersection.h"
#include "RimIntersectionCollection.h"
#include "Riu3DMainWindowTools.h"
#include "cafSelectionManager.h"
#include <QAction>
CAF_CMD_SOURCE_INIT( RicAppendIjkIntersectionFeature, "RicAppendIjkIntersectionFeature" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RicAppendIjkIntersectionFeature::isCommandEnabled() const
{
RimIntersectionCollection* coll = RicAppendIjkIntersectionFeature::intersectionCollection();
// I/J/K intersections rely on a structured Eclipse grid
return coll != nullptr && coll->firstAncestorOrThisOfType<RimEclipseView>() != nullptr;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicAppendIjkIntersectionFeature::onActionTriggered( bool isChecked )
{
RimIntersectionCollection* coll = RicAppendIjkIntersectionFeature::intersectionCollection();
if ( coll )
{
RimIjkIntersection* intersection = new RimIjkIntersection();
intersection->setName( "Intersection I/J/K" );
// The default values are computed from the grid, which is resolved through the parent
// view, so the intersection must be added to the collection first
coll->appendIjkIntersectionNoUpdate( intersection );
intersection->setToDefaultValues();
coll->updateConnectedEditors();
Riu3DMainWindowTools::selectAsCurrentItem( intersection );
RimGridView* rimView = coll->firstAncestorOrThisOfTypeAsserted<RimGridView>();
rimView->scheduleCreateDisplayModelAndRedraw();
rimView->showGridCells( false );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicAppendIjkIntersectionFeature::setupActionLook( QAction* actionToSetup )
{
actionToSetup->setIcon( QIcon( ":/IntersectionBox16x16.png" ) );
actionToSetup->setText( "New Intersection I/J/K" );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimIntersectionCollection* RicAppendIjkIntersectionFeature::intersectionCollection()
{
if ( auto selectedObject = caf::SelectionManager::instance()->selectedItemOfType<caf::PdmObjectHandle>() )
{
return selectedObject->firstAncestorOrThisOfType<RimIntersectionCollection>();
}
return nullptr;
}
@@ -0,0 +1,39 @@
/////////////////////////////////////////////////////////////////////////////////
//
// 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.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "cafCmdFeature.h"
class RimIntersectionCollection;
//==================================================================================================
///
//==================================================================================================
class RicAppendIjkIntersectionFeature : public caf::CmdFeature
{
CAF_CMD_HEADER_INIT;
protected:
bool isCommandEnabled() const override;
void onActionTriggered( bool isChecked ) override;
void setupActionLook( QAction* actionToSetup ) override;
private:
static RimIntersectionCollection* intersectionCollection();
};
@@ -0,0 +1,86 @@
/////////////////////////////////////////////////////////////////////////////////
//
// 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 "RicNewIjkIntersection3dviewFeature.h"
#include "RiaApplication.h"
#include "RimEclipseView.h"
#include "RimIjkIntersection.h"
#include "RimIntersectionCollection.h"
#include "Riu3DMainWindowTools.h"
#include <QAction>
CAF_CMD_SOURCE_INIT( RicNewIjkIntersection3dviewFeature, "RicNewIjkIntersection3dviewFeature" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RicNewIjkIntersection3dviewFeature::isCommandEnabled() const
{
// I/J/K intersections rely on a structured Eclipse grid
return dynamic_cast<RimEclipseView*>( RiaApplication::instance()->activeMainOrComparisonGridView() ) != nullptr;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicNewIjkIntersection3dviewFeature::onActionTriggered( bool isChecked )
{
QVariant userData = this->userData();
if ( userData.isNull() || userData.type() != QVariant::List ) return;
// Axis and 0-based fixed index computed from the picked cell
QVariantList list = userData.toList();
CAF_ASSERT( list.size() == 2 );
auto axis = static_cast<RimIjkIntersection::GridAxis>( list[0].toInt() );
int fixedIndex = list[1].toInt();
auto* eclipseView = dynamic_cast<RimEclipseView*>( RiaApplication::instance()->activeMainOrComparisonGridView() );
if ( !eclipseView ) return;
RimIntersectionCollection* coll = eclipseView->intersectionCollection();
if ( !coll ) return;
RimIjkIntersection* intersection = new RimIjkIntersection();
intersection->setName( "Intersection I/J/K" );
// The default values are computed from the grid, which is resolved through the parent
// view, so the intersection must be added to the collection first
coll->appendIjkIntersectionNoUpdate( intersection );
intersection->setToDefaultValues();
intersection->setAxis( axis );
intersection->setFixedIndex( fixedIndex );
coll->updateConnectedEditors();
Riu3DMainWindowTools::selectAsCurrentItem( intersection );
eclipseView->scheduleCreateDisplayModelAndRedraw();
eclipseView->showGridCells( false );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RicNewIjkIntersection3dviewFeature::setupActionLook( QAction* actionToSetup )
{
actionToSetup->setIcon( QIcon( ":/IntersectionBox16x16.png" ) );
}
@@ -0,0 +1,34 @@
/////////////////////////////////////////////////////////////////////////////////
//
// 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.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "cafCmdFeature.h"
//==================================================================================================
///
//==================================================================================================
class RicNewIjkIntersection3dviewFeature : public caf::CmdFeature
{
CAF_CMD_HEADER_INIT;
protected:
bool isCommandEnabled() const override;
void onActionTriggered( bool isChecked ) override;
void setupActionLook( QAction* actionToSetup ) override;
};
@@ -29,6 +29,7 @@
#include "RimGeoMechView.h"
#include "RimGridCrossPlotCurve.h"
#include "RimGridCrossPlotDataSet.h"
#include "RimIjkIntersection.h"
#include "RimMimeData.h"
#include "RimModeledWellPath.h"
#include "RimSummaryMultiPlot.h"
@@ -125,6 +126,7 @@ bool RicCopyReferencesToClipboardFeature::isCopyOfObjectSupported( caf::PdmObjec
if ( dynamic_cast<RimEclipseCase*>( pdmObject ) ) return true;
if ( dynamic_cast<RimExtrudedCurveIntersection*>( pdmObject ) ) return true;
if ( dynamic_cast<RimBoxIntersection*>( pdmObject ) ) return true;
if ( dynamic_cast<RimIjkIntersection*>( pdmObject ) ) return true;
if ( dynamic_cast<RimSummaryPlot*>( pdmObject ) ) return true;
if ( dynamic_cast<RimFractureTemplate*>( pdmObject ) ) return true;
if ( dynamic_cast<RimEnsembleCurveSet*>( pdmObject ) ) return true;
@@ -22,6 +22,7 @@
#include "RimBoxIntersection.h"
#include "RimExtrudedCurveIntersection.h"
#include "RimIjkIntersection.h"
#include "RimIntersectionCollection.h"
#include "RiuMainWindow.h"
@@ -47,7 +48,10 @@ bool RicPasteIntersectionsFeature::isCommandEnabled() const
std::vector<caf::PdmPointer<RimBoxIntersection>> intersectionBoxObjects;
objectGroup.objectsByType( &intersectionBoxObjects );
if ( intersectionObjects.empty() && intersectionBoxObjects.empty() )
std::vector<caf::PdmPointer<RimIjkIntersection>> ijkIntersectionObjects;
objectGroup.objectsByType( &ijkIntersectionObjects );
if ( intersectionObjects.empty() && intersectionBoxObjects.empty() && ijkIntersectionObjects.empty() )
{
return false;
}
@@ -110,6 +114,25 @@ void RicPasteIntersectionsFeature::onActionTriggered( bool isChecked )
intersectionCollection->appendIntersectionBoxNoUpdate( intersectionBox );
}
}
std::vector<caf::PdmPointer<RimIjkIntersection>> ijkIntersectionObjects;
objectGroup.objectsByType( &ijkIntersectionObjects );
for ( size_t i = 0; i < ijkIntersectionObjects.size(); i++ )
{
RimIjkIntersection* ijkIntersection = ijkIntersectionObjects[i]->copyObject<RimIjkIntersection>();
QString nameOfCopy = QString( "Copy of " ) + ijkIntersection->name();
ijkIntersection->setName( nameOfCopy );
if ( i == ijkIntersectionObjects.size() - 1 )
{
intersectionCollection->appendIjkIntersectionAndUpdate( ijkIntersection );
}
else
{
intersectionCollection->appendIjkIntersectionNoUpdate( ijkIntersection );
}
}
}
//--------------------------------------------------------------------------------------------------
@@ -145,5 +168,11 @@ RimIntersectionCollection* RicPasteIntersectionsFeature::findIntersectionCollect
return intersectionBox->firstAncestorOrThisOfType<RimIntersectionCollection>();
}
RimIjkIntersection* ijkIntersection = dynamic_cast<RimIjkIntersection*>( objectHandle );
if ( ijkIntersection )
{
return ijkIntersection->firstAncestorOrThisOfType<RimIntersectionCollection>();
}
return nullptr;
}
@@ -6,6 +6,9 @@ set(SOURCE_GROUP_SOURCE_FILES
${CMAKE_CURRENT_LIST_DIR}/RivBoxIntersectionGeometryGenerator.cpp
${CMAKE_CURRENT_LIST_DIR}/RivBoxIntersectionPartMgr.cpp
${CMAKE_CURRENT_LIST_DIR}/RivBoxIntersectionSourceInfo.cpp
${CMAKE_CURRENT_LIST_DIR}/RivIjkIntersectionGeometryGenerator.cpp
${CMAKE_CURRENT_LIST_DIR}/RivIjkIntersectionPartMgr.cpp
${CMAKE_CURRENT_LIST_DIR}/RivIjkIntersectionSourceInfo.cpp
${CMAKE_CURRENT_LIST_DIR}/RivSectionFlattener.cpp
${CMAKE_CURRENT_LIST_DIR}/RivEclipseIntersectionGrid.cpp
${CMAKE_CURRENT_LIST_DIR}/RivFemIntersectionGrid.cpp
@@ -0,0 +1,243 @@
/////////////////////////////////////////////////////////////////////////////////
//
// 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 "RivIjkIntersectionGeometryGenerator.h"
#include "RivIntersectionHexGridInterface.h"
#include "RigBoundingBoxIjk.h"
#include "RigMainGrid.h"
#include "RimIjkIntersection.h"
#include "cafVecIjk.h"
#include "cvfDrawableGeo.h"
#include "cvfPrimitiveSetDirect.h"
#include "cvfStructGrid.h"
#include <algorithm>
#include <array>
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RivIjkIntersectionGeometryGenerator::RivIjkIntersectionGeometryGenerator( RimIjkIntersection* intersection,
const RivIntersectionHexGridInterface* grid,
const RigMainGrid* mainGrid )
: m_hexGrid( grid )
, m_mainGrid( mainGrid )
, m_intersectionDefinition( intersection )
{
m_triangleVxes = new cvf::Vec3fArray;
m_cellBorderLineVxes = new cvf::Vec3fArray;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RivIjkIntersectionGeometryGenerator::~RivIjkIntersectionGeometryGenerator()
{
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RivIjkIntersectionGeometryGenerator::isAnyGeometryPresent() const
{
return m_triangleVxes->size() != 0;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
cvf::ref<cvf::DrawableGeo> RivIjkIntersectionGeometryGenerator::generateSurface( cvf::UByteArray* visibleCells )
{
calculateArrays( visibleCells );
CVF_ASSERT( m_triangleVxes.notNull() );
if ( m_triangleVxes->size() == 0 ) return nullptr;
cvf::ref<cvf::DrawableGeo> geo = new cvf::DrawableGeo;
geo->setFromTriangleVertexArray( m_triangleVxes.p() );
return geo;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
cvf::ref<cvf::DrawableGeo> RivIjkIntersectionGeometryGenerator::createMeshDrawable()
{
if ( !m_cellBorderLineVxes.notNull() || m_cellBorderLineVxes->size() == 0 ) return nullptr;
cvf::ref<cvf::DrawableGeo> geo = new cvf::DrawableGeo;
geo->setVertexArray( m_cellBorderLineVxes.p() );
cvf::ref<cvf::PrimitiveSetDirect> prim = new cvf::PrimitiveSetDirect( cvf::PT_LINES );
prim->setIndexCount( m_cellBorderLineVxes->size() );
geo->addPrimitiveSet( prim.p() );
return geo;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
const std::vector<size_t>& RivIjkIntersectionGeometryGenerator::triangleToCellIndex() const
{
CVF_ASSERT( m_triangleVxes->size() );
return m_triangleToCellIdxMap;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
const std::vector<RivIntersectionVertexWeights>& RivIjkIntersectionGeometryGenerator::triangleVxToCellCornerInterpolationWeights() const
{
CVF_ASSERT( m_triangleVxes->size() );
return m_triVxToCellCornerWeights;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
const cvf::Vec3fArray* RivIjkIntersectionGeometryGenerator::triangleVxes() const
{
CVF_ASSERT( m_triangleVxes->size() );
return m_triangleVxes.p();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimIjkIntersection* RivIjkIntersectionGeometryGenerator::intersection() const
{
return m_intersectionDefinition;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RivIjkIntersectionGeometryGenerator::calculateArrays( cvf::UByteArray* visibleCells )
{
if ( m_triangleVxes->size() ) return;
if ( m_mainGrid.isNull() || m_hexGrid.isNull() ) return;
using FaceType = cvf::StructGridInterface::FaceType;
const size_t ni = m_mainGrid->cellCountI();
const size_t nj = m_mainGrid->cellCountJ();
const size_t nk = m_mainGrid->cellCountK();
if ( ni == 0 || nj == 0 || nk == 0 ) return;
// Determine which cell face the surface follows and the i/j/k loop bounds
RimIjkIntersection::GridAxis axis = m_intersectionDefinition->axis();
bool neg = m_intersectionDefinition->useNegativeFace();
const RigBoundingBoxIjk<caf::VecIjk0> range = m_intersectionDefinition->ijkRange();
// Snap the fixed index into the grid so a stale project file still renders the border slice
const size_t fixed = static_cast<size_t>( std::max( 0, m_intersectionDefinition->fixedIndex() ) );
// Pick the cell face the surface follows and collapse the fixed axis of the range to the fixed index.
// The i()/j()/k() accessors are read-only, so the assignments must use x()/y()/z()
auto computeFaceAndRange = [&]() -> std::pair<FaceType, RigBoundingBoxIjk<caf::VecIjk0>>
{
caf::VecIjk0 min = range.min();
caf::VecIjk0 max = range.max();
switch ( axis )
{
case RimIjkIntersection::GridAxis::AXIS_I:
min.x() = max.x() = std::min( fixed, ni - 1 );
return { neg ? FaceType::NEG_I : FaceType::POS_I, { min, max } };
case RimIjkIntersection::GridAxis::AXIS_J:
min.y() = max.y() = std::min( fixed, nj - 1 );
return { neg ? FaceType::NEG_J : FaceType::POS_J, { min, max } };
case RimIjkIntersection::GridAxis::AXIS_K:
default:
min.z() = max.z() = std::min( fixed, nk - 1 );
return { neg ? FaceType::NEG_K : FaceType::POS_K, { min, max } };
}
};
const auto [face, requestedRange] = computeFaceAndRange();
const RigBoundingBoxIjk<caf::VecIjk0> gridBounds( caf::VecIjk0::ZERO, caf::VecIjk0( ni - 1, nj - 1, nk - 1 ) );
const auto visibleRange = requestedRange.clamp( gridBounds );
if ( !visibleRange ) return;
cvf::ubyte faceVtxIdx[4];
cvf::StructGridInterface::cellFaceVertexIndices( face, faceVtxIdx );
cvf::Vec3d displayOffset = m_hexGrid->displayOffset();
std::vector<cvf::Vec3f> triangleVertices;
std::vector<cvf::Vec3f> cellBorderLineVxes;
for ( size_t k = visibleRange->min().k(); k <= visibleRange->max().k(); ++k )
{
for ( size_t j = visibleRange->min().j(); j <= visibleRange->max().j(); ++j )
{
for ( size_t i = visibleRange->min().i(); i <= visibleRange->max().i(); ++i )
{
size_t globalCellIdx = m_mainGrid->cellIndexFromIJK( i, j, k );
if ( ( visibleCells != nullptr ) && ( ( *visibleCells )[globalCellIdx] == 0 ) ) continue;
if ( !m_hexGrid->useCell( globalCellIdx ) ) continue;
const std::array<cvf::Vec3d, 8> cellCorners = m_hexGrid->cellCornerVertices( globalCellIdx );
const std::array<size_t, 8> cornerIndices = m_hexGrid->cellCornerIndices( globalCellIdx );
cvf::Vec3f faceVx[4];
for ( int n = 0; n < 4; ++n )
{
faceVx[n] = cvf::Vec3f( cellCorners[faceVtxIdx[n]] - displayOffset );
}
// Two triangles: (0, 1, 2) and (0, 2, 3)
const int triVtxOrder[6] = { 0, 1, 2, 0, 2, 3 };
for ( int n = 0; n < 6; ++n )
{
int corner = triVtxOrder[n];
triangleVertices.push_back( faceVx[corner] );
size_t nodeId = cornerIndices[faceVtxIdx[corner]];
m_triVxToCellCornerWeights.push_back( RivIntersectionVertexWeights( nodeId, nodeId, 0.0 ) );
}
m_triangleToCellIdxMap.push_back( globalCellIdx );
m_triangleToCellIdxMap.push_back( globalCellIdx );
// Mesh border lines: the four face edges
for ( int n = 0; n < 4; ++n )
{
cellBorderLineVxes.push_back( faceVx[n] );
cellBorderLineVxes.push_back( faceVx[( n + 1 ) % 4] );
}
}
}
}
m_cellBorderLineVxes->assign( cellBorderLineVxes );
m_triangleVxes->assign( triangleVertices );
}
@@ -0,0 +1,79 @@
/////////////////////////////////////////////////////////////////////////////////
//
// 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.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "RivIntersectionGeometryGeneratorInterface.h"
#include "cvfArray.h"
#include "cvfObject.h"
#include "cvfVector3.h"
#include <vector>
class RimIjkIntersection;
class RigMainGrid;
class RivIntersectionHexGridInterface;
namespace cvf
{
class DrawableGeo;
} // namespace cvf
//==================================================================================================
/// Generates the geometry for an intersection following the grid pillars at a fixed i/j/k index.
/// The surface is the union of one cell face of every cell in the index plane, so the triangle
/// vertices are native grid corner nodes.
//==================================================================================================
class RivIjkIntersectionGeometryGenerator : public cvf::Object, public RivIntersectionGeometryGeneratorInterface
{
public:
RivIjkIntersectionGeometryGenerator( RimIjkIntersection* intersection,
const RivIntersectionHexGridInterface* grid,
const RigMainGrid* mainGrid );
~RivIjkIntersectionGeometryGenerator() override;
// Generate geometry
cvf::ref<cvf::DrawableGeo> generateSurface( cvf::UByteArray* visibleCells );
cvf::ref<cvf::DrawableGeo> createMeshDrawable();
RimIjkIntersection* intersection() const;
// GeomGen Interface
bool isAnyGeometryPresent() const override;
const std::vector<size_t>& triangleToCellIndex() const override;
const std::vector<RivIntersectionVertexWeights>& triangleVxToCellCornerInterpolationWeights() const override;
const cvf::Vec3fArray* triangleVxes() const override;
private:
void calculateArrays( cvf::UByteArray* visibleCells );
cvf::cref<RivIntersectionHexGridInterface> m_hexGrid;
cvf::cref<RigMainGrid> m_mainGrid;
// Output arrays
cvf::ref<cvf::Vec3fArray> m_triangleVxes;
cvf::ref<cvf::Vec3fArray> m_cellBorderLineVxes;
std::vector<size_t> m_triangleToCellIdxMap;
std::vector<RivIntersectionVertexWeights> m_triVxToCellCornerWeights;
RimIjkIntersection* m_intersectionDefinition;
};
@@ -0,0 +1,208 @@
/////////////////////////////////////////////////////////////////////////////////
//
// 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 "RivIjkIntersectionPartMgr.h"
#include "Rim3dView.h"
#include "RimEclipseView.h"
#include "RimIjkIntersection.h"
#include "RivEclipseIntersectionGrid.h"
#include "RivIjkIntersectionSourceInfo.h"
#include "RivIntersectionGeometryGeneratorInterface.h"
#include "RivIntersectionHexGridInterface.h"
#include "RivIntersectionResultsColoringTools.h"
#include "RivMeshLinesSourceInfo.h"
#include "RivPartPriority.h"
#include "RivScalarMapperUtils.h"
#include "cafEffectGenerator.h"
#include "cvfDrawableGeo.h"
#include "cvfModelBasicList.h"
#include "cvfPart.h"
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RivIjkIntersectionPartMgr::RivIjkIntersectionPartMgr( RimIjkIntersection* intersection )
: m_rimIntersection( intersection )
, m_defaultColor( cvf::Color3::WHITE )
{
CVF_ASSERT( m_rimIntersection );
m_intersectionFacesTextureCoords = new cvf::Vec2fArray;
// The IJK indices refer to the view's main grid, so the hex grid must be built from the same grid as mainGrid().
// RimIntersection::createHexGridInterface() is not used here, as it returns the grid of a different case when a
// separate result definition is active.
cvf::ref<RivIntersectionHexGridInterface> hexGrid;
auto eclipseView = intersection->firstAncestorOrThisOfType<RimEclipseView>();
if ( eclipseView && eclipseView->mainGrid() )
{
hexGrid = new RivEclipseIntersectionGrid( eclipseView->mainGrid(),
eclipseView->currentActiveCellInfo(),
intersection->isInactiveCellsVisible() );
}
m_intersectionGenerator = new RivIjkIntersectionGeometryGenerator( m_rimIntersection, hexGrid.p(), intersection->mainGrid() );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RivIjkIntersectionPartMgr::applySingleColorEffect()
{
m_defaultColor = cvf::Color3f::OLIVE;
updatePartEffect();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RivIjkIntersectionPartMgr::updateCellResultColor( int timeStepIndex )
{
RivIntersectionResultsColoringTools::calculateIntersectionResultColors( timeStepIndex,
true,
m_rimIntersection,
m_intersectionGenerator.p(),
nullptr,
nullptr,
m_intersectionFaces.p(),
m_intersectionFacesTextureCoords.p() );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RivIjkIntersectionPartMgr::generatePartGeometry( cvf::UByteArray* visibleCells )
{
bool useBufferObjects = true;
// Surface geometry
{
cvf::ref<cvf::DrawableGeo> geo = m_intersectionGenerator->generateSurface( visibleCells );
if ( geo.notNull() )
{
geo->computeNormals();
if ( useBufferObjects )
{
geo->setRenderMode( cvf::DrawableGeo::BUFFER_OBJECT );
}
cvf::ref<cvf::Part> part = new cvf::Part;
part->setName( "Intersection IJK" );
part->setDrawable( geo.p() );
// Set mapping from triangle face index to cell index
cvf::ref<RivIjkIntersectionSourceInfo> si = new RivIjkIntersectionSourceInfo( m_intersectionGenerator.p() );
part->setSourceInfo( si.p() );
part->updateBoundingBox();
part->setEnableMask( intersectionCellFaceBit );
part->setPriority( RivPartPriority::PartType::Intersection );
m_intersectionFaces = part;
}
}
// Mesh geometry
{
cvf::ref<cvf::DrawableGeo> geoMesh = m_intersectionGenerator->createMeshDrawable();
if ( geoMesh.notNull() )
{
if ( useBufferObjects )
{
geoMesh->setRenderMode( cvf::DrawableGeo::BUFFER_OBJECT );
}
cvf::ref<cvf::Part> part = new cvf::Part;
part->setName( "Intersection IJK mesh" );
part->setDrawable( geoMesh.p() );
part->updateBoundingBox();
part->setEnableMask( intersectionCellMeshBit );
part->setPriority( RivPartPriority::PartType::MeshLines );
part->setSourceInfo( new RivMeshLinesSourceInfo( m_rimIntersection ) );
m_intersectionGridLines = part;
}
}
updatePartEffect();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RivIjkIntersectionPartMgr::updatePartEffect()
{
caf::SurfaceEffectGenerator geometryEffgen( m_defaultColor, caf::PO_1 );
cvf::ref<cvf::Effect> geometryOnlyEffect = geometryEffgen.generateCachedEffect();
if ( m_intersectionFaces.notNull() )
{
m_intersectionFaces->setEffect( geometryOnlyEffect.p() );
}
cvf::ref<cvf::Effect> eff;
caf::MeshEffectGenerator meshEffGen( cvf::Color3::WHITE );
eff = meshEffGen.generateCachedEffect();
if ( m_intersectionGridLines.notNull() )
{
m_intersectionGridLines->setEffect( eff.p() );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RivIjkIntersectionPartMgr::appendNativeIntersectionFacesToModel( cvf::ModelBasicList* model, cvf::Transform* scaleTransform )
{
if ( m_intersectionFaces.notNull() )
{
m_intersectionFaces->setTransform( scaleTransform );
model->addPart( m_intersectionFaces.p() );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RivIjkIntersectionPartMgr::appendMeshLinePartsToModel( cvf::ModelBasicList* model, cvf::Transform* scaleTransform )
{
if ( m_intersectionGridLines.notNull() )
{
m_intersectionGridLines->setTransform( scaleTransform );
model->addPart( m_intersectionGridLines.p() );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
const RivIntersectionGeometryGeneratorInterface* RivIjkIntersectionPartMgr::intersectionGeometryGenerator() const
{
if ( m_intersectionGenerator.notNull() ) return m_intersectionGenerator.p();
return nullptr;
}
@@ -0,0 +1,67 @@
/////////////////////////////////////////////////////////////////////////////////
//
// 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.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "RivIjkIntersectionGeometryGenerator.h"
#include "cvfObject.h"
namespace cvf
{
class ModelBasicList;
class Transform;
class Part;
} // namespace cvf
class RimIjkIntersection;
class RivIntersectionGeometryGeneratorInterface;
//==================================================================================================
///
///
//==================================================================================================
class RivIjkIntersectionPartMgr : public cvf::Object
{
public:
explicit RivIjkIntersectionPartMgr( RimIjkIntersection* intersection );
void applySingleColorEffect();
void updateCellResultColor( int timeStepIndex );
void appendNativeIntersectionFacesToModel( cvf::ModelBasicList* model, cvf::Transform* scaleTransform );
void appendMeshLinePartsToModel( cvf::ModelBasicList* model, cvf::Transform* scaleTransform );
const RivIntersectionGeometryGeneratorInterface* intersectionGeometryGenerator() const;
void generatePartGeometry( cvf::UByteArray* visibleCells );
private:
void updatePartEffect();
private:
RimIjkIntersection* m_rimIntersection;
cvf::Color3f m_defaultColor;
cvf::ref<cvf::Part> m_intersectionFaces;
cvf::ref<cvf::Part> m_intersectionGridLines;
cvf::ref<cvf::Vec2fArray> m_intersectionFacesTextureCoords;
cvf::ref<RivIjkIntersectionGeometryGenerator> m_intersectionGenerator;
};
@@ -0,0 +1,61 @@
/////////////////////////////////////////////////////////////////////////////////
//
// 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 "RivIjkIntersectionSourceInfo.h"
#include "RivIjkIntersectionGeometryGenerator.h"
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RivIjkIntersectionSourceInfo::RivIjkIntersectionSourceInfo( RivIjkIntersectionGeometryGenerator* geometryGenerator )
: m_intersectionGeometryGenerator( geometryGenerator )
{
CVF_ASSERT( m_intersectionGeometryGenerator.notNull() );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
const std::vector<size_t>& RivIjkIntersectionSourceInfo::triangleToCellIndex() const
{
CVF_ASSERT( m_intersectionGeometryGenerator.notNull() );
return m_intersectionGeometryGenerator->triangleToCellIndex();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::array<cvf::Vec3f, 3> RivIjkIntersectionSourceInfo::triangle( int triangleIdx ) const
{
std::array<cvf::Vec3f, 3> tri;
tri[0] = ( *m_intersectionGeometryGenerator->triangleVxes() )[triangleIdx * 3];
tri[1] = ( *m_intersectionGeometryGenerator->triangleVxes() )[triangleIdx * 3 + 1];
tri[2] = ( *m_intersectionGeometryGenerator->triangleVxes() )[triangleIdx * 3 + 2];
return tri;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimIjkIntersection* RivIjkIntersectionSourceInfo::intersection() const
{
return m_intersectionGeometryGenerator->intersection();
}
@@ -0,0 +1,40 @@
/////////////////////////////////////////////////////////////////////////////////
//
// 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.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "cvfArray.h"
#include "cvfObject.h"
#include <array>
class RivIjkIntersectionGeometryGenerator;
class RimIjkIntersection;
class RivIjkIntersectionSourceInfo : public cvf::Object
{
public:
explicit RivIjkIntersectionSourceInfo( RivIjkIntersectionGeometryGenerator* geometryGenerator );
const std::vector<size_t>& triangleToCellIndex() const;
std::array<cvf::Vec3f, 3> triangle( int triangleIdx ) const;
RimIjkIntersection* intersection() const;
private:
cvf::cref<RivIjkIntersectionGeometryGenerator> m_intersectionGeometryGenerator;
};
@@ -5,6 +5,7 @@ set(SOURCE_GROUP_SOURCE_FILES
${CMAKE_CURRENT_LIST_DIR}/RimIntersectionResultDefinition.cpp
${CMAKE_CURRENT_LIST_DIR}/RimIntersectionResultsDefinitionCollection.cpp
${CMAKE_CURRENT_LIST_DIR}/RimBoxIntersection.cpp
${CMAKE_CURRENT_LIST_DIR}/RimIjkIntersection.cpp
${CMAKE_CURRENT_LIST_DIR}/RimIntersectionEnums.cpp
)
@@ -0,0 +1,404 @@
/////////////////////////////////////////////////////////////////////////////////
//
// 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 "cafPdmUiSliderEditor.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_K );
}
} // 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_K;
m_iMin = 1;
m_iMax = ni;
m_jMin = 1;
m_jMax = nj;
m_kMin = 1;
m_kMax = nk;
m_fixedIndex = nk / 2 + 1;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
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::defineEditorAttribute( const caf::PdmFieldHandle* field, QString uiConfigName, caf::PdmUiEditorAttribute* attribute )
{
auto* sliderAttr = dynamic_cast<caf::PdmUiSliderEditorAttribute*>( attribute );
if ( !sliderAttr ) return;
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() );
if ( field == &m_fixedIndex )
{
sliderAttr->m_minimum = 1;
sliderAttr->m_maximum = std::max( 1, axisCellCount() );
}
else if ( field == &m_iMin || field == &m_iMax )
{
sliderAttr->m_minimum = 1;
sliderAttr->m_maximum = std::max( 1, ni );
}
else if ( field == &m_jMin || field == &m_jMax )
{
sliderAttr->m_minimum = 1;
sliderAttr->m_maximum = std::max( 1, nj );
}
else if ( field == &m_kMin || field == &m_kMax )
{
sliderAttr->m_minimum = 1;
sliderAttr->m_maximum = std::max( 1, nk );
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
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 );
uiOrdering.skipRemainingFields( 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();
}
}
@@ -0,0 +1,103 @@
/////////////////////////////////////////////////////////////////////////////////
//
// 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.
//
/////////////////////////////////////////////////////////////////////////////////
#pragma once
#include "RigBoundingBoxIjk.h"
#include "RimIntersection.h"
#include "cafAppEnum.h"
#include "cafPdmField.h"
#include "cafVecIjk.h"
class RigMainGrid;
class RivIjkIntersectionPartMgr;
//==================================================================================================
///
/// Intersection following the grid pillars at a fixed i, j or k index. Unlike the axis-aligned
/// intersection box, the surface follows the cell faces and therefore steps along faults and
/// curved pillars.
//==================================================================================================
class RimIjkIntersection : public RimIntersection
{
CAF_PDM_HEADER_INIT;
public:
enum class GridAxis
{
AXIS_I,
AXIS_J,
AXIS_K
};
public:
RimIjkIntersection();
~RimIjkIntersection() override;
QString name() const override;
void setName( const QString& newName );
GridAxis axis() const;
bool useNegativeFace() const;
// Index accessors and setters use 0-based grid indices; the PDM fields and UI are 1-based
int fixedIndex() const;
RigBoundingBoxIjk<caf::VecIjk0> ijkRange() const;
void setAxis( GridAxis axis );
void setUseNegativeFace( bool useNegativeFace );
void setFixedIndex( int fixedIndex );
void setIjkRange( const RigBoundingBoxIjk<caf::VecIjk0>& range );
void setToDefaultValues();
RivIjkIntersectionPartMgr* intersectionPartMgr();
void clearGeometry();
RigMainGrid* mainGrid() const;
const RivIntersectionGeometryGeneratorInterface* intersectionGeometryGenerator() const override;
protected:
caf::PdmFieldHandle* userDescriptionField() final;
void defineEditorAttribute( const caf::PdmFieldHandle* field, QString uiConfigName, caf::PdmUiEditorAttribute* attribute ) override;
void fieldChangedByUi( const caf::PdmFieldHandle* changedField, const QVariant& oldValue, const QVariant& newValue ) override;
void defineUiOrdering( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ) override;
private:
int axisCellCount() const;
void rebuildGeometryAndScheduleCreateDisplayModel();
private:
caf::PdmField<QString> m_name;
caf::PdmField<caf::AppEnum<GridAxis>> m_axis;
caf::PdmField<bool> m_useNegativeFace;
caf::PdmField<int> m_fixedIndex;
caf::PdmField<int> m_iMin;
caf::PdmField<int> m_iMax;
caf::PdmField<int> m_jMin;
caf::PdmField<int> m_jMax;
caf::PdmField<int> m_kMin;
caf::PdmField<int> m_kMax;
cvf::ref<RivIjkIntersectionPartMgr> m_intersectionPartMgr;
};
@@ -29,6 +29,7 @@
#include "RimEclipseView.h"
#include "RimExtrudedCurveIntersection.h"
#include "RimGridView.h"
#include "RimIjkIntersection.h"
#include "RimIntersectionResultDefinition.h"
#include "RimIntersectionResultsDefinitionCollection.h"
#include "RimProject.h"
@@ -38,6 +39,7 @@
#include "RivBoxIntersectionPartMgr.h"
#include "RivExtrudedCurveIntersectionPartMgr.h"
#include "RivIjkIntersectionPartMgr.h"
#include "cafPdmObjectScriptingCapability.h"
#include "cafPdmUiCheckBoxEditor.h"
@@ -59,6 +61,8 @@ RimIntersectionCollection::RimIntersectionCollection()
CAF_PDM_InitFieldNoDefault( &m_intersectionBoxes, "IntersectionBoxes", "IntersectionBoxes" );
CAF_PDM_InitFieldNoDefault( &m_ijkIntersections, "IjkIntersections", "IjkIntersections" );
CAF_PDM_InitField( &m_isActive, "Active", true, "Active" );
m_isActive.uiCapability()->setUiHidden( true );
@@ -139,6 +143,14 @@ void RimIntersectionCollection::applySingleColorEffect()
cs->intersectionBoxPartMgr()->applySingleColorEffect();
}
}
for ( RimIjkIntersection* cs : m_ijkIntersections )
{
if ( cs->isActive() )
{
cs->intersectionPartMgr()->applySingleColorEffect();
}
}
}
//--------------------------------------------------------------------------------------------------
@@ -180,6 +192,22 @@ void RimIntersectionCollection::updateCellResultColor( bool hasGeneralCellResult
}
}
}
for ( RimIjkIntersection* cs : m_ijkIntersections )
{
if ( cs->isActive() )
{
bool hasSeparateInterResult = cs->activeSeparateResultDefinition() && cs->activeSeparateResultDefinition()->hasResult();
if ( hasSeparateInterResult || hasGeneralCellResult )
{
cs->intersectionPartMgr()->updateCellResultColor( timeStepIndex );
}
else
{
cs->intersectionPartMgr()->applySingleColorEffect();
}
}
}
}
//--------------------------------------------------------------------------------------------------
@@ -204,6 +232,14 @@ bool RimIntersectionCollection::hasAnyActiveSeparateResults()
return true;
}
}
for ( RimIjkIntersection* cs : m_ijkIntersections )
{
if ( cs->isActive() && cs->activeSeparateResultDefinition() && cs->activeSeparateResultDefinition()->hasResult() )
{
return true;
}
}
return false;
}
@@ -260,6 +296,16 @@ void RimIntersectionCollection::appendDynamicPartsToModel( cvf::ModelBasicList*
cs->intersectionBoxPartMgr()->appendMeshLinePartsToModel( model, scaleTransform );
}
}
for ( RimIjkIntersection* cs : m_ijkIntersections )
{
if ( cs->isActive() )
{
cs->intersectionPartMgr()->generatePartGeometry( visibleCells );
cs->intersectionPartMgr()->appendNativeIntersectionFacesToModel( model, scaleTransform );
cs->intersectionPartMgr()->appendMeshLinePartsToModel( model, scaleTransform );
}
}
}
//--------------------------------------------------------------------------------------------------
@@ -276,6 +322,11 @@ void RimIntersectionCollection::clearGeometry()
{
intersectionBox->clearGeometry();
}
for ( RimIjkIntersection* ijkIntersection : m_ijkIntersections )
{
ijkIntersection->clearGeometry();
}
}
//--------------------------------------------------------------------------------------------------
@@ -294,6 +345,14 @@ std::vector<RimBoxIntersection*> RimIntersectionCollection::intersectionBoxes()
return m_intersectionBoxes.childrenByType();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<RimIjkIntersection*> RimIntersectionCollection::ijkIntersections() const
{
return m_ijkIntersections.childrenByType();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -366,6 +425,27 @@ void RimIntersectionCollection::appendIntersectionBoxNoUpdate( RimBoxIntersectio
m_intersectionBoxes.push_back( intersectionBox );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimIntersectionCollection::appendIjkIntersectionAndUpdate( RimIjkIntersection* intersection )
{
m_ijkIntersections.push_back( intersection );
updateConnectedEditors();
Riu3DMainWindowTools::selectAsCurrentItem( intersection, false );
rebuild3dView();
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimIntersectionCollection::appendIjkIntersectionNoUpdate( RimIjkIntersection* intersection )
{
m_ijkIntersections.push_back( intersection );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -31,6 +31,7 @@ class Rim3dView;
class RimEclipseView;
class RimExtrudedCurveIntersection;
class RimBoxIntersection;
class RimIjkIntersection;
class RimEclipseCellColors;
class RimSimWellInView;
class RivTernaryScalarMapper;
@@ -60,6 +61,9 @@ public:
void appendIntersectionBoxAndUpdate( RimBoxIntersection* intersectionBox );
void appendIntersectionBoxNoUpdate( RimBoxIntersection* intersectionBox );
void appendIjkIntersectionAndUpdate( RimIjkIntersection* intersection );
void appendIjkIntersectionNoUpdate( RimIjkIntersection* intersection );
bool hasActiveIntersectionForSimulationWell( const RimSimWellInView* simWell ) const;
bool hasAnyActiveSeparateResults();
@@ -83,6 +87,7 @@ public:
std::vector<RimExtrudedCurveIntersection*> intersections() const;
std::vector<RimBoxIntersection*> intersectionBoxes() const;
std::vector<RimIjkIntersection*> ijkIntersections() const;
void onChildDeleted( caf::PdmChildArrayFieldHandle* childArray, std::vector<caf::PdmObjectHandle*>& referringObjects ) override;
@@ -108,6 +113,7 @@ private:
caf::PdmChildArrayField<RimExtrudedCurveIntersection*> m_intersections;
caf::PdmChildArrayField<RimBoxIntersection*> m_intersectionBoxes;
caf::PdmChildArrayField<RimIjkIntersection*> m_ijkIntersections;
caf::PdmField<bool> m_depthThresholdOverridden;
caf::PdmField<double> m_depthUpperThreshold;
@@ -90,6 +90,7 @@
#include "RimGridCrossPlotCollection.h"
#include "RimGridCrossPlotDataSet.h"
#include "RimIdenticalGridCaseGroup.h"
#include "RimIjkIntersection.h"
#include "RimIntersectionCollection.h"
#include "RimIntersectionResultDefinition.h"
#include "RimIntersectionResultsDefinitionCollection.h"
@@ -769,6 +770,7 @@ caf::CmdFeatureMenuBuilder RimContextCommandBuilder::commandsFromSelection()
menuBuilder.addSeparator();
menuBuilder << "RicAppendIntersectionFeature";
menuBuilder << "RicAppendIntersectionBoxFeature";
menuBuilder << "RicAppendIjkIntersectionFeature";
menuBuilder.addSeparator();
menuBuilder << "RicCopyIntersectionsToAllViewsInCaseFeature";
}
@@ -781,6 +783,7 @@ caf::CmdFeatureMenuBuilder RimContextCommandBuilder::commandsFromSelection()
menuBuilder.addSeparator();
menuBuilder << "RicAppendIntersectionFeature";
menuBuilder << "RicAppendIntersectionBoxFeature";
menuBuilder << "RicAppendIjkIntersectionFeature";
menuBuilder.addSeparator();
menuBuilder << "RicSeismicSectionFromIntersectionFeature";
menuBuilder.addSeparator();
@@ -794,6 +797,17 @@ caf::CmdFeatureMenuBuilder RimContextCommandBuilder::commandsFromSelection()
menuBuilder.addSeparator();
menuBuilder << "RicAppendIntersectionFeature";
menuBuilder << "RicAppendIntersectionBoxFeature";
menuBuilder << "RicAppendIjkIntersectionFeature";
menuBuilder.addSeparator();
menuBuilder << "RicCopyIntersectionsToAllViewsInCaseFeature";
}
else if ( dynamic_cast<RimIjkIntersection*>( firstUiItem ) )
{
menuBuilder << "RicPasteIntersectionsFeature";
menuBuilder.addSeparator();
menuBuilder << "RicAppendIntersectionFeature";
menuBuilder << "RicAppendIntersectionBoxFeature";
menuBuilder << "RicAppendIjkIntersectionFeature";
menuBuilder.addSeparator();
menuBuilder << "RicCopyIntersectionsToAllViewsInCaseFeature";
}
@@ -30,6 +30,7 @@ set(SOURCE_UNITTEST_FILES
${CMAKE_CURRENT_LIST_DIR}/RigWellPathIntersectionTools-Test.cpp
${CMAKE_CURRENT_LIST_DIR}/RimWellLogExtractionCurveImpl-Test.cpp
${CMAKE_CURRENT_LIST_DIR}/RivPipeGeometryGenerator-Test.cpp
${CMAKE_CURRENT_LIST_DIR}/RivIjkIntersectionGeometryGenerator-Test.cpp
${CMAKE_CURRENT_LIST_DIR}/RivTernaryScalarMapper-Test.cpp
${CMAKE_CURRENT_LIST_DIR}/ScalarMapper-Test.cpp
${CMAKE_CURRENT_LIST_DIR}/WellPathAsciiFileReader-Test.cpp
@@ -0,0 +1,141 @@
/////////////////////////////////////////////////////////////////////////////////
//
// 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 "gtest/gtest.h"
#include "RigActiveCellInfo.h"
#include "RigEclipseCaseData.h"
#include "RigMainGrid.h"
#include "RigReservoirBuilder.h"
#include "RivEclipseIntersectionGrid.h"
#include "RivIjkIntersectionGeometryGenerator.h"
#include "RimIjkIntersection.h"
#include "cvfDrawableGeo.h"
#include "cvfVector3.h"
#include <cmath>
namespace
{
//--------------------------------------------------------------------------------------------------
/// Build a regular ni x nj x nk box grid in memory (no file, no view)
//--------------------------------------------------------------------------------------------------
cvf::ref<RigEclipseCaseData> buildBoxGrid( int ni, int nj, int nk )
{
RigReservoirBuilder builder;
builder.setIJKCount( cvf::Vec3st( ni, nj, nk ) );
builder.setWorldCoordinates( cvf::Vec3d( 0.0, 0.0, 0.0 ), cvf::Vec3d( ni, nj, -nk ) );
cvf::ref<RigEclipseCaseData> caseData = new RigEclipseCaseData( nullptr );
builder.createGridsAndCells( caseData.p() );
caseData->mainGrid()->computeCachedData();
return caseData;
}
} // namespace
//--------------------------------------------------------------------------------------------------
/// A full-range K slice produces one cell face (two triangles) per cell in the K layer
//--------------------------------------------------------------------------------------------------
TEST( RivIjkIntersectionGeometryGeneratorTest, FullRangeKSliceTriangleCount )
{
const int ni = 4;
const int nj = 3;
const int nk = 5;
cvf::ref<RigEclipseCaseData> caseData = buildBoxGrid( ni, nj, nk );
RigMainGrid* mainGrid = caseData->mainGrid();
cvf::ref<RivEclipseIntersectionGrid> hexGrid = new RivEclipseIntersectionGrid( mainGrid, nullptr, true );
RimIjkIntersection def;
def.setAxis( RimIjkIntersection::GridAxis::AXIS_K );
def.setFixedIndex( 2 );
def.setIjkRange( { caf::VecIjk0( 0, 0, 0 ), caf::VecIjk0( ni - 1, nj - 1, nk - 1 ) } );
cvf::ref<RivIjkIntersectionGeometryGenerator> generator = new RivIjkIntersectionGeometryGenerator( &def, hexGrid.p(), mainGrid );
generator->generateSurface( nullptr );
EXPECT_TRUE( generator->isAnyGeometryPresent() );
// 2 triangles per cell face, 3 vertices per triangle
EXPECT_EQ( static_cast<size_t>( 2 * ni * nj * 3 ), generator->triangleVxes()->size() );
EXPECT_EQ( static_cast<size_t>( 2 * ni * nj ), generator->triangleToCellIndex().size() );
EXPECT_EQ( generator->triangleVxes()->size(), generator->triangleVxToCellCornerInterpolationWeights().size() );
}
//--------------------------------------------------------------------------------------------------
/// All vertices of a K slice through a regular grid lie at the same depth
//--------------------------------------------------------------------------------------------------
TEST( RivIjkIntersectionGeometryGeneratorTest, KSliceVerticesAreCoplanar )
{
const int ni = 3;
const int nj = 3;
const int nk = 4;
cvf::ref<RigEclipseCaseData> caseData = buildBoxGrid( ni, nj, nk );
RigMainGrid* mainGrid = caseData->mainGrid();
cvf::ref<RivEclipseIntersectionGrid> hexGrid = new RivEclipseIntersectionGrid( mainGrid, nullptr, true );
RimIjkIntersection def;
def.setAxis( RimIjkIntersection::GridAxis::AXIS_K );
def.setFixedIndex( 1 );
def.setIjkRange( { caf::VecIjk0( 0, 0, 0 ), caf::VecIjk0( ni - 1, nj - 1, nk - 1 ) } );
cvf::ref<RivIjkIntersectionGeometryGenerator> generator = new RivIjkIntersectionGeometryGenerator( &def, hexGrid.p(), mainGrid );
generator->generateSurface( nullptr );
ASSERT_TRUE( generator->isAnyGeometryPresent() );
const cvf::Vec3fArray* vxes = generator->triangleVxes();
float z0 = ( *vxes )[0].z();
for ( size_t i = 0; i < vxes->size(); ++i )
{
EXPECT_NEAR( z0, ( *vxes )[i].z(), 1.0e-4f );
}
}
//--------------------------------------------------------------------------------------------------
/// A narrowed perpendicular range yields exactly the requested number of cell faces
//--------------------------------------------------------------------------------------------------
TEST( RivIjkIntersectionGeometryGeneratorTest, NarrowedRangeISlice )
{
const int ni = 5;
const int nj = 4;
const int nk = 6;
cvf::ref<RigEclipseCaseData> caseData = buildBoxGrid( ni, nj, nk );
RigMainGrid* mainGrid = caseData->mainGrid();
cvf::ref<RivEclipseIntersectionGrid> hexGrid = new RivEclipseIntersectionGrid( mainGrid, nullptr, true );
// Fixed I = 2, J in [1,1] (single column), K in [0,2] (3 layers) => 1 * 3 = 3 cell faces
RimIjkIntersection def;
def.setAxis( RimIjkIntersection::GridAxis::AXIS_I );
def.setFixedIndex( 2 );
def.setIjkRange( { caf::VecIjk0( 0, 1, 0 ), caf::VecIjk0( ni - 1, 1, 2 ) } );
cvf::ref<RivIjkIntersectionGeometryGenerator> generator = new RivIjkIntersectionGeometryGenerator( &def, hexGrid.p(), mainGrid );
generator->generateSurface( nullptr );
EXPECT_EQ( static_cast<size_t>( 2 * 1 * 3 ), generator->triangleToCellIndex().size() );
}
@@ -29,6 +29,7 @@
#include "RimGeoMechCase.h"
#include "RimGeoMechCellColors.h"
#include "RimGeoMechView.h"
#include "RimIjkIntersection.h"
#include "RimIntersectionResultDefinition.h"
#include "RimSurfaceInView.h"
@@ -39,6 +40,7 @@
#include "RivExtrudedCurveIntersectionSourceInfo.h"
#include "RivFemPartGeometryGenerator.h"
#include "RivFemPickSourceInfo.h"
#include "RivIjkIntersectionSourceInfo.h"
#include "RivReservoirSurfaceIntersectionSourceInfo.h"
#include "RivSourceInfo.h"
@@ -193,6 +195,19 @@ bool RiuCellAndNncPickEventHandler::handle3dPickEvent( const Ric3dPickEvent& eve
intersectionTriangleHit = intersectionBoxSourceInfo->triangle( firstPartTriangleIndex );
sepInterResDef = intersectionBoxSourceInfo->intersectionBox()->activeSeparateResultDefinition();
}
else if ( const RivIjkIntersectionSourceInfo* ijkIntersectionSourceInfo =
dynamic_cast<const RivIjkIntersectionSourceInfo*>( firstHitPart->sourceInfo() ) )
{
RiuViewerCommands::findCellAndGridIndex( mainOrComparisonView,
ijkIntersectionSourceInfo->intersection()->activeSeparateResultDefinition(),
ijkIntersectionSourceInfo->triangleToCellIndex()[firstPartTriangleIndex],
&gridLocalCellIndex,
&gridIndex );
intersectionHit = true;
intersectionTriangleHit = ijkIntersectionSourceInfo->triangle( firstPartTriangleIndex );
sepInterResDef = ijkIntersectionSourceInfo->intersection()->activeSeparateResultDefinition();
}
if ( sepInterResDef )
{
@@ -59,6 +59,7 @@
#include "RimGeoMechCellColors.h"
#include "RimGeoMechContourMapView.h"
#include "RimGeoMechView.h"
#include "RimIjkIntersection.h"
#include "RimIntersectionResultDefinition.h"
#include "RimLegendConfig.h"
#include "RimPerforationInterval.h"
@@ -85,6 +86,7 @@
#include "RivExtrudedCurveIntersectionSourceInfo.h"
#include "RivFemPartGeometryGenerator.h"
#include "RivFemPickSourceInfo.h"
#include "RivIjkIntersectionSourceInfo.h"
#include "RivObjectSourceInfo.h"
#include "RivPartPriority.h"
#include "RivReservoirSurfaceIntersectionSourceInfo.h"
@@ -284,8 +286,10 @@ void RiuViewerCommands::displayContextMenu( QMouseEvent* event )
const auto* surfIntersectSourceInfo = dynamic_cast<const RivReservoirSurfaceIntersectionSourceInfo*>( firstHitPart->sourceInfo() );
const auto* crossSectionSourceInfo = dynamic_cast<const RivExtrudedCurveIntersectionSourceInfo*>( firstHitPart->sourceInfo() );
const auto* intersectionBoxSourceInfo = dynamic_cast<const RivBoxIntersectionSourceInfo*>( firstHitPart->sourceInfo() );
const auto* ijkIntersectionSourceInfo = dynamic_cast<const RivIjkIntersectionSourceInfo*>( firstHitPart->sourceInfo() );
if ( rivSourceInfo || femSourceInfo || crossSectionSourceInfo || intersectionBoxSourceInfo || surfIntersectSourceInfo )
if ( rivSourceInfo || femSourceInfo || crossSectionSourceInfo || intersectionBoxSourceInfo || ijkIntersectionSourceInfo ||
surfIntersectSourceInfo )
{
if ( rivSourceInfo )
{
@@ -356,6 +360,19 @@ void RiuViewerCommands::displayContextMenu( QMouseEvent* event )
menuBuilder << "RicHideIntersectionBoxFeature";
menuBuilder.addSeparator();
}
else if ( ijkIntersectionSourceInfo )
{
findCellAndGridIndex( mainOrComparisonView,
ijkIntersectionSourceInfo->intersection()->activeSeparateResultDefinition(),
ijkIntersectionSourceInfo->triangleToCellIndex()[firstPartTriangleIndex],
&m_currentCellIndex,
&m_currentGridIdx );
m_currentFaceIndex = cvf::StructGridInterface::NO_FACE;
RiuSelectionItem* selItem = new RiuGeneralSelectionItem( ijkIntersectionSourceInfo->intersection() );
Riu3dSelectionManager::instance()->setSelectedItem( selItem, Riu3dSelectionManager::RUI_TEMPORARY );
}
if ( gridView )
{
@@ -419,6 +436,28 @@ void RiuViewerCommands::displayContextMenu( QMouseEvent* event )
menuBuilder << "RicIntersectionBoxXSliceFeature";
menuBuilder << "RicIntersectionBoxYSliceFeature";
menuBuilder << "RicIntersectionBoxZSliceFeature";
if ( auto eclipseView = dynamic_cast<RimEclipseView*>( mainOrComparisonView ) )
{
// I/J/K intersections follow the main grid, so map the picked cell to the main grid
const RigGridBase* grid = eclipseView->eclipseCase()->eclipseCaseData()->grid( m_currentGridIdx );
if ( grid && m_currentCellIndex < grid->cellCount() )
{
size_t mainGridCellIndex = grid->cell( m_currentCellIndex ).mainGridCellIndex();
size_t i, j, k;
if ( eclipseView->mainGrid()->ijkFromCellIndex( mainGridCellIndex, &i, &j, &k ) )
{
QVariantList iIntersectionList = { 0, static_cast<int>( i ) };
QVariantList jIntersectionList = { 1, static_cast<int>( j ) };
QVariantList kIntersectionList = { 2, static_cast<int>( k ) };
menuBuilder.addCmdFeatureWithUserData( "RicNewIjkIntersection3dviewFeature", "I Intersection", iIntersectionList );
menuBuilder.addCmdFeatureWithUserData( "RicNewIjkIntersection3dviewFeature", "J Intersection", jIntersectionList );
menuBuilder.addCmdFeatureWithUserData( "RicNewIjkIntersection3dviewFeature", "K Intersection", kIntersectionList );
}
}
}
}
menuBuilder.subMenuEnd();
@@ -885,6 +924,10 @@ void RiuViewerCommands::handlePickAction( int winPosX, int winPosY, Qt::Keyboard
{
RiuMainWindow::instance()->selectAsCurrentItem( intersectionBoxSourceInfo->intersectionBox() );
}
else if ( const auto* ijkIntersectionSourceInfo = dynamic_cast<const RivIjkIntersectionSourceInfo*>( firstHitPart->sourceInfo() ) )
{
RiuMainWindow::instance()->selectAsCurrentItem( ijkIntersectionSourceInfo->intersection() );
}
else if ( eclipseWellSourceInfo )
{
bool allowActiveViewChange = dynamic_cast<Rim2dIntersectionView*>( m_viewer->ownerViewWindow() ) == nullptr;