diff --git a/ApplicationCode/ModelVisualization/CMakeLists_files.cmake b/ApplicationCode/ModelVisualization/CMakeLists_files.cmake index 22f34001ff..f4f9769204 100644 --- a/ApplicationCode/ModelVisualization/CMakeLists_files.cmake +++ b/ApplicationCode/ModelVisualization/CMakeLists_files.cmake @@ -57,6 +57,7 @@ ${CMAKE_CURRENT_LIST_DIR}/RivMeasurementPartMgr.h ${CMAKE_CURRENT_LIST_DIR}/RivTextLabelSourceInfo.h ${CMAKE_CURRENT_LIST_DIR}/RivDiskGeometryGenerator.h ${CMAKE_CURRENT_LIST_DIR}/RivWellDiskPartMgr.h +${CMAKE_CURRENT_LIST_DIR}/RivElementVectorResultPartMgr.h ) set (SOURCE_GROUP_SOURCE_FILES @@ -112,6 +113,7 @@ ${CMAKE_CURRENT_LIST_DIR}/RivMeasurementPartMgr.cpp ${CMAKE_CURRENT_LIST_DIR}/RivTextLabelSourceInfo.cpp ${CMAKE_CURRENT_LIST_DIR}/RivDiskGeometryGenerator.cpp ${CMAKE_CURRENT_LIST_DIR}/RivWellDiskPartMgr.cpp +${CMAKE_CURRENT_LIST_DIR}/RivElementVectorResultPartMgr.cpp ) list(APPEND CODE_HEADER_FILES diff --git a/ApplicationCode/ModelVisualization/RivElementVectorResultPartMgr.cpp b/ApplicationCode/ModelVisualization/RivElementVectorResultPartMgr.cpp new file mode 100644 index 0000000000..2a13817747 --- /dev/null +++ b/ApplicationCode/ModelVisualization/RivElementVectorResultPartMgr.cpp @@ -0,0 +1,321 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2020- Equinor ASA +// +// ResInsight is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// See the GNU General Public License at +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#include "RivElementVectorResultPartMgr.h" + +#include "RimEclipseCase.h" +#include "RimEclipseView.h" +#include "RimElementVectorResult.h" +#include "RimRegularLegendConfig.h" + +#include "RigActiveCellInfo.h" +#include "RigCaseCellResultsData.h" +#include "RigCell.h" +#include "RigEclipseCaseData.h" +#include "RigEclipseResultAddress.h" +#include "RigMainGrid.h" + +#include "cafEffectGenerator.h" + +#include "cvfDrawableGeo.h" +#include "cvfModelBasicList.h" +#include "cvfPart.h" +#include "cvfPrimitiveSetIndexedUInt.h" +#include "cvfShaderProgram.h" +#include "cvfStructGrid.h" +#include "cvfStructGridGeometryGenerator.h" + +#include + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RivElementVectorResultPartMgr::RivElementVectorResultPartMgr( RimEclipseView* reservoirView ) +{ + m_rimReservoirView = reservoirView; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RivElementVectorResultPartMgr::~RivElementVectorResultPartMgr() +{ +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RivElementVectorResultPartMgr::setTransform( cvf::Transform* scaleTransform ) +{ + m_scaleTransform = scaleTransform; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RivElementVectorResultPartMgr::appendDynamicGeometryPartsToModel( cvf::ModelBasicList* model, size_t timeStepIndex ) +{ + CVF_ASSERT( model ); + + if ( m_rimReservoirView.isNull() ) return; + + RimEclipseCase* eclipseCase = m_rimReservoirView->eclipseCase(); + if ( !eclipseCase ) return; + + RigEclipseCaseData* eclipseCaseData = eclipseCase->eclipseCaseData(); + if ( !eclipseCaseData ) return; + + RimElementVectorResult* result = m_rimReservoirView->elementVectorResult(); + if ( !result ) return; + + if ( !result->showResult() ) return; + + std::vector tensorVisualizations; + + double characteristicCellSize = eclipseCase->characteristicCellSize(); + float arrowConstantScaling = 0.5 * result->sizeScale() * characteristicCellSize; + + double min, max; + result->mappingRange( min, max ); + + double maxAbsResult = 1.0; + if ( min != cvf::UNDEFINED_DOUBLE && max != cvf::UNDEFINED_DOUBLE ) + { + maxAbsResult = std::max( cvf::Math::abs( max ), cvf::Math::abs( min ) ); + } + + float arrowScaling = arrowConstantScaling; + if ( result->scaleMethod() == RimElementVectorResult::RESULT ) + { + arrowScaling = arrowConstantScaling / maxAbsResult; + } + + std::vector addresses; + result->resultAddressIJK( addresses ); + + std::vector directions; + std::vector resultAddresses; + if ( result->showVectorI() ) + { + directions.push_back( cvf::StructGridInterface::POS_I ); + resultAddresses.push_back( addresses[0] ); + } + if ( result->showVectorJ() ) + { + directions.push_back( cvf::StructGridInterface::POS_J ); + resultAddresses.push_back( addresses[1] ); + } + if ( result->showVectorK() ) + { + directions.push_back( cvf::StructGridInterface::POS_K ); + resultAddresses.push_back( addresses[2] ); + } + + RigCaseCellResultsData* resultsData = eclipseCaseData->results( RiaDefines::MATRIX_MODEL ); + RigActiveCellInfo* activeCellInfo = eclipseCaseData->activeCellInfo( RiaDefines::MATRIX_MODEL ); + + const cvf::Vec3d offset = eclipseCase->mainGrid()->displayModelOffset(); + + const std::vector& cells = eclipseCase->mainGrid()->globalCellArray(); + for ( int gcIdx = 0; gcIdx < static_cast( cells.size() ); ++gcIdx ) + { + if ( !cells[gcIdx].isInvalid() && activeCellInfo->isActive( gcIdx ) ) + { + for ( int dir = 0; dir < static_cast( directions.size() ); dir++ ) + { + size_t resultIdx = activeCellInfo->cellResultIndex( gcIdx ); + double resultValue = resultsData->cellScalarResults( resultAddresses[dir], timeStepIndex ).at( resultIdx ); + + if ( std::abs( resultValue ) >= result->threshold() ) + { + cvf::Vec3d faceCenter = cells[gcIdx].faceCenter( directions[dir] ) - offset; + cvf::Vec3d cellCenter = cells[gcIdx].center() - offset; + cvf::Vec3d faceNormal = ( faceCenter - cellCenter ).getNormalized() * arrowScaling; + + if ( result->scaleMethod() == RimElementVectorResult::RESULT ) + { + faceNormal *= std::abs( resultValue ); + } + + tensorVisualizations.push_back( + ElementVectorResultVisualization( faceCenter, faceNormal, resultValue ) ); + } + } + } + } + + if ( !tensorVisualizations.empty() ) + { + cvf::ref partIdx = createPart( *result, tensorVisualizations ); + partIdx->setTransform( m_scaleTransform.p() ); + partIdx->updateBoundingBox(); + model->addPart( partIdx.p() ); + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +cvf::ref + RivElementVectorResultPartMgr::createPart( const RimElementVectorResult& result, + const std::vector& tensorVisualizations ) const +{ + std::vector indices; + indices.reserve( tensorVisualizations.size() * 5 ); + + std::vector vertices; + vertices.reserve( tensorVisualizations.size() * 5 ); + + uint counter = 0; + for ( ElementVectorResultVisualization tensor : tensorVisualizations ) + { + for ( const cvf::Vec3f& vertex : createArrowVertices( tensor ) ) + { + vertices.push_back( vertex ); + } + + for ( const uint& index : createArrowIndices( counter ) ) + { + indices.push_back( index ); + } + + counter += 5; + } + + cvf::ref indexedUInt = new cvf::PrimitiveSetIndexedUInt( cvf::PrimitiveType::PT_LINES ); + cvf::ref indexArray = new cvf::UIntArray( indices ); + + cvf::ref drawable = new cvf::DrawableGeo(); + + indexedUInt->setIndices( indexArray.p() ); + drawable->addPrimitiveSet( indexedUInt.p() ); + + cvf::ref vertexArray = new cvf::Vec3fArray( vertices ); + drawable->setVertexArray( vertexArray.p() ); + + cvf::ref lineTexCoords = const_cast( drawable->textureCoordArray() ); + + if ( lineTexCoords.isNull() ) + { + lineTexCoords = new cvf::Vec2fArray; + } + + const cvf::ScalarMapper* activeScalerMapper = nullptr; + + cvf::ref effect; + + auto vectorColors = result.vectorColors(); + if ( vectorColors == RimElementVectorResult::RESULT_COLORS ) + { + activeScalerMapper = result.legendConfig()->scalarMapper(); + createResultColorTextureCoords( lineTexCoords.p(), tensorVisualizations, activeScalerMapper ); + + caf::ScalarMapperMeshEffectGenerator meshEffGen( activeScalerMapper ); + effect = meshEffGen.generateCachedEffect(); + } + else + { + caf::SurfaceEffectGenerator surfaceGen( result.getUniformVectorColor(), caf::PO_1 ); + surfaceGen.enableLighting( !m_rimReservoirView->isLightingDisabled() ); + effect = surfaceGen.generateCachedEffect(); + } + + drawable->setTextureCoordArray( lineTexCoords.p() ); + + cvf::ref part = new cvf::Part; + part->setDrawable( drawable.p() ); + part->setEffect( effect.p() ); + + return part; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RivElementVectorResultPartMgr::createResultColorTextureCoords( + cvf::Vec2fArray* textureCoords, + const std::vector& elementVectorResultVisualizations, + const cvf::ScalarMapper* mapper ) +{ + CVF_ASSERT( textureCoords ); + CVF_ASSERT( mapper ); + + size_t vertexCount = elementVectorResultVisualizations.size() * 5; + if ( textureCoords->size() != vertexCount ) textureCoords->reserve( vertexCount ); + + for ( auto evrViz : elementVectorResultVisualizations ) + { + for ( size_t vxIdx = 0; vxIdx < 5; ++vxIdx ) + { + cvf::Vec2f texCoord = mapper->mapToTextureCoord( evrViz.result ); + textureCoords->add( texCoord ); + } + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::array + RivElementVectorResultPartMgr::createArrowVertices( const ElementVectorResultVisualization& evrViz ) const +{ + std::array vertices; + + cvf::Vec3f headTop = evrViz.faceCenter + evrViz.faceNormal; + cvf::Vec3f shaftStart = evrViz.faceCenter; + + // Flip arrow for negative results + if ( evrViz.result < 0 ) + { + std::swap( headTop, shaftStart ); + } + + float headWidth = 0.05 * evrViz.faceNormal.length(); + + cvf::Vec3f headBottom = headTop - ( headTop - shaftStart ) * 0.2f; + + cvf::Vec3f headBottomDirection = evrViz.faceNormal ^ evrViz.faceCenter; + cvf::Vec3f arrowBottomSegment = headBottomDirection.getNormalized() * headWidth; + + vertices[0] = shaftStart; + vertices[1] = headBottom; + vertices[2] = headBottom + arrowBottomSegment; + vertices[3] = headBottom - arrowBottomSegment; + vertices[4] = headTop; + + return vertices; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::array RivElementVectorResultPartMgr::createArrowIndices( uint startIndex ) const +{ + std::array indices; + + indices[0] = startIndex; + indices[1] = startIndex + 1; + indices[2] = startIndex + 2; + indices[3] = startIndex + 3; + indices[4] = startIndex + 3; + indices[5] = startIndex + 4; + indices[6] = startIndex + 4; + indices[7] = startIndex + 2; + + return indices; +} diff --git a/ApplicationCode/ModelVisualization/RivElementVectorResultPartMgr.h b/ApplicationCode/ModelVisualization/RivElementVectorResultPartMgr.h new file mode 100644 index 0000000000..4d1f6bc720 --- /dev/null +++ b/ApplicationCode/ModelVisualization/RivElementVectorResultPartMgr.h @@ -0,0 +1,84 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// Copyright (C) 2020- Equinor ASA +// +// ResInsight is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY +// WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// See the GNU General Public License at +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "cvfArray.h" +#include "cvfObject.h" +#include "cvfTransform.h" +#include "cvfVector3.h" + +#include "cafPdmPointer.h" +#include "cafTensor3.h" + +#include "RimTensorResults.h" + +#include +#include + +namespace cvf +{ +class ModelBasicList; +class Part; +class ScalarMapper; +class ScalarMapperDiscreteLinear; +} // namespace cvf + +class RimEclipseView; +class RimElementVectorResult; + +class RivElementVectorResultPartMgr : public cvf::Object +{ +public: + RivElementVectorResultPartMgr( RimEclipseView* reservoirView ); + ~RivElementVectorResultPartMgr() override; + + void appendDynamicGeometryPartsToModel( cvf::ModelBasicList* model, size_t timeStepIndex ); + void setTransform( cvf::Transform* scaleTransform ); + +private: + struct ElementVectorResultVisualization + { + ElementVectorResultVisualization( cvf::Vec3d faceCenter, cvf::Vec3d faceNormal, double result ) + : faceCenter( faceCenter ) + , faceNormal( faceNormal ) + , result( result ) + { + } + + cvf::Vec3f faceCenter; + cvf::Vec3f faceNormal; + double result; + }; + +private: + cvf::ref createPart( const RimElementVectorResult& result, + const std::vector& tensorVisualizations ) const; + + static void + createResultColorTextureCoords( cvf::Vec2fArray* textureCoords, + const std::vector& elementVectorResultVisualizations, + const cvf::ScalarMapper* mapper ); + + std::array createArrowVertices( const ElementVectorResultVisualization& tensorVisualization ) const; + std::array createArrowIndices( uint startIndex ) const; + +private: + caf::PdmPointer m_rimReservoirView; + cvf::ref m_scaleTransform; +}; diff --git a/ApplicationCode/ModelVisualization/RivReservoirPartMgr.cpp b/ApplicationCode/ModelVisualization/RivReservoirPartMgr.cpp index 010351ccb4..654e76913d 100644 --- a/ApplicationCode/ModelVisualization/RivReservoirPartMgr.cpp +++ b/ApplicationCode/ModelVisualization/RivReservoirPartMgr.cpp @@ -24,6 +24,7 @@ #include "RimEclipseCase.h" +#include "RivElementVectorResultPartMgr.h" #include "RivGridPartMgr.h" #include "RivReservoirFaultsPartMgr.h" @@ -52,7 +53,8 @@ void RivReservoirPartMgr::clearAndSetReservoir( RivCellSetEnum cellSetType, if ( eclipseCase->mainGrid() ) { // Faults read from file are present only on main grid - m_faultsPartMgr = new RivReservoirFaultsPartMgr( eclipseCase->mainGrid(), reservoirView ); + m_faultsPartMgr = new RivReservoirFaultsPartMgr( eclipseCase->mainGrid(), reservoirView ); + m_elementVectorResultMgr = new RivElementVectorResultPartMgr( reservoirView ); } } } @@ -71,6 +73,11 @@ void RivReservoirPartMgr::setTransform( cvf::Transform* scaleTransform ) { m_faultsPartMgr->setTransform( scaleTransform ); } + + if ( m_elementVectorResultMgr.notNull() ) + { + m_elementVectorResultMgr->setTransform( scaleTransform ); + } } //-------------------------------------------------------------------------------------------------- @@ -163,6 +170,17 @@ void RivReservoirPartMgr::appendGridPartsToModel( cvf::ModelBasicList* model, co } } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RivReservoirPartMgr::appendElementVectorResultPartsToModel( cvf::ModelBasicList* model, size_t timeStepIndex ) +{ + if ( m_elementVectorResultMgr.notNull() ) + { + m_elementVectorResultMgr->appendDynamicGeometryPartsToModel( model, timeStepIndex ); + } +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationCode/ModelVisualization/RivReservoirPartMgr.h b/ApplicationCode/ModelVisualization/RivReservoirPartMgr.h index c3206cebfd..5b04a703a0 100644 --- a/ApplicationCode/ModelVisualization/RivReservoirPartMgr.h +++ b/ApplicationCode/ModelVisualization/RivReservoirPartMgr.h @@ -38,6 +38,7 @@ class RigEclipseCaseData; class RimEclipseView; class RivReservoirFaultsPartMgr; class RivGridPartMgr; +class RivElementVectorResultPartMgr; //================================================================================================== /// @@ -76,9 +77,13 @@ public: void appendFaultPartsToModel( cvf::ModelBasicList* model ); void appendFaultLabelPartsToModel( cvf::ModelBasicList* model ); + // Element Vector Result + void appendElementVectorResultPartsToModel( cvf::ModelBasicList* model, size_t timeStepIndex ); + private: - cvf::Collection m_allGrids; // Main grid and all LGR's - cvf::ref m_faultsPartMgr; + cvf::Collection m_allGrids; // Main grid and all LGR's + cvf::ref m_elementVectorResultMgr; + cvf::ref m_faultsPartMgr; RivCellSetEnum m_cellSetType; }; diff --git a/ApplicationCode/ModelVisualization/RivReservoirViewPartMgr.cpp b/ApplicationCode/ModelVisualization/RivReservoirViewPartMgr.cpp index b6446126b8..125071728a 100644 --- a/ApplicationCode/ModelVisualization/RivReservoirViewPartMgr.cpp +++ b/ApplicationCode/ModelVisualization/RivReservoirViewPartMgr.cpp @@ -43,6 +43,7 @@ #include "RimViewController.h" #include "RimViewLinker.h" +#include "RivElementVectorResultPartMgr.h" #include "RivGridPartMgr.h" #include "RivReservoirFaultsPartMgr.h" @@ -1174,6 +1175,24 @@ void RivReservoirViewPartMgr::appendFaultLabelsDynamicGeometryPartsToModel( cvf: m_propFilteredGeometryFrames[frameIndex]->appendFaultLabelPartsToModel( model ); } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RivReservoirViewPartMgr::appendElementVectorResultDynamicGeometryPartsToModel( cvf::ModelBasicList* model, + RivCellSetEnum geometryType, + size_t frameIndex ) +{ + ensureDynamicGeometryPartsCreated( geometryType, frameIndex ); + if ( geometryType == PROPERTY_FILTERED ) + { + m_propFilteredGeometryFrames[frameIndex]->appendElementVectorResultPartsToModel( model, frameIndex ); + } + else if ( geometryType == PROPERTY_FILTERED_WELL_CELLS ) + { + m_propFilteredWellGeometryFrames[frameIndex]->appendElementVectorResultPartsToModel( model, frameIndex ); + } +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationCode/ModelVisualization/RivReservoirViewPartMgr.h b/ApplicationCode/ModelVisualization/RivReservoirViewPartMgr.h index 2c5c42a7e2..41ea7510db 100644 --- a/ApplicationCode/ModelVisualization/RivReservoirViewPartMgr.h +++ b/ApplicationCode/ModelVisualization/RivReservoirViewPartMgr.h @@ -90,6 +90,11 @@ public: RivCellSetEnum geometryType, size_t frameIndex ); + // Element Vector Result + void appendElementVectorResultDynamicGeometryPartsToModel( cvf::ModelBasicList* model, + RivCellSetEnum geometryType, + size_t frameIndex ); + void forceWatertightGeometryOnForType( RivCellSetEnum geometryType ); void clearWatertightGeometryFlags(); diff --git a/ApplicationCode/ProjectDataModel/RimEclipseView.cpp b/ApplicationCode/ProjectDataModel/RimEclipseView.cpp index 82d0f58784..89a6a5f58e 100644 --- a/ApplicationCode/ProjectDataModel/RimEclipseView.cpp +++ b/ApplicationCode/ProjectDataModel/RimEclipseView.cpp @@ -657,6 +657,8 @@ void RimEclipseView::onUpdateDisplayModelForCurrentTimeStep() appendWellsAndFracturesToModel(); + appendElementVectorResultToModel(); + m_overlayInfoConfig()->update3DInfo(); // Invisible Wells are marked as read only when "show wells intersecting visible cells" is enabled @@ -910,6 +912,37 @@ void RimEclipseView::appendWellsAndFracturesToModel() } } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimEclipseView::appendElementVectorResultToModel() +{ + if ( nativeOrOverrideViewer() ) + { + cvf::Scene* frameScene = nativeOrOverrideViewer()->frame( m_currentTimeStep, isUsingOverrideViewer() ); + if ( frameScene ) + { + // Element Vector Results + cvf::String name = "ElementVectorModelMod"; + this->removeModelByName( frameScene, name ); + + cvf::ref frameParts = new cvf::ModelBasicList; + frameParts->setName( name ); + + m_reservoirGridPartManager->appendElementVectorResultDynamicGeometryPartsToModel( frameParts.p(), + PROPERTY_FILTERED, + m_currentTimeStep ); + + // TODO: should this be ACTIVE? + m_reservoirGridPartManager->appendElementVectorResultDynamicGeometryPartsToModel( frameParts.p(), + PROPERTY_FILTERED_WELL_CELLS, + m_currentTimeStep ); + + frameScene->addModel( frameParts.p() ); + } + } +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationCode/ProjectDataModel/RimEclipseView.h b/ApplicationCode/ProjectDataModel/RimEclipseView.h index bce9b202fa..1868eebb90 100644 --- a/ApplicationCode/ProjectDataModel/RimEclipseView.h +++ b/ApplicationCode/ProjectDataModel/RimEclipseView.h @@ -160,6 +160,7 @@ protected: void onUpdateDisplayModelForCurrentTimeStep() override; void updateVisibleGeometriesAndCellColors(); void appendWellsAndFracturesToModel(); + void appendElementVectorResultToModel(); void onCreateDisplayModel() override; RimPropertyFilterCollection* nativePropertyFilterCollection();