mirror of
https://github.com/OPM/ResInsight.git
synced 2026-11-02 09:00:14 -06:00
Sector Export: fix indexes in EQUALS keyword.
This commit is contained in:
1 parent
2bba6afe10
commit
eb02db6cc0
7 files changed
+511
No files matched your search
@@ -42,6 +42,7 @@
|
|||||||
#include "RimProject.h"
|
#include "RimProject.h"
|
||||||
|
|
||||||
#include "RigActiveCellInfo.h"
|
#include "RigActiveCellInfo.h"
|
||||||
|
#include "RigBoundingBoxIjk.h"
|
||||||
#include "RigEclipseCaseData.h"
|
#include "RigEclipseCaseData.h"
|
||||||
#include "RigEclipseCaseDataTools.h"
|
#include "RigEclipseCaseDataTools.h"
|
||||||
#include "RigEclipseResultTools.h"
|
#include "RigEclipseResultTools.h"
|
||||||
@@ -65,6 +66,7 @@
|
|||||||
#include "opm/input/eclipse/Deck/DeckRecord.hpp"
|
#include "opm/input/eclipse/Deck/DeckRecord.hpp"
|
||||||
#include "opm/input/eclipse/Parser/ParserKeyword.hpp"
|
#include "opm/input/eclipse/Parser/ParserKeyword.hpp"
|
||||||
#include "opm/input/eclipse/Parser/ParserKeywords/C.hpp"
|
#include "opm/input/eclipse/Parser/ParserKeywords/C.hpp"
|
||||||
|
#include "opm/input/eclipse/Parser/ParserKeywords/E.hpp"
|
||||||
#include "opm/input/eclipse/Parser/ParserKeywords/O.hpp"
|
#include "opm/input/eclipse/Parser/ParserKeywords/O.hpp"
|
||||||
#include "opm/input/eclipse/Parser/ParserKeywords/S.hpp"
|
#include "opm/input/eclipse/Parser/ParserKeywords/S.hpp"
|
||||||
#include "opm/input/eclipse/Parser/ParserKeywords/W.hpp"
|
#include "opm/input/eclipse/Parser/ParserKeywords/W.hpp"
|
||||||
@@ -342,6 +344,11 @@ std::expected<void, QString> RicExportEclipseSectorModelFeature::exportSimulatio
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( auto result = replaceEqualsKeywordIndices( &eclipseCase, exportSettings, deckFile ); !result )
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
if ( auto result = addBorderBoundaryConditions( &eclipseCase, exportSettings, deckFile ); !result )
|
if ( auto result = addBorderBoundaryConditions( &eclipseCase, exportSettings, deckFile ); !result )
|
||||||
{
|
{
|
||||||
return result;
|
return result;
|
||||||
@@ -566,6 +573,72 @@ std::expected<void, QString> RicExportEclipseSectorModelFeature::addBorderBounda
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::expected<void, QString> RicExportEclipseSectorModelFeature::replaceEqualsKeywordIndices( RimEclipseCase* eclipseCase,
|
||||||
|
const RicExportEclipseSectorModelUi& exportSettings,
|
||||||
|
RifOpmFlowDeckFile& deckFile )
|
||||||
|
{
|
||||||
|
auto keywords = deckFile.keywords( false );
|
||||||
|
|
||||||
|
for ( const auto& keyword : keywords )
|
||||||
|
{
|
||||||
|
// Handle EQUALS keyword specially - it has IJK coordinates that need transformation
|
||||||
|
if ( keyword == "EQUALS" )
|
||||||
|
{
|
||||||
|
auto equalsKeywordsWithIndices = deckFile.findAllKeywordsWithIndices( keyword );
|
||||||
|
if ( !equalsKeywordsWithIndices.empty() )
|
||||||
|
{
|
||||||
|
RiaLogging::info( QString( "Processing %1 occurrence(s) of EQUALS keyword" ).arg( equalsKeywordsWithIndices.size() ) );
|
||||||
|
|
||||||
|
// Process in reverse order so indices remain valid after modifications
|
||||||
|
for ( auto it = equalsKeywordsWithIndices.rbegin(); it != equalsKeywordsWithIndices.rend(); ++it )
|
||||||
|
{
|
||||||
|
const Opm::FileDeck::Index& index = it->first;
|
||||||
|
const Opm::DeckKeyword& kw = it->second;
|
||||||
|
|
||||||
|
// Create new keyword with transformed coordinates
|
||||||
|
Opm::DeckKeyword transformedKeyword( kw.location(), kw.name() );
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
for ( size_t recordIdx = 0; recordIdx < kw.size(); ++recordIdx )
|
||||||
|
{
|
||||||
|
const auto& record = kw.getRecord( recordIdx );
|
||||||
|
auto result = processEqualsRecord( record, exportSettings );
|
||||||
|
|
||||||
|
if ( result )
|
||||||
|
{
|
||||||
|
transformedKeyword.addRecord( std::move( result.value() ) );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RiaLogging::warning( QString( "Failed to process EQUALS record: %1" ).arg( result.error() ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace with transformed keyword
|
||||||
|
if ( transformedKeyword.size() > 0 )
|
||||||
|
{
|
||||||
|
RiaLogging::info( QString( "Got %1 keywords." ).arg( transformedKeyword.size() ) );
|
||||||
|
deckFile.replaceKeywordAtIndex( index, transformedKeyword );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If all records failed, remove the keyword
|
||||||
|
deckFile.replaceKeywordAtIndex( index, Opm::DeckKeyword( kw.location(), "SKIP" ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch ( std::exception& e )
|
||||||
|
{
|
||||||
|
return std::unexpected( QString( "Exception processing EQUALS keyword: %1" ).arg( e.what() ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------------------
|
||||||
///
|
///
|
||||||
//--------------------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------------------
|
||||||
@@ -987,6 +1060,106 @@ std::expected<Opm::DeckRecord, QString>
|
|||||||
return Opm::DeckRecord{ std::move( items ) };
|
return Opm::DeckRecord{ std::move( items ) };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
std::expected<Opm::DeckRecord, QString>
|
||||||
|
RicExportEclipseSectorModelFeature::processEqualsRecord( const Opm::DeckRecord& record, const RicExportEclipseSectorModelUi& exportSettings )
|
||||||
|
{
|
||||||
|
// EQUALS format: FIELD VALUE I1 I2 J1 J2 K1 K2
|
||||||
|
// Items: 0=FIELD, 1=VALUE, 2=I1, 3=I2, 4=J1, 5=J2, 6=K1, 7=K2
|
||||||
|
if ( record.size() < 8 )
|
||||||
|
{
|
||||||
|
return std::unexpected( QString( "EQUALS record has insufficient items (expected at least 8, got %1)" ).arg( record.size() ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Opm::DeckItem> items;
|
||||||
|
|
||||||
|
// Copy field name and value (first two items)
|
||||||
|
items.push_back( record.getItem( 0 ) );
|
||||||
|
items.push_back( record.getItem( 1 ) );
|
||||||
|
|
||||||
|
// Transform IJK box coordinates (items 2-7: I1, I2, J1, J2, K1, K2)
|
||||||
|
// Note: EQUALS uses 1-based Eclipse coordinates
|
||||||
|
int origI1 = record.getItem( 2 ).get<int>( 0 ) - 1; // Convert to 0-based
|
||||||
|
int origI2 = record.getItem( 3 ).get<int>( 0 ) - 1;
|
||||||
|
int origJ1 = record.getItem( 4 ).get<int>( 0 ) - 1;
|
||||||
|
int origJ2 = record.getItem( 5 ).get<int>( 0 ) - 1;
|
||||||
|
int origK1 = record.getItem( 6 ).get<int>( 0 ) - 1;
|
||||||
|
int origK2 = record.getItem( 7 ).get<int>( 0 ) - 1;
|
||||||
|
|
||||||
|
// Create bounding boxes
|
||||||
|
RigBoundingBoxIjk equalsBox( cvf::Vec3st( origI1, origJ1, origK1 ), cvf::Vec3st( origI2, origJ2, origK2 ) );
|
||||||
|
RigBoundingBoxIjk sectorBox( exportSettings.min(), exportSettings.max() - cvf::Vec3st( 1, 1, 1 ) );
|
||||||
|
|
||||||
|
// Check if boxes overlap and get intersection
|
||||||
|
auto intersection = equalsBox.intersection( sectorBox );
|
||||||
|
if ( !intersection )
|
||||||
|
{
|
||||||
|
// No overlap with sector - skip this record
|
||||||
|
QString fieldName = QString::fromStdString( record.getItem( 0 ).get<std::string>( 0 ) );
|
||||||
|
RiaLogging::info( QString( "EQUALS record for field '%1' [%2-%3, %4-%5, %6-%7] does not overlap with sector - skipping" )
|
||||||
|
.arg( fieldName )
|
||||||
|
.arg( origI1 + 1 )
|
||||||
|
.arg( origI2 + 1 )
|
||||||
|
.arg( origJ1 + 1 )
|
||||||
|
.arg( origJ2 + 1 )
|
||||||
|
.arg( origK1 + 1 )
|
||||||
|
.arg( origK2 + 1 ) );
|
||||||
|
return std::unexpected( "EQUALS record does not overlap with sector" );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the clamped coordinates from the intersection
|
||||||
|
cvf::Vec3st corner1 = intersection->min();
|
||||||
|
cvf::Vec3st corner2 = intersection->max();
|
||||||
|
|
||||||
|
// Log if clamping occurred (partial overlap)
|
||||||
|
if ( origI1 != static_cast<int>( corner1.x() ) || origI2 != static_cast<int>( corner2.x() ) || origJ1 != static_cast<int>( corner1.y() ) ||
|
||||||
|
origJ2 != static_cast<int>( corner2.y() ) || origK1 != static_cast<int>( corner1.z() ) || origK2 != static_cast<int>( corner2.z() ) )
|
||||||
|
{
|
||||||
|
QString fieldName = QString::fromStdString( record.getItem( 0 ).get<std::string>( 0 ) );
|
||||||
|
RiaLogging::info(
|
||||||
|
QString( "EQUALS record for field '%1' partially overlaps sector, clamped from [%2-%3, %4-%5, %6-%7] to [%8-%9, %10-%11, "
|
||||||
|
"%12-%13]" )
|
||||||
|
.arg( fieldName )
|
||||||
|
.arg( origI1 + 1 )
|
||||||
|
.arg( origI2 + 1 )
|
||||||
|
.arg( origJ1 + 1 )
|
||||||
|
.arg( origJ2 + 1 )
|
||||||
|
.arg( origK1 + 1 )
|
||||||
|
.arg( origK2 + 1 )
|
||||||
|
.arg( corner1.x() + 1 )
|
||||||
|
.arg( corner2.x() + 1 )
|
||||||
|
.arg( corner1.y() + 1 )
|
||||||
|
.arg( corner2.y() + 1 )
|
||||||
|
.arg( corner1.z() + 1 )
|
||||||
|
.arg( corner2.z() + 1 ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
auto transformResult1 = transformIjkToSectorCoordinates( corner1, exportSettings.min(), exportSettings.max(), exportSettings.refinement() );
|
||||||
|
auto transformResult2 = transformIjkToSectorCoordinates( corner2, exportSettings.min(), exportSettings.max(), exportSettings.refinement() );
|
||||||
|
|
||||||
|
if ( !transformResult1 )
|
||||||
|
{
|
||||||
|
return std::unexpected( QString( "EQUALS I1,J1,K1 coordinate is out of sector bounds: %1" ).arg( transformResult1.error() ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !transformResult2 )
|
||||||
|
{
|
||||||
|
return std::unexpected( QString( "EQUALS I2,J2,K2 coordinate is out of sector bounds: %1" ).arg( transformResult2.error() ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
using E = Opm::ParserKeywords::EQUALS;
|
||||||
|
items.push_back( RifOpmDeckTools::item( E::I1::itemName, static_cast<int>( transformResult1->x() ) ) );
|
||||||
|
items.push_back( RifOpmDeckTools::item( E::I2::itemName, static_cast<int>( transformResult2->x() ) ) );
|
||||||
|
items.push_back( RifOpmDeckTools::item( E::J1::itemName, static_cast<int>( transformResult1->y() ) ) );
|
||||||
|
items.push_back( RifOpmDeckTools::item( E::J2::itemName, static_cast<int>( transformResult2->y() ) ) );
|
||||||
|
items.push_back( RifOpmDeckTools::item( E::K1::itemName, static_cast<int>( transformResult1->z() ) ) );
|
||||||
|
items.push_back( RifOpmDeckTools::item( E::K2::itemName, static_cast<int>( transformResult2->z() ) ) );
|
||||||
|
|
||||||
|
return Opm::DeckRecord{ std::move( items ) };
|
||||||
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------------------
|
||||||
///
|
///
|
||||||
//--------------------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -89,6 +89,10 @@ private:
|
|||||||
RifOpmFlowDeckFile& deckFile,
|
RifOpmFlowDeckFile& deckFile,
|
||||||
int operNumRegion );
|
int operNumRegion );
|
||||||
|
|
||||||
|
static std::expected<void, QString> replaceEqualsKeywordIndices( RimEclipseCase* eclipseCase,
|
||||||
|
const RicExportEclipseSectorModelUi& exportSettings,
|
||||||
|
RifOpmFlowDeckFile& deckFile );
|
||||||
|
|
||||||
static std::vector<RigSimWellData*> findIntersectingWells( RimEclipseCase* eclipseCase, const cvf::Vec3st& min, const cvf::Vec3st& max );
|
static std::vector<RigSimWellData*> findIntersectingWells( RimEclipseCase* eclipseCase, const cvf::Vec3st& min, const cvf::Vec3st& max );
|
||||||
|
|
||||||
static std::expected<cvf::Vec3st, QString> transformIjkToSectorCoordinates( const cvf::Vec3st& originalIjk,
|
static std::expected<cvf::Vec3st, QString> transformIjkToSectorCoordinates( const cvf::Vec3st& originalIjk,
|
||||||
@@ -108,4 +112,7 @@ private:
|
|||||||
const std::string& wellName,
|
const std::string& wellName,
|
||||||
bool isWellNameRecord,
|
bool isWellNameRecord,
|
||||||
const RicExportEclipseSectorModelUi& exportSettings );
|
const RicExportEclipseSectorModelUi& exportSettings );
|
||||||
|
|
||||||
|
static std::expected<Opm::DeckRecord, QString> processEqualsRecord( const Opm::DeckRecord& record,
|
||||||
|
const RicExportEclipseSectorModelUi& exportSettings );
|
||||||
};
|
};
|
||||||
@@ -7,6 +7,7 @@ set(SOURCE_GROUP_HEADER_FILES
|
|||||||
${CMAKE_CURRENT_LIST_DIR}/RigTypeSafeIndex.h
|
${CMAKE_CURRENT_LIST_DIR}/RigTypeSafeIndex.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/RigAllanDiagramData.h
|
${CMAKE_CURRENT_LIST_DIR}/RigAllanDiagramData.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/RigBasicPlane.h
|
${CMAKE_CURRENT_LIST_DIR}/RigBasicPlane.h
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/RigBoundingBoxIjk.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/RigCaseCellResultCalculator.h
|
${CMAKE_CURRENT_LIST_DIR}/RigCaseCellResultCalculator.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/RigCaseCellResultsData.h
|
${CMAKE_CURRENT_LIST_DIR}/RigCaseCellResultsData.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/RigCaseRealizationParameters.h
|
${CMAKE_CURRENT_LIST_DIR}/RigCaseRealizationParameters.h
|
||||||
@@ -93,6 +94,7 @@ set(SOURCE_GROUP_SOURCE_FILES
|
|||||||
${CMAKE_CURRENT_LIST_DIR}/RigActiveCellInfo.cpp
|
${CMAKE_CURRENT_LIST_DIR}/RigActiveCellInfo.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/RigActiveCellLocalGrid.cpp
|
${CMAKE_CURRENT_LIST_DIR}/RigActiveCellLocalGrid.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/RigAllanDiagramData.cpp
|
${CMAKE_CURRENT_LIST_DIR}/RigAllanDiagramData.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/RigBoundingBoxIjk.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/RigBasicPlane.cpp
|
${CMAKE_CURRENT_LIST_DIR}/RigBasicPlane.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/RigCaseCellResultCalculator.cpp
|
${CMAKE_CURRENT_LIST_DIR}/RigCaseCellResultCalculator.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/RigCaseCellResultsData.cpp
|
${CMAKE_CURRENT_LIST_DIR}/RigCaseCellResultsData.cpp
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Copyright (C) 2025- 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 "RigBoundingBoxIjk.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
RigBoundingBoxIjk::RigBoundingBoxIjk()
|
||||||
|
: m_min( cvf::Vec3st::ZERO )
|
||||||
|
, m_max( cvf::Vec3st::ZERO )
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
RigBoundingBoxIjk::RigBoundingBoxIjk( const cvf::Vec3st& min, const cvf::Vec3st& max )
|
||||||
|
: m_min( min )
|
||||||
|
, m_max( max )
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
bool RigBoundingBoxIjk::isValid() const
|
||||||
|
{
|
||||||
|
return m_min.x() <= m_max.x() && m_min.y() <= m_max.y() && m_min.z() <= m_max.z();
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
bool RigBoundingBoxIjk::overlaps( const RigBoundingBoxIjk& other ) const
|
||||||
|
{
|
||||||
|
// Two boxes overlap if they overlap in all three dimensions
|
||||||
|
// No overlap if: this.max < other.min OR this.min > other.max (for any dimension)
|
||||||
|
bool overlapsI = m_max.x() >= other.m_min.x() && m_min.x() <= other.m_max.x();
|
||||||
|
bool overlapsJ = m_max.y() >= other.m_min.y() && m_min.y() <= other.m_max.y();
|
||||||
|
bool overlapsK = m_max.z() >= other.m_min.z() && m_min.z() <= other.m_max.z();
|
||||||
|
|
||||||
|
return overlapsI && overlapsJ && overlapsK;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
std::optional<RigBoundingBoxIjk> RigBoundingBoxIjk::intersection( const RigBoundingBoxIjk& other ) const
|
||||||
|
{
|
||||||
|
if ( !overlaps( other ) )
|
||||||
|
{
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute intersection box
|
||||||
|
cvf::Vec3st intersectMin( std::max( m_min.x(), other.m_min.x() ),
|
||||||
|
std::max( m_min.y(), other.m_min.y() ),
|
||||||
|
std::max( m_min.z(), other.m_min.z() ) );
|
||||||
|
|
||||||
|
cvf::Vec3st intersectMax( std::min( m_max.x(), other.m_max.x() ),
|
||||||
|
std::min( m_max.y(), other.m_max.y() ),
|
||||||
|
std::min( m_max.z(), other.m_max.z() ) );
|
||||||
|
|
||||||
|
return RigBoundingBoxIjk( intersectMin, intersectMax );
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
std::optional<RigBoundingBoxIjk> RigBoundingBoxIjk::clamp( const RigBoundingBoxIjk& bounds ) const
|
||||||
|
{
|
||||||
|
// Clamp is the same as intersection for bounding boxes
|
||||||
|
return intersection( bounds );
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Copyright (C) 2025- 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 "cvfVector3.h"
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
//==================================================================================================
|
||||||
|
///
|
||||||
|
/// Value type representing an axis-aligned bounding box in IJK (integer) grid coordinates
|
||||||
|
///
|
||||||
|
//==================================================================================================
|
||||||
|
class RigBoundingBoxIjk
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
RigBoundingBoxIjk();
|
||||||
|
RigBoundingBoxIjk( const cvf::Vec3st& min, const cvf::Vec3st& max );
|
||||||
|
|
||||||
|
const cvf::Vec3st& min() const { return m_min; }
|
||||||
|
const cvf::Vec3st& max() const { return m_max; }
|
||||||
|
|
||||||
|
bool isValid() const;
|
||||||
|
|
||||||
|
// Check if this box overlaps with another box
|
||||||
|
bool overlaps( const RigBoundingBoxIjk& other ) const;
|
||||||
|
|
||||||
|
// Compute intersection of this box with another box
|
||||||
|
// Returns std::nullopt if boxes don't overlap
|
||||||
|
std::optional<RigBoundingBoxIjk> intersection( const RigBoundingBoxIjk& other ) const;
|
||||||
|
|
||||||
|
// Clamp this box to be within another box
|
||||||
|
// Returns std::nullopt if boxes don't overlap
|
||||||
|
std::optional<RigBoundingBoxIjk> clamp( const RigBoundingBoxIjk& bounds ) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
cvf::Vec3st m_min;
|
||||||
|
cvf::Vec3st m_max;
|
||||||
|
};
|
||||||
@@ -8,6 +8,7 @@ endif(MSVC)
|
|||||||
set(SOURCE_UNITTEST_FILES
|
set(SOURCE_UNITTEST_FILES
|
||||||
${CMAKE_CURRENT_LIST_DIR}/cvfGeometryTools-Test.cpp
|
${CMAKE_CURRENT_LIST_DIR}/cvfGeometryTools-Test.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/Ert-Test.cpp
|
${CMAKE_CURRENT_LIST_DIR}/Ert-Test.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/RigBoundingBoxIjk-Test.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/RifcCommandCore-Test.cpp
|
${CMAKE_CURRENT_LIST_DIR}/RifcCommandCore-Test.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/RifEclipseInputFileTools-Test.cpp
|
${CMAKE_CURRENT_LIST_DIR}/RifEclipseInputFileTools-Test.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/RifOpmFlowDeckFile-Test.cpp
|
${CMAKE_CURRENT_LIST_DIR}/RifOpmFlowDeckFile-Test.cpp
|
||||||
|
|||||||
@@ -0,0 +1,181 @@
|
|||||||
|
/////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Copyright (C) 2025- 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 "RigBoundingBoxIjk.h"
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
TEST( RigBoundingBoxIjk, BasicConstruction )
|
||||||
|
{
|
||||||
|
// Default constructor
|
||||||
|
RigBoundingBoxIjk defaultBox;
|
||||||
|
EXPECT_TRUE( defaultBox.isValid() ); // Min and max are both (0,0,0)
|
||||||
|
EXPECT_EQ( cvf::Vec3st::ZERO, defaultBox.min() );
|
||||||
|
EXPECT_EQ( cvf::Vec3st::ZERO, defaultBox.max() );
|
||||||
|
|
||||||
|
// Constructor with min and max
|
||||||
|
RigBoundingBoxIjk box( cvf::Vec3st( 1, 2, 3 ), cvf::Vec3st( 5, 6, 7 ) );
|
||||||
|
EXPECT_TRUE( box.isValid() );
|
||||||
|
EXPECT_EQ( cvf::Vec3st( 1, 2, 3 ), box.min() );
|
||||||
|
EXPECT_EQ( cvf::Vec3st( 5, 6, 7 ), box.max() );
|
||||||
|
|
||||||
|
// Invalid box (min > max)
|
||||||
|
RigBoundingBoxIjk invalidBox( cvf::Vec3st( 10, 10, 10 ), cvf::Vec3st( 5, 5, 5 ) );
|
||||||
|
EXPECT_FALSE( invalidBox.isValid() );
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
TEST( RigBoundingBoxIjk, IsValid )
|
||||||
|
{
|
||||||
|
// Valid boxes
|
||||||
|
EXPECT_TRUE( RigBoundingBoxIjk( cvf::Vec3st( 0, 0, 0 ), cvf::Vec3st( 10, 10, 10 ) ).isValid() );
|
||||||
|
EXPECT_TRUE( RigBoundingBoxIjk( cvf::Vec3st( 5, 5, 5 ), cvf::Vec3st( 5, 5, 5 ) ).isValid() ); // Single point
|
||||||
|
|
||||||
|
// Invalid boxes (min > max in at least one dimension)
|
||||||
|
EXPECT_FALSE( RigBoundingBoxIjk( cvf::Vec3st( 10, 0, 0 ), cvf::Vec3st( 5, 10, 10 ) ).isValid() ); // X invalid
|
||||||
|
EXPECT_FALSE( RigBoundingBoxIjk( cvf::Vec3st( 0, 10, 0 ), cvf::Vec3st( 10, 5, 10 ) ).isValid() ); // Y invalid
|
||||||
|
EXPECT_FALSE( RigBoundingBoxIjk( cvf::Vec3st( 0, 0, 10 ), cvf::Vec3st( 10, 10, 5 ) ).isValid() ); // Z invalid
|
||||||
|
EXPECT_FALSE( RigBoundingBoxIjk( cvf::Vec3st( 10, 10, 10 ), cvf::Vec3st( 5, 5, 5 ) ).isValid() ); // All invalid
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
TEST( RigBoundingBoxIjk, OverlapDetection )
|
||||||
|
{
|
||||||
|
RigBoundingBoxIjk box1( cvf::Vec3st( 0, 0, 0 ), cvf::Vec3st( 10, 10, 10 ) );
|
||||||
|
|
||||||
|
// Same box overlaps with itself
|
||||||
|
EXPECT_TRUE( box1.overlaps( box1 ) );
|
||||||
|
|
||||||
|
// Completely inside
|
||||||
|
RigBoundingBoxIjk box2( cvf::Vec3st( 2, 2, 2 ), cvf::Vec3st( 8, 8, 8 ) );
|
||||||
|
EXPECT_TRUE( box1.overlaps( box2 ) );
|
||||||
|
EXPECT_TRUE( box2.overlaps( box1 ) ); // Symmetric
|
||||||
|
|
||||||
|
// Partially overlapping
|
||||||
|
RigBoundingBoxIjk box3( cvf::Vec3st( 5, 5, 5 ), cvf::Vec3st( 15, 15, 15 ) );
|
||||||
|
EXPECT_TRUE( box1.overlaps( box3 ) );
|
||||||
|
EXPECT_TRUE( box3.overlaps( box1 ) );
|
||||||
|
|
||||||
|
// Touching at edge (inclusive bounds - should overlap)
|
||||||
|
RigBoundingBoxIjk box4( cvf::Vec3st( 10, 10, 10 ), cvf::Vec3st( 20, 20, 20 ) );
|
||||||
|
EXPECT_TRUE( box1.overlaps( box4 ) );
|
||||||
|
EXPECT_TRUE( box4.overlaps( box1 ) );
|
||||||
|
|
||||||
|
// Completely separate (no overlap)
|
||||||
|
RigBoundingBoxIjk box5( cvf::Vec3st( 20, 20, 20 ), cvf::Vec3st( 30, 30, 30 ) );
|
||||||
|
EXPECT_FALSE( box1.overlaps( box5 ) );
|
||||||
|
EXPECT_FALSE( box5.overlaps( box1 ) );
|
||||||
|
|
||||||
|
// Overlap in 2D but not 3D (separated in Z)
|
||||||
|
RigBoundingBoxIjk box6( cvf::Vec3st( 5, 5, 20 ), cvf::Vec3st( 15, 15, 30 ) );
|
||||||
|
EXPECT_FALSE( box1.overlaps( box6 ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
TEST( RigBoundingBoxIjk, Intersection )
|
||||||
|
{
|
||||||
|
RigBoundingBoxIjk box1( cvf::Vec3st( 0, 0, 0 ), cvf::Vec3st( 10, 10, 10 ) );
|
||||||
|
|
||||||
|
// No overlap returns nullopt
|
||||||
|
RigBoundingBoxIjk box2( cvf::Vec3st( 20, 20, 20 ), cvf::Vec3st( 30, 30, 30 ) );
|
||||||
|
auto result1 = box1.intersection( box2 );
|
||||||
|
EXPECT_FALSE( result1.has_value() );
|
||||||
|
|
||||||
|
// Partial overlap
|
||||||
|
RigBoundingBoxIjk box3( cvf::Vec3st( 5, 5, 5 ), cvf::Vec3st( 15, 15, 15 ) );
|
||||||
|
auto result2 = box1.intersection( box3 );
|
||||||
|
EXPECT_TRUE( result2.has_value() );
|
||||||
|
EXPECT_EQ( cvf::Vec3st( 5, 5, 5 ), result2->min() );
|
||||||
|
EXPECT_EQ( cvf::Vec3st( 10, 10, 10 ), result2->max() );
|
||||||
|
|
||||||
|
// Complete containment (box4 inside box1)
|
||||||
|
RigBoundingBoxIjk box4( cvf::Vec3st( 2, 2, 2 ), cvf::Vec3st( 8, 8, 8 ) );
|
||||||
|
auto result3 = box1.intersection( box4 );
|
||||||
|
EXPECT_TRUE( result3.has_value() );
|
||||||
|
EXPECT_EQ( cvf::Vec3st( 2, 2, 2 ), result3->min() );
|
||||||
|
EXPECT_EQ( cvf::Vec3st( 8, 8, 8 ), result3->max() );
|
||||||
|
|
||||||
|
// Same box
|
||||||
|
auto result4 = box1.intersection( box1 );
|
||||||
|
EXPECT_TRUE( result4.has_value() );
|
||||||
|
EXPECT_EQ( cvf::Vec3st( 0, 0, 0 ), result4->min() );
|
||||||
|
EXPECT_EQ( cvf::Vec3st( 10, 10, 10 ), result4->max() );
|
||||||
|
|
||||||
|
// Single cell intersection
|
||||||
|
RigBoundingBoxIjk box5( cvf::Vec3st( 10, 10, 10 ), cvf::Vec3st( 20, 20, 20 ) );
|
||||||
|
auto result5 = box1.intersection( box5 );
|
||||||
|
EXPECT_TRUE( result5.has_value() );
|
||||||
|
EXPECT_EQ( cvf::Vec3st( 10, 10, 10 ), result5->min() );
|
||||||
|
EXPECT_EQ( cvf::Vec3st( 10, 10, 10 ), result5->max() );
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
TEST( RigBoundingBoxIjk, Clamp )
|
||||||
|
{
|
||||||
|
RigBoundingBoxIjk bounds( cvf::Vec3st( 0, 0, 0 ), cvf::Vec3st( 10, 10, 10 ) );
|
||||||
|
|
||||||
|
// Box completely inside bounds
|
||||||
|
RigBoundingBoxIjk box1( cvf::Vec3st( 2, 2, 2 ), cvf::Vec3st( 8, 8, 8 ) );
|
||||||
|
auto result1 = box1.clamp( bounds );
|
||||||
|
EXPECT_TRUE( result1.has_value() );
|
||||||
|
EXPECT_EQ( cvf::Vec3st( 2, 2, 2 ), result1->min() );
|
||||||
|
EXPECT_EQ( cvf::Vec3st( 8, 8, 8 ), result1->max() );
|
||||||
|
|
||||||
|
// Box partially outside bounds - should be clamped
|
||||||
|
RigBoundingBoxIjk box2( cvf::Vec3st( 5, 5, 5 ), cvf::Vec3st( 15, 15, 15 ) );
|
||||||
|
auto result2 = box2.clamp( bounds );
|
||||||
|
EXPECT_TRUE( result2.has_value() );
|
||||||
|
EXPECT_EQ( cvf::Vec3st( 5, 5, 5 ), result2->min() );
|
||||||
|
EXPECT_EQ( cvf::Vec3st( 10, 10, 10 ), result2->max() );
|
||||||
|
|
||||||
|
// Box completely outside bounds
|
||||||
|
RigBoundingBoxIjk box3( cvf::Vec3st( 20, 20, 20 ), cvf::Vec3st( 30, 30, 30 ) );
|
||||||
|
auto result3 = box3.clamp( bounds );
|
||||||
|
EXPECT_FALSE( result3.has_value() );
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
///
|
||||||
|
//--------------------------------------------------------------------------------------------------
|
||||||
|
TEST( RigBoundingBoxIjk, ClampBehavesLikeIntersection )
|
||||||
|
{
|
||||||
|
// Verify that clamp() and intersection() behave identically
|
||||||
|
RigBoundingBoxIjk bounds( cvf::Vec3st( 0, 0, 0 ), cvf::Vec3st( 10, 10, 10 ) );
|
||||||
|
RigBoundingBoxIjk box( cvf::Vec3st( 5, 5, 5 ), cvf::Vec3st( 15, 15, 15 ) );
|
||||||
|
|
||||||
|
auto clampResult = box.clamp( bounds );
|
||||||
|
auto intersectionResult = box.intersection( bounds );
|
||||||
|
|
||||||
|
EXPECT_EQ( clampResult.has_value(), intersectionResult.has_value() );
|
||||||
|
if ( clampResult.has_value() && intersectionResult.has_value() )
|
||||||
|
{
|
||||||
|
EXPECT_EQ( clampResult->min(), intersectionResult->min() );
|
||||||
|
EXPECT_EQ( clampResult->max(), intersectionResult->max() );
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user