///////////////////////////////////////////////////////////////////////////////// // // 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 "RigNNCData.h" #include "cafDisplayCoordTransform.h" #include "cafEffectGenerator.h" #include "cvfDrawableGeo.h" #include "cvfGeometryTools.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; cvf::ref displayCordXf = m_rimReservoirView->displayCoordTransform(); std::vector tensorVisualizations; double characteristicCellSize = eclipseCase->characteristicCellSize(); float arrowConstantScaling = 10.0 * result->sizeScale() * characteristicCellSize; double maxAbsResult = 1.0; { double min, max; result->mappingRange( min, max ); if ( min != cvf::UNDEFINED_DOUBLE && max != cvf::UNDEFINED_DOUBLE ) { maxAbsResult = std::max( cvf::Math::abs( max ), cvf::Math::abs( min ) ); } } float arrowScaling = arrowConstantScaling / maxAbsResult; std::vector resultAddresses; std::vector directions; RigCaseCellResultsData* resultsData = eclipseCaseData->results( RiaDefines::PorosityModelType::MATRIX_MODEL ); { std::vector addresses; result->resultAddressesIJK( addresses ); for ( size_t fluidIndex = 0; fluidIndex < addresses.size(); fluidIndex += 3 ) { if ( result->showVectorI() ) { if ( fluidIndex == 0 ) directions.push_back( cvf::StructGridInterface::POS_I ); auto candidate = addresses[0 + fluidIndex]; if ( resultsData->hasResultEntry( candidate ) && !resultsData->cellScalarResults( candidate, timeStepIndex ).empty() ) { resultAddresses.push_back( candidate ); } } if ( result->showVectorJ() ) { if ( fluidIndex == 0 ) directions.push_back( cvf::StructGridInterface::POS_J ); auto candidate = addresses[1 + fluidIndex]; if ( resultsData->hasResultEntry( candidate ) && !resultsData->cellScalarResults( candidate, timeStepIndex ).empty() ) { resultAddresses.push_back( candidate ); } } if ( result->showVectorK() ) { if ( fluidIndex == 0 ) directions.push_back( cvf::StructGridInterface::POS_K ); auto candidate = addresses[2 + fluidIndex]; if ( resultsData->hasResultEntry( candidate ) && !resultsData->cellScalarResults( candidate, timeStepIndex ).empty() ) { resultAddresses.push_back( candidate ); } } } } RigActiveCellInfo* activeCellInfo = eclipseCaseData->activeCellInfo( RiaDefines::PorosityModelType::MATRIX_MODEL ); const std::vector& cells = eclipseCase->mainGrid()->globalCellArray(); auto getFaceCenterAndNormal = [cells, arrowScaling, displayCordXf]( size_t globalCellIdx, cvf::StructGridInterface::FaceType faceType, cvf::Vec3d& faceCenter, cvf::Vec3d& faceNormal ) { faceCenter = displayCordXf->transformToDisplayCoord( cells[globalCellIdx].faceCenter( faceType ) ); cvf::Vec3d cellCenter = displayCordXf->transformToDisplayCoord( cells[globalCellIdx].center() ); faceNormal = ( faceCenter - cellCenter ).getNormalized() * arrowScaling; }; if ( !resultAddresses.empty() && !directions.empty() ) { #pragma omp parallel for for ( int gcIdx = 0; gcIdx < static_cast( cells.size() ); ++gcIdx ) { if ( !cells[gcIdx].isInvalid() && activeCellInfo->isActive( gcIdx ) ) { size_t resultIdx = activeCellInfo->cellResultIndex( gcIdx ); if ( result->vectorView() == RimElementVectorResult::VectorView::PER_FACE ) { for ( int dir = 0; dir < static_cast( directions.size() ); dir++ ) { double resultValue = 0.0; for ( size_t flIdx = dir; flIdx < resultAddresses.size(); flIdx += directions.size() ) { resultValue += resultsData->cellScalarResults( resultAddresses[flIdx], timeStepIndex ).at( resultIdx ); } if ( std::abs( resultValue ) >= result->threshold() ) { cvf::Vec3d faceCenter; cvf::Vec3d faceNormal; getFaceCenterAndNormal( static_cast( gcIdx ), directions[dir], faceCenter, faceNormal ); faceNormal *= std::abs( resultValue ); bool centerArrow = false; if ( result->vectorSuraceCrossingLocation() == RimElementVectorResult::VectorSurfaceCrossingLocation::VECTOR_CENTER && result->vectorView() == RimElementVectorResult::VectorView::PER_FACE ) { centerArrow = true; } #pragma omp critical( critical_section_RivElementVectorResultPartMgr_add_1 ) tensorVisualizations.push_back( ElementVectorResultVisualization( faceCenter, faceNormal, resultValue, std::cbrt( cells[gcIdx].volume() / 3.0 ), centerArrow ) ); } } } else if ( result->vectorView() == RimElementVectorResult::VectorView::CELL_CENTER_TOTAL ) { cvf::Vec3d aggregatedVector; cvf::Vec3d aggregatedResult; for ( int dir = 0; dir < static_cast( directions.size() ); dir++ ) { double resultValue = 0.0; for ( size_t flIdx = dir; flIdx < resultAddresses.size(); flIdx += directions.size() ) { resultValue += resultsData->cellScalarResults( resultAddresses[flIdx], timeStepIndex ).at( resultIdx ); } cvf::Vec3d faceCenter; cvf::Vec3d faceNormal; cvf::Vec3d faceNormalScaled; getFaceCenterAndNormal( gcIdx, directions[dir], faceCenter, faceNormal ); faceNormalScaled = faceNormal * resultValue; aggregatedVector += faceNormalScaled; aggregatedResult += faceNormal.getNormalized() * resultValue; } if ( aggregatedResult.length() >= result->threshold() ) { bool centerArrow = false; if ( result->vectorSuraceCrossingLocation() == RimElementVectorResult::VectorSurfaceCrossingLocation::VECTOR_CENTER ) { centerArrow = true; } #pragma omp critical( critical_section_RivElementVectorResultPartMgr_add_2 ) tensorVisualizations.push_back( ElementVectorResultVisualization( displayCordXf->transformToDisplayCoord( cells[gcIdx].center() ), aggregatedVector, aggregatedResult.length(), std::cbrt( cells[gcIdx].volume() / 3.0 ), centerArrow ) ); } } } } } if ( result->showNncData() ) { RigNNCData* nncData = eclipseCaseData->mainGrid()->nncData(); nncData->buildPolygonsForEclipseConnections(); std::vector>*> nncResultVals; std::vector combinedAddresses; result->resultAddressesCombined( combinedAddresses ); for ( auto candidate : combinedAddresses ) { if ( candidate.resultCatType() == RiaDefines::ResultCatType::DYNAMIC_NATIVE ) { if ( nncData->hasScalarValues( candidate ) ) { nncResultVals.push_back( nncData->dynamicConnectionScalarResult( candidate ) ); } } } #pragma omp parallel for for ( int nIdx = 0; nIdx < static_cast( nncData->eclipseConnectionCount() ); ++nIdx ) { const RigConnection& conn = nncData->availableConnections()[nIdx]; if ( conn.polygon().size() ) { double resultValue = 0.0; for ( size_t flIdx = 0; flIdx < nncResultVals.size(); flIdx++ ) { if ( nIdx < static_cast( nncResultVals.at( flIdx )->at( timeStepIndex ).size() ) ) { resultValue += nncResultVals.at( flIdx )->at( timeStepIndex )[nIdx]; } } cvf::Vec3d connCenter = static_cast( cvf::GeometryTools::computePolygonCenter( conn.polygon() ) ); cvf::Vec3d faceCenter; cvf::Vec3d connNormal; getFaceCenterAndNormal( conn.c1GlobIdx(), conn.face(), faceCenter, connNormal ); connNormal *= std::abs( resultValue ); if ( std::abs( resultValue ) >= result->threshold() ) { bool centerArrow = false; if ( result->vectorView() == RimElementVectorResult::VectorView::CELL_CENTER_TOTAL ) { centerArrow = true; } else if ( result->vectorView() == RimElementVectorResult::VectorView::PER_FACE ) { if ( result->vectorSuraceCrossingLocation() == RimElementVectorResult::VectorSurfaceCrossingLocation::VECTOR_CENTER ) centerArrow = true; } #pragma omp critical( critical_section_RivElementVectorResultPartMgr_add_nnc ) tensorVisualizations.push_back( ElementVectorResultVisualization( displayCordXf->transformToDisplayCoord( connCenter ), connNormal, resultValue, std::cbrt( cells[conn.c1GlobIdx()].volume() / 3.0 ), centerArrow ) ); } } } } if ( !tensorVisualizations.empty() ) { cvf::ref partIdx = createPart( *result, tensorVisualizations ); partIdx->updateBoundingBox(); model->addPart( partIdx.p() ); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- cvf::ref RivElementVectorResultPartMgr::createPart( const RimElementVectorResult& result, const std::vector& tensorVisualizations ) const { std::vector shaftIndices; shaftIndices.reserve( tensorVisualizations.size() * 2 ); std::vector headIndices; headIndices.reserve( tensorVisualizations.size() * 6 ); std::vector vertices; vertices.reserve( tensorVisualizations.size() * 7 ); uint counter = 0; for ( const ElementVectorResultVisualization& tensor : tensorVisualizations ) { for ( const cvf::Vec3f& vertex : createArrowVertices( tensor ) ) { vertices.push_back( vertex ); } for ( const uint& index : createArrowShaftIndices( counter ) ) { shaftIndices.push_back( index ); } for ( const uint& index : createArrowHeadIndices( counter ) ) { headIndices.push_back( index ); } counter += 7; } cvf::ref indexedUIntShaft = new cvf::PrimitiveSetIndexedUInt( cvf::PrimitiveType::PT_LINES ); cvf::ref indexArrayShaft = new cvf::UIntArray( shaftIndices ); cvf::ref indexedUIntHead = new cvf::PrimitiveSetIndexedUInt( cvf::PrimitiveType::PT_TRIANGLES ); cvf::ref indexArrayHead = new cvf::UIntArray( headIndices ); cvf::ref drawable = new cvf::DrawableGeo(); indexedUIntShaft->setIndices( indexArrayShaft.p() ); drawable->addPrimitiveSet( indexedUIntShaft.p() ); indexedUIntHead->setIndices( indexArrayHead.p() ); drawable->addPrimitiveSet( indexedUIntHead.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::TensorColors::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->setName( "RivElementVectorResultPartMgr::createPart" ); 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() * 7; if ( textureCoords->size() != vertexCount ) textureCoords->reserve( vertexCount ); for ( auto& evrViz : elementVectorResultVisualizations ) { for ( size_t vxIdx = 0; vxIdx < 7; ++vxIdx ) { cvf::Vec2f texCoord = mapper->mapToTextureCoord( std::abs( evrViz.result ) ); textureCoords->add( texCoord ); } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::array RivElementVectorResultPartMgr::createArrowVertices( const ElementVectorResultVisualization& evrViz ) const { std::array vertices; RimElementVectorResult* result = m_rimReservoirView->elementVectorResult(); if ( !result ) return vertices; cvf::Vec3f headTop = evrViz.faceCenter + evrViz.faceNormal; cvf::Vec3f shaftStart = evrViz.faceCenter; if ( evrViz.centerArrow ) { headTop = evrViz.faceCenter + evrViz.faceNormal / 2.0; shaftStart = evrViz.faceCenter - evrViz.faceNormal / 2.0; } // Flip arrow for negative results and if the vector is not aggregated (in which case we do not have any negative // result) if ( evrViz.result < 0 ) { std::swap( headTop, shaftStart ); } float headLength = std::min( evrViz.approximateCellLength / 3.0f, ( headTop - shaftStart ).length() / 2.0 ); // A fixed size is preferred here cvf::Vec3f headBottom = headTop - ( headTop - shaftStart ).getNormalized() * headLength; float arrowWidth = headLength / 2.0f; cvf::Vec3f headBottomDirection1 = evrViz.faceNormal ^ evrViz.faceCenter; cvf::Vec3f headBottomDirection2 = headBottomDirection1 ^ evrViz.faceNormal; cvf::Vec3f arrowBottomSegment1 = headBottomDirection1.getNormalized() * arrowWidth; cvf::Vec3f arrowBottomSegment2 = headBottomDirection2.getNormalized() * arrowWidth; vertices[0] = shaftStart; vertices[1] = headBottom; vertices[2] = headBottom + arrowBottomSegment1; vertices[3] = headBottom - arrowBottomSegment1; vertices[4] = headTop; vertices[5] = headBottom + arrowBottomSegment2; vertices[6] = headBottom - arrowBottomSegment2; return vertices; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::array RivElementVectorResultPartMgr::createArrowShaftIndices( uint startIndex ) const { std::array indices; indices[0] = startIndex; indices[1] = startIndex + 1; return indices; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::array RivElementVectorResultPartMgr::createArrowHeadIndices( uint startIndex ) const { std::array indices; indices[0] = startIndex + 2; indices[1] = startIndex + 3; indices[2] = startIndex + 4; indices[3] = startIndex + 5; indices[4] = startIndex + 6; indices[5] = startIndex + 4; return indices; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- double RivElementVectorResultPartMgr::scaleLogarithmically( double value ) const { // If values are smaller than one, the logarithm would return // increasing negative values the smaller the number is. However, small // numbers shall remain small and not be scaled up. In order to achieve this, // add 1.0 to small values in order to still obtain small positive numbers after scaling. if ( value <= 1.0 ) { value += 1.0; } return std::log10( value ); }