diff --git a/ApplicationLibCode/Commands/RicCreateSurfaceIntersectionBandFeature.cpp b/ApplicationLibCode/Commands/RicCreateSurfaceIntersectionBandFeature.cpp index 7f8b0f9d4b..db359b9fb3 100644 --- a/ApplicationLibCode/Commands/RicCreateSurfaceIntersectionBandFeature.cpp +++ b/ApplicationLibCode/Commands/RicCreateSurfaceIntersectionBandFeature.cpp @@ -22,7 +22,7 @@ #include "RimAnnotationLineAppearance.h" #include "RimEnsembleSurface.h" -#include "RimExtrudedCurveIntersection.h" +#include "RimIntersection.h" #include "RimSurface.h" #include "RimSurfaceCollection.h" #include "RimSurfaceIntersectionBand.h" @@ -36,13 +36,23 @@ CAF_CMD_SOURCE_INIT( RicCreateSurfaceIntersectionBandFeature, "RicCreateSurfaceIntersectionBandFeature" ); +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool RicCreateSurfaceIntersectionBandFeature::isCommandEnabled() const +{ + auto* intersection = caf::SelectionManager::instance()->selectedItemAncestorOfType(); + + return intersection && intersection->supportsSurfaceIntersectionCurves(); +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicCreateSurfaceIntersectionBandFeature::onActionTriggered( bool isChecked ) { - auto* intersection = caf::SelectionManager::instance()->selectedItemAncestorOfType(); - if ( intersection ) + auto* intersection = caf::SelectionManager::instance()->selectedItemAncestorOfType(); + if ( intersection && intersection->supportsSurfaceIntersectionCurves() ) { RimEnsembleSurface* firstEnsembleSurface = nullptr; { diff --git a/ApplicationLibCode/Commands/RicCreateSurfaceIntersectionBandFeature.h b/ApplicationLibCode/Commands/RicCreateSurfaceIntersectionBandFeature.h index edc7510d3d..8063ab3404 100644 --- a/ApplicationLibCode/Commands/RicCreateSurfaceIntersectionBandFeature.h +++ b/ApplicationLibCode/Commands/RicCreateSurfaceIntersectionBandFeature.h @@ -28,6 +28,7 @@ class RicCreateSurfaceIntersectionBandFeature : public caf::CmdFeature CAF_CMD_HEADER_INIT; protected: + bool isCommandEnabled() const override; void onActionTriggered( bool isChecked ) override; void setupActionLook( QAction* actionToSetup ) override; }; diff --git a/ApplicationLibCode/Commands/RicCreateSurfaceIntersectionCurveFeature.cpp b/ApplicationLibCode/Commands/RicCreateSurfaceIntersectionCurveFeature.cpp index 9cc1edff13..2703687be6 100644 --- a/ApplicationLibCode/Commands/RicCreateSurfaceIntersectionCurveFeature.cpp +++ b/ApplicationLibCode/Commands/RicCreateSurfaceIntersectionCurveFeature.cpp @@ -18,7 +18,7 @@ #include "RicCreateSurfaceIntersectionCurveFeature.h" -#include "RimExtrudedCurveIntersection.h" +#include "RimIntersection.h" #include "RimSurfaceIntersectionCurve.h" #include "Riu3DMainWindowTools.h" @@ -29,13 +29,23 @@ CAF_CMD_SOURCE_INIT( RicCreateSurfaceIntersectionCurveFeature, "RicCreateSurfaceIntersectionCurveFeature" ); +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool RicCreateSurfaceIntersectionCurveFeature::isCommandEnabled() const +{ + auto* intersection = caf::SelectionManager::instance()->selectedItemAncestorOfType(); + + return intersection && intersection->supportsSurfaceIntersectionCurves(); +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RicCreateSurfaceIntersectionCurveFeature::onActionTriggered( bool isChecked ) { - auto* intersection = caf::SelectionManager::instance()->selectedItemAncestorOfType(); - if ( intersection ) + auto* intersection = caf::SelectionManager::instance()->selectedItemAncestorOfType(); + if ( intersection && intersection->supportsSurfaceIntersectionCurves() ) { auto curve = intersection->addIntersectionCurve(); intersection->updateAllRequiredEditors(); diff --git a/ApplicationLibCode/Commands/RicCreateSurfaceIntersectionCurveFeature.h b/ApplicationLibCode/Commands/RicCreateSurfaceIntersectionCurveFeature.h index 65e8f2bcf4..955c7bd924 100644 --- a/ApplicationLibCode/Commands/RicCreateSurfaceIntersectionCurveFeature.h +++ b/ApplicationLibCode/Commands/RicCreateSurfaceIntersectionCurveFeature.h @@ -28,6 +28,7 @@ class RicCreateSurfaceIntersectionCurveFeature : public caf::CmdFeature CAF_CMD_HEADER_INIT; protected: + bool isCommandEnabled() const override; void onActionTriggered( bool isChecked ) override; void setupActionLook( QAction* actionToSetup ) override; }; diff --git a/ApplicationLibCode/ModelVisualization/Intersections/RivBoxIntersectionPartMgr.cpp b/ApplicationLibCode/ModelVisualization/Intersections/RivBoxIntersectionPartMgr.cpp index abbcad12c3..4fb90b6afa 100644 --- a/ApplicationLibCode/ModelVisualization/Intersections/RivBoxIntersectionPartMgr.cpp +++ b/ApplicationLibCode/ModelVisualization/Intersections/RivBoxIntersectionPartMgr.cpp @@ -42,6 +42,7 @@ #include "RivPartPriority.h" #include "RivResultToTextureMapper.h" #include "RivScalarMapperUtils.h" +#include "RivSurfaceIntersectionCurveTools.h" #include "RivTernaryScalarMapper.h" #include "RivTernaryTextureCoordsCreator.h" @@ -96,7 +97,7 @@ void RivBoxIntersectionPartMgr::updateCellResultColor( int timeStepIndex ) //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -void RivBoxIntersectionPartMgr::generatePartGeometry( cvf::UByteArray* visibleCells ) +void RivBoxIntersectionPartMgr::generatePartGeometry( cvf::UByteArray* visibleCells, cvf::Transform* scaleTransform ) { bool useBufferObjects = true; // Surface geometry @@ -151,6 +152,9 @@ void RivBoxIntersectionPartMgr::generatePartGeometry( cvf::UByteArray* visibleCe } } + if ( scaleTransform ) + m_annotationParts = RivSurfaceIntersectionCurveTools::createAnnotationParts( m_rimIntersectionBox, *scaleTransform ); + updatePartEffect(); } @@ -206,6 +210,18 @@ void RivBoxIntersectionPartMgr::appendMeshLinePartsToModel( cvf::ModelBasicList* } } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RivBoxIntersectionPartMgr::appendAnnotationPartsToModel( cvf::ModelBasicList* model, cvf::Transform* scaleTransform ) +{ + for ( size_t i = 0; i < m_annotationParts.size(); i++ ) + { + m_annotationParts[i]->setTransform( scaleTransform ); + model->addPart( m_annotationParts.at( i ) ); + } +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/ModelVisualization/Intersections/RivBoxIntersectionPartMgr.h b/ApplicationLibCode/ModelVisualization/Intersections/RivBoxIntersectionPartMgr.h index 05b8fc841c..9e8906bec2 100644 --- a/ApplicationLibCode/ModelVisualization/Intersections/RivBoxIntersectionPartMgr.h +++ b/ApplicationLibCode/ModelVisualization/Intersections/RivBoxIntersectionPartMgr.h @@ -20,6 +20,7 @@ #include "RivBoxIntersectionGeometryGenerator.h" +#include "cvfCollection.h" #include "cvfObject.h" namespace cvf @@ -60,10 +61,11 @@ public: void appendNativeIntersectionFacesToModel( cvf::ModelBasicList* model, cvf::Transform* scaleTransform ); void appendMeshLinePartsToModel( cvf::ModelBasicList* model, cvf::Transform* scaleTransform ); + void appendAnnotationPartsToModel( cvf::ModelBasicList* model, cvf::Transform* scaleTransform ); const RivIntersectionGeometryGeneratorInterface* intersectionGeometryGenerator() const; - void generatePartGeometry( cvf::UByteArray* visibleCells ); + void generatePartGeometry( cvf::UByteArray* visibleCells, cvf::Transform* scaleTransform ); private: void updatePartEffect(); @@ -73,9 +75,10 @@ private: cvf::Color3f m_defaultColor; - cvf::ref m_intersectionBoxFaces; - cvf::ref m_intersectionBoxGridLines; - cvf::ref m_intersectionBoxFacesTextureCoords; + cvf::ref m_intersectionBoxFaces; + cvf::ref m_intersectionBoxGridLines; + cvf::Collection m_annotationParts; + cvf::ref m_intersectionBoxFacesTextureCoords; cvf::ref m_intersectionBoxGenerator; }; diff --git a/ApplicationLibCode/ModelVisualization/Intersections/RivExtrudedCurveIntersectionGeometryGenerator.cpp b/ApplicationLibCode/ModelVisualization/Intersections/RivExtrudedCurveIntersectionGeometryGenerator.cpp index 606db6e952..046687ec75 100644 --- a/ApplicationLibCode/ModelVisualization/Intersections/RivExtrudedCurveIntersectionGeometryGenerator.cpp +++ b/ApplicationLibCode/ModelVisualization/Intersections/RivExtrudedCurveIntersectionGeometryGenerator.cpp @@ -160,52 +160,24 @@ void RivExtrudedCurveIntersectionGeometryGenerator::calculateSurfaceIntersection { m_transformedSurfaceIntersectionPolylines.clear(); - if ( !m_polylines.empty() ) + if ( m_polylines.empty() ) return; + + const auto surfaces = RivSurfaceIntersectionCurveTools::referencedSurfaces( m_intersection->surfaceIntersectionCollection() ); + if ( surfaces.empty() ) return; + + // Vertical pillars along the same polyline as the one used to build the geometry + const auto curtain = m_intersection->surfaceCurtain(); + if ( !curtain.isValid() ) return; + + // The polyline is flattened when the curve is displayed in a 2D intersection view + auto pointTransform = [this]( const cvf::Vec3d& point, size_t segmentIndex ) { - auto firstPolyLine = m_polylines.front(); + const size_t lineIndex = 0; + return transformPointByPolylineSegmentIndex( point, lineIndex, segmentIndex ); + }; - std::vector surfaces; - for ( auto curve : m_intersection->surfaceIntersectionCurves() ) - { - if ( curve->surface() ) surfaces.push_back( curve->surface() ); - } - for ( auto band : m_intersection->surfaceIntersectionBands() ) - { - if ( band->surface1() ) surfaces.push_back( band->surface1() ); - if ( band->surface2() ) surfaces.push_back( band->surface2() ); - } - - for ( auto rimSurface : surfaces ) - { - if ( !rimSurface ) return; - - rimSurface->loadDataIfRequired(); - auto surface = rimSurface->surfaceData(); - if ( !surface ) return; - - std::vector transformedSurfacePolyline; - - // Resample polyline to required resolution - const double maxLineSegmentLength = 1.0; - const auto resampledPolyline = RigSurfaceResampler::computeResampledPolylineWithSegmentInfo( firstPolyLine, maxLineSegmentLength ); - - for ( const auto& [point, segmentIndex] : resampledPolyline ) - { - cvf::Vec3d pointAbove = cvf::Vec3d( point.x(), point.y(), 10000.0 ); - cvf::Vec3d pointBelow = cvf::Vec3d( point.x(), point.y(), -10000.0 ); - - cvf::Vec3d intersectionPoint; - bool foundMatch = RigSurfaceResampler::findClosestPointOnSurface( surface, pointAbove, pointBelow, intersectionPoint ); - if ( foundMatch ) - { - const size_t lineIndex = 0; - transformedSurfacePolyline.push_back( transformPointByPolylineSegmentIndex( intersectionPoint, lineIndex, segmentIndex ) ); - } - } - - if ( !transformedSurfacePolyline.empty() ) m_transformedSurfaceIntersectionPolylines[rimSurface] = transformedSurfacePolyline; - } - } + m_transformedSurfaceIntersectionPolylines = + RivSurfaceIntersectionCurveTools::computeSurfaceCurtainPolylines( surfaces, curtain, pointTransform ); } class MeshLinesAccumulator @@ -844,7 +816,7 @@ bool RivExtrudedCurveIntersectionGeometryGenerator::isAnyGeometryPresent() const //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -std::map> RivExtrudedCurveIntersectionGeometryGenerator::transformedSurfaceIntersectionPolylines() const +std::map RivExtrudedCurveIntersectionGeometryGenerator::transformedSurfaceIntersectionPolylines() const { return m_transformedSurfaceIntersectionPolylines; } diff --git a/ApplicationLibCode/ModelVisualization/Intersections/RivExtrudedCurveIntersectionGeometryGenerator.h b/ApplicationLibCode/ModelVisualization/Intersections/RivExtrudedCurveIntersectionGeometryGenerator.h index d2f7b4b18e..89f19fc6a3 100644 --- a/ApplicationLibCode/ModelVisualization/Intersections/RivExtrudedCurveIntersectionGeometryGenerator.h +++ b/ApplicationLibCode/ModelVisualization/Intersections/RivExtrudedCurveIntersectionGeometryGenerator.h @@ -22,6 +22,7 @@ #include "cafPdmPointer.h" #include "RivIntersectionGeometryGeneratorInterface.h" +#include "RivSurfaceIntersectionCurveTools.h" #include "cvfArray.h" @@ -76,7 +77,7 @@ public: // GeomGen Interface bool isAnyGeometryPresent() const override; - std::map> transformedSurfaceIntersectionPolylines() const; + std::map transformedSurfaceIntersectionPolylines() const; const std::vector& triangleToCellIndex() const override; const std::vector& triangleVxToCellCornerInterpolationWeights() const override; @@ -116,5 +117,5 @@ private: std::vector> m_faultMeshLabelAndAnchorPositions; - std::map> m_transformedSurfaceIntersectionPolylines; + std::map m_transformedSurfaceIntersectionPolylines; }; diff --git a/ApplicationLibCode/ModelVisualization/Intersections/RivExtrudedCurveIntersectionPartMgr.cpp b/ApplicationLibCode/ModelVisualization/Intersections/RivExtrudedCurveIntersectionPartMgr.cpp index 233cd7b724..6094cb2a9c 100644 --- a/ApplicationLibCode/ModelVisualization/Intersections/RivExtrudedCurveIntersectionPartMgr.cpp +++ b/ApplicationLibCode/ModelVisualization/Intersections/RivExtrudedCurveIntersectionPartMgr.cpp @@ -64,6 +64,7 @@ #include "RivResultToTextureMapper.h" #include "RivScalarMapperUtils.h" #include "RivSimWellPipeSourceInfo.h" +#include "RivSurfaceIntersectionCurveTools.h" #include "RivTernaryScalarMapper.h" #include "RivTernaryTextureCoordsCreator.h" #include "RivWellPathSourceInfo.h" @@ -325,7 +326,7 @@ void RivExtrudedCurveIntersectionPartMgr::generatePartGeometry( cvf::UByteArray* applySingleColorEffect(); - createAnnotationSurfaceParts( useBufferObjects, scaleTransform ); + if ( scaleTransform ) createAnnotationSurfaceParts( *scaleTransform ); } //-------------------------------------------------------------------------------------------------- @@ -602,199 +603,12 @@ void RivExtrudedCurveIntersectionPartMgr::createExtrusionDirParts( bool useBuffe //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -void RivExtrudedCurveIntersectionPartMgr::createAnnotationSurfaceParts( bool useBufferObjects, cvf::Transform* scaleTransform ) +void RivExtrudedCurveIntersectionPartMgr::createAnnotationSurfaceParts( cvf::Transform& scaleTransform ) { - m_annotationParts.clear(); - - auto surfPolys = m_intersectionGenerator->transformedSurfaceIntersectionPolylines(); - - for ( auto curve : m_rimIntersection->surfaceIntersectionCurves() ) - { - if ( !curve->isChecked() ) continue; - - auto surface = curve->surface(); - if ( !surface ) continue; - - if ( surfPolys.count( surface ) == 0 ) continue; - auto polylines = surfPolys[surface]; - - auto part = createCurvePart( polylines, - useBufferObjects, - surface->fullName(), - curve->lineAppearance()->color(), - curve->lineAppearance()->thickness(), - scaleTransform ); - - if ( part.notNull() ) m_annotationParts.push_back( part.p() ); - } - - for ( auto band : m_rimIntersection->surfaceIntersectionBands() ) - { - if ( !band->isChecked() ) continue; - - auto surface1 = band->surface1(); - auto surface2 = band->surface2(); - - if ( !surface1 || !surface2 ) continue; - - if ( surfPolys.count( surface1 ) == 0 ) continue; - if ( surfPolys.count( surface2 ) == 0 ) continue; - - auto polylineA = surfPolys[surface1]; - auto polylineB = surfPolys[surface2]; - - // Create curve parts - { - auto part = createCurvePart( polylineA, - useBufferObjects, - surface1->fullName(), - band->lineAppearance()->color(), - band->lineAppearance()->thickness(), - scaleTransform ); - - if ( part.notNull() ) m_annotationParts.push_back( part.p() ); - } - { - auto part = createCurvePart( polylineB, - useBufferObjects, - surface2->fullName(), - band->lineAppearance()->color(), - band->lineAppearance()->thickness(), - scaleTransform ); - - if ( part.notNull() ) m_annotationParts.push_back( part.p() ); - } - - // Create a quad strip between the two polylines - size_t pointCount = std::min( polylineA.size(), polylineB.size() ); - if ( pointCount > 1 ) - { - cvf::GeometryBuilderDrawableGeo geoBuilder; - - for ( size_t i = 1; i < pointCount; i++ ) - { - const auto& pA0 = polylineA[i - 1]; - const auto& pA1 = polylineA[i]; - const auto& pB0 = polylineB[i - 1]; - const auto& pB1 = polylineB[i]; - - geoBuilder.addQuadByVertices( cvf::Vec3f( pA0 ), cvf::Vec3f( pA1 ), cvf::Vec3f( pB1 ), cvf::Vec3f( pB0 ) ); - } - - cvf::ref geo = geoBuilder.drawableGeo(); - if ( geo.notNull() ) - { - geo->computeNormals(); - - if ( useBufferObjects ) - { - geo->setRenderMode( cvf::DrawableGeo::BUFFER_OBJECT ); - } - - cvf::ref part = new cvf::Part; - part->setName( "Surface Intersection Band" ); - part->setDrawable( geo.p() ); - part->updateBoundingBox(); - part->setEnableMask( intersectionCellFaceBit ); - part->setPriority( RivPartPriority::PartType::Transparent ); - - auto color = cvf::Color4f( band->bandColor(), band->bandOpacity() ); - caf::SurfaceEffectGenerator geometryEffgen( color, caf::PO_NEG_LARGE ); - - cvf::ref geometryOnlyEffect = geometryEffgen.generateUnCachedEffect(); - - { - cvf::ref polyOffset = new cvf::RenderStatePolygonOffset; - - polyOffset->enableFillMode( true ); - - // The factor value is defined by enums in - // EffectGenerator::createAndConfigurePolygonOffsetRenderState() Use a factor that is more negative - // than the existing enums - const double offsetFactor = -5; - polyOffset->setFactor( offsetFactor ); - - polyOffset->setUnits( band->polygonOffsetUnit() ); - - geometryOnlyEffect->setRenderState( polyOffset.p() ); - } - - part->setEffect( geometryOnlyEffect.p() ); - - m_annotationParts.push_back( part.p() ); - } - } - } -} - -//-------------------------------------------------------------------------------------------------- -/// -//-------------------------------------------------------------------------------------------------- -cvf::ref RivExtrudedCurveIntersectionPartMgr::createCurvePart( const std::vector& polylines, - bool useBufferObjects, - const QString& description, - const cvf::Color3f& color, - float lineWidth, - cvf::Transform* scaleTransform ) -{ - auto polylineGeo = RivPolylineGenerator::createLineAlongPolylineDrawable( polylines ); - if ( polylineGeo.notNull() ) - { - if ( useBufferObjects ) - { - polylineGeo->setRenderMode( cvf::DrawableGeo::BUFFER_OBJECT ); - } - - cvf::ref part = new cvf::Part; - part->setName( "Intersection " + description.toStdString() ); - part->setDrawable( polylineGeo.p() ); - - part->updateBoundingBox(); - part->setPriority( RivPartPriority::PartType::FaultMeshLines ); - - caf::MeshEffectGenerator lineEffGen( color ); - lineEffGen.setLineWidth( lineWidth ); - - cvf::ref eff = lineEffGen.generateUnCachedEffect(); - - cvf::ref polyOffset = new cvf::RenderStatePolygonOffset; - polyOffset->enableFillMode( true ); - polyOffset->setFactor( -5 ); - const double maxOffsetFactor = -1000; - polyOffset->setUnits( maxOffsetFactor ); - - eff->setRenderState( polyOffset.p() ); - - part->setEffect( eff.p() ); - - if ( part.notNull() && scaleTransform ) - { - // The polylines are defined in the display coordinate system without Z-scaling. The z-scaling is applied to the visualization - // parts using Part::setTransform(Transform* transform) - // The annotation objects are defined by display coordinates, so apply the Z-scaling to the coordinates - - std::vector displayCoords; - const auto& mat = scaleTransform->worldTransform(); - - for ( const auto& p : polylines ) - { - displayCoords.push_back( p.getTransformedPoint( mat ) ); - } - - // Add annotation info to be used to display label in Rim3dView::onViewNavigationChanged() - // Set the source info on one part only, as this data is only used for display of labels - auto annoObj = new RivAnnotationSourceInfo( description.toStdString(), displayCoords ); - annoObj->setLabelPositionStrategyHint( RivAnnotationTools::LabelPositionStrategy::RIGHT ); - annoObj->setShowColor( true ); - annoObj->setColor( color ); - - part->setSourceInfo( annoObj ); - } - - return part; - } - - return nullptr; + m_annotationParts = + RivSurfaceIntersectionCurveTools::createAnnotationParts( m_rimIntersection->surfaceIntersectionCollection(), + m_intersectionGenerator->transformedSurfaceIntersectionPolylines(), + scaleTransform ); } //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/ModelVisualization/Intersections/RivExtrudedCurveIntersectionPartMgr.h b/ApplicationLibCode/ModelVisualization/Intersections/RivExtrudedCurveIntersectionPartMgr.h index 8cf97e267d..fe449c5b76 100644 --- a/ApplicationLibCode/ModelVisualization/Intersections/RivExtrudedCurveIntersectionPartMgr.h +++ b/ApplicationLibCode/ModelVisualization/Intersections/RivExtrudedCurveIntersectionPartMgr.h @@ -87,14 +87,7 @@ private: void createFaultLabelParts( const std::vector>& labelAndAnchors ); void createPolyLineParts( bool useBufferObjects ); void createExtrusionDirParts( bool useBufferObjects ); - void createAnnotationSurfaceParts( bool useBufferObjects, cvf::Transform* scaleTransform ); - - cvf::ref createCurvePart( const std::vector& polylines, - bool useBufferObjects, - const QString& description, - const cvf::Color3f& color, - float lineWidth, - cvf::Transform* scaleTransform ); + void createAnnotationSurfaceParts( cvf::Transform& scaleTransform ); private: caf::PdmPointer m_rimIntersection; diff --git a/ApplicationLibCode/ModelVisualization/Intersections/RivIjkIntersectionGeometryGenerator.cpp b/ApplicationLibCode/ModelVisualization/Intersections/RivIjkIntersectionGeometryGenerator.cpp index 6e155f619e..4cf3bf9e7c 100644 --- a/ApplicationLibCode/ModelVisualization/Intersections/RivIjkIntersectionGeometryGenerator.cpp +++ b/ApplicationLibCode/ModelVisualization/Intersections/RivIjkIntersectionGeometryGenerator.cpp @@ -148,42 +148,27 @@ void RivIjkIntersectionGeometryGenerator::calculateArrays( cvf::UByteArray* visi 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 + // Determine which cell face the surface follows RimIjkIntersection::GridAxis axis = m_intersectionDefinition->axis(); bool neg = m_intersectionDefinition->useNegativeFace(); - const RigBoundingBoxIjk 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( 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> + auto computeFace = [&]() -> FaceType { - 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 } }; + return neg ? FaceType::NEG_I : FaceType::POS_I; case RimIjkIntersection::GridAxis::AXIS_J: - min.y() = max.y() = std::min( fixed, nj - 1 ); - return { neg ? FaceType::NEG_J : FaceType::POS_J, { min, max } }; + return neg ? FaceType::NEG_J : FaceType::POS_J; 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 } }; + return neg ? FaceType::NEG_K : FaceType::POS_K; } }; - const auto [face, requestedRange] = computeFaceAndRange(); + const FaceType face = computeFace(); - const RigBoundingBoxIjk gridBounds( caf::VecIjk0::ZERO, caf::VecIjk0( ni - 1, nj - 1, nk - 1 ) ); - - const auto visibleRange = requestedRange.clamp( gridBounds ); + const auto visibleRange = m_intersectionDefinition->clampedCellRange( m_mainGrid.p() ); if ( !visibleRange ) return; cvf::ubyte faceVtxIdx[4]; diff --git a/ApplicationLibCode/ModelVisualization/Intersections/RivIjkIntersectionPartMgr.cpp b/ApplicationLibCode/ModelVisualization/Intersections/RivIjkIntersectionPartMgr.cpp index e191f4f714..3d91a8035f 100644 --- a/ApplicationLibCode/ModelVisualization/Intersections/RivIjkIntersectionPartMgr.cpp +++ b/ApplicationLibCode/ModelVisualization/Intersections/RivIjkIntersectionPartMgr.cpp @@ -30,6 +30,7 @@ #include "RivMeshLinesSourceInfo.h" #include "RivPartPriority.h" #include "RivScalarMapperUtils.h" +#include "RivSurfaceIntersectionCurveTools.h" #include "cafEffectGenerator.h" @@ -91,7 +92,7 @@ void RivIjkIntersectionPartMgr::updateCellResultColor( int timeStepIndex ) //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -void RivIjkIntersectionPartMgr::generatePartGeometry( cvf::UByteArray* visibleCells ) +void RivIjkIntersectionPartMgr::generatePartGeometry( cvf::UByteArray* visibleCells, cvf::Transform* scaleTransform ) { bool useBufferObjects = true; // Surface geometry @@ -146,6 +147,8 @@ void RivIjkIntersectionPartMgr::generatePartGeometry( cvf::UByteArray* visibleCe } } + if ( scaleTransform ) m_annotationParts = RivSurfaceIntersectionCurveTools::createAnnotationParts( m_rimIntersection, *scaleTransform ); + updatePartEffect(); } @@ -197,6 +200,18 @@ void RivIjkIntersectionPartMgr::appendMeshLinePartsToModel( cvf::ModelBasicList* } } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RivIjkIntersectionPartMgr::appendAnnotationPartsToModel( cvf::ModelBasicList* model, cvf::Transform* scaleTransform ) +{ + for ( size_t i = 0; i < m_annotationParts.size(); i++ ) + { + m_annotationParts[i]->setTransform( scaleTransform ); + model->addPart( m_annotationParts.at( i ) ); + } +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/ModelVisualization/Intersections/RivIjkIntersectionPartMgr.h b/ApplicationLibCode/ModelVisualization/Intersections/RivIjkIntersectionPartMgr.h index 0e3b165dc2..25ada6fcff 100644 --- a/ApplicationLibCode/ModelVisualization/Intersections/RivIjkIntersectionPartMgr.h +++ b/ApplicationLibCode/ModelVisualization/Intersections/RivIjkIntersectionPartMgr.h @@ -20,6 +20,7 @@ #include "RivIjkIntersectionGeometryGenerator.h" +#include "cvfCollection.h" #include "cvfObject.h" namespace cvf @@ -46,10 +47,11 @@ public: void appendNativeIntersectionFacesToModel( cvf::ModelBasicList* model, cvf::Transform* scaleTransform ); void appendMeshLinePartsToModel( cvf::ModelBasicList* model, cvf::Transform* scaleTransform ); + void appendAnnotationPartsToModel( cvf::ModelBasicList* model, cvf::Transform* scaleTransform ); const RivIntersectionGeometryGeneratorInterface* intersectionGeometryGenerator() const; - void generatePartGeometry( cvf::UByteArray* visibleCells ); + void generatePartGeometry( cvf::UByteArray* visibleCells, cvf::Transform* scaleTransform ); private: void updatePartEffect(); @@ -59,9 +61,10 @@ private: cvf::Color3f m_defaultColor; - cvf::ref m_intersectionFaces; - cvf::ref m_intersectionGridLines; - cvf::ref m_intersectionFacesTextureCoords; + cvf::ref m_intersectionFaces; + cvf::ref m_intersectionGridLines; + cvf::Collection m_annotationParts; + cvf::ref m_intersectionFacesTextureCoords; cvf::ref m_intersectionGenerator; }; diff --git a/ApplicationLibCode/ModelVisualization/Surfaces/CMakeLists_files.cmake b/ApplicationLibCode/ModelVisualization/Surfaces/CMakeLists_files.cmake index aae47e204e..0bfae8c6a1 100644 --- a/ApplicationLibCode/ModelVisualization/Surfaces/CMakeLists_files.cmake +++ b/ApplicationLibCode/ModelVisualization/Surfaces/CMakeLists_files.cmake @@ -2,6 +2,7 @@ set(SOURCE_GROUP_SOURCE_FILES ${CMAKE_CURRENT_LIST_DIR}/RivSurfacePartMgr.cpp ${CMAKE_CURRENT_LIST_DIR}/RivSurfaceIntersectionGeometryGenerator.cpp ${CMAKE_CURRENT_LIST_DIR}/RivReservoirSurfaceIntersectionSourceInfo.cpp + ${CMAKE_CURRENT_LIST_DIR}/RivSurfaceIntersectionCurveTools.cpp ) list(APPEND CODE_SOURCE_FILES ${SOURCE_GROUP_SOURCE_FILES}) diff --git a/ApplicationLibCode/ModelVisualization/Surfaces/RivSurfaceIntersectionCurveTools.cpp b/ApplicationLibCode/ModelVisualization/Surfaces/RivSurfaceIntersectionCurveTools.cpp new file mode 100644 index 0000000000..2d31c9fc5c --- /dev/null +++ b/ApplicationLibCode/ModelVisualization/Surfaces/RivSurfaceIntersectionCurveTools.cpp @@ -0,0 +1,513 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#include "RivSurfaceIntersectionCurveTools.h" + +#include "Surface/RigSurface.h" +#include "Surface/RigSurfaceResampler.h" + +#include "Rim3dView.h" +#include "RimAnnotationLineAppearance.h" +#include "RimCase.h" +#include "RimGridView.h" +#include "RimIntersection.h" +#include "RimSurface.h" +#include "RimSurfaceIntersectionBand.h" +#include "RimSurfaceIntersectionCollection.h" +#include "RimSurfaceIntersectionCurve.h" + +#include "RivAnnotationSourceInfo.h" +#include "RivPartPriority.h" +#include "RivPolylineGenerator.h" + +#include "cafEffectGenerator.h" + +#include "cvfDrawableGeo.h" +#include "cvfGeometryBuilderDrawableGeo.h" +#include "cvfPart.h" +#include "cvfRenderStatePolygonOffset.h" +#include "cvfTransform.h" + +#include +#include +#include +#include + +namespace +{ +// Distance between the points the footprint is resampled to before the surface is looked up +const double maxLineSegmentLength = 1.0; + +// Added to the extent a pillar is continued to, to keep a surface touching the end inside the search +const double pillarExtensionMargin = 1.0; + +//-------------------------------------------------------------------------------------------------- +/// Move a point onto the line through the pillar, at the depth of the point. The point is allowed to +/// fall outside the pillar itself, so a surface above or below the intersection is placed along the +/// continuation of the pillar rather than beside it. +//-------------------------------------------------------------------------------------------------- +cvf::Vec3d projectOntoPillarLine( const cvf::Vec3d& pillarTop, const cvf::Vec3d& pillarBottom, const cvf::Vec3d& point ) +{ + const double pillarHeight = pillarTop.z() - pillarBottom.z(); + if ( std::fabs( pillarHeight ) < 1.0e-9 ) return point; + + const double t = ( pillarTop.z() - point.z() ) / pillarHeight; + + return pillarTop + t * ( pillarBottom - pillarTop ); +} + +//-------------------------------------------------------------------------------------------------- +/// The depth range covered by a surface, used to decide how far a pillar has to be continued +//-------------------------------------------------------------------------------------------------- +std::pair surfaceZRange( const RigSurface& surface ) +{ + double lowZ = std::numeric_limits::max(); + double highZ = -std::numeric_limits::max(); + + for ( const auto& vertex : surface.vertices() ) + { + lowZ = std::min( lowZ, vertex.z() ); + highZ = std::max( highZ, vertex.z() ); + } + + return { lowZ, highZ }; +} + +//-------------------------------------------------------------------------------------------------- +/// Find where the surface crosses the pillar. A surface that does not cross the curtain is estimated +/// by continuing the pillar in both directions, keeping the tilt of the pillar, so the curve carries +/// on past the extent of the intersection instead of stopping. The pillar is continued only as far as +/// the surface reaches, to keep the search for candidate triangles small. +//-------------------------------------------------------------------------------------------------- +bool findSurfacePointOnPillar( RigSurface& surface, + const cvf::Vec3d& pillarTop, + const cvf::Vec3d& pillarBottom, + double surfaceLowZ, + double surfaceHighZ, + cvf::Vec3d& point ) +{ + if ( RigSurfaceResampler::computeIntersectionWithLine( &surface, pillarTop, pillarBottom, point ) ) return true; + + if ( std::fabs( pillarTop.z() - pillarBottom.z() ) > 1.0e-9 ) + { + const double searchHighZ = std::max( std::max( pillarTop.z(), pillarBottom.z() ), surfaceHighZ + pillarExtensionMargin ); + const double searchLowZ = std::min( std::min( pillarTop.z(), pillarBottom.z() ), surfaceLowZ - pillarExtensionMargin ); + + const cvf::Vec3d extendedTop = projectOntoPillarLine( pillarTop, pillarBottom, cvf::Vec3d( 0.0, 0.0, searchHighZ ) ); + const cvf::Vec3d extendedBottom = projectOntoPillarLine( pillarTop, pillarBottom, cvf::Vec3d( 0.0, 0.0, searchLowZ ) ); + + if ( RigSurfaceResampler::computeIntersectionWithLine( &surface, extendedTop, extendedBottom, point ) ) return true; + } + + // The surface may not cover this position at all. Search in the XY plane around the pillar itself, as + // a continued pillar would move the search away from the intersection. + return RigSurfaceResampler::findClosestPointOnSurface( &surface, pillarTop, pillarBottom, point ); +} +} // namespace + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector> RivSurfaceCurtainPolyline::validRuns() const +{ + std::vector> runs; + + std::vector currentRun; + for ( const auto& point : points ) + { + if ( !point.isUndefined() ) + { + currentRun.push_back( point ); + } + else if ( currentRun.size() > 1 ) + { + runs.push_back( currentRun ); + currentRun.clear(); + } + else + { + currentRun.clear(); + } + } + + if ( currentRun.size() > 1 ) runs.push_back( currentRun ); + + return runs; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector RivSurfaceIntersectionCurveTools::referencedSurfaces( const RimSurfaceIntersectionCollection* surfaceIntersections ) +{ + if ( !surfaceIntersections ) return {}; + + std::vector surfaces; + + auto appendSurface = [&surfaces]( RimSurface* surface ) + { + if ( !surface ) return; + if ( std::find( surfaces.begin(), surfaces.end(), surface ) != surfaces.end() ) return; + + surfaces.push_back( surface ); + }; + + for ( auto curve : surfaceIntersections->surfaceIntersectionCurves() ) + { + appendSurface( curve->surface() ); + } + + for ( auto band : surfaceIntersections->surfaceIntersectionBands() ) + { + appendSurface( band->surface1() ); + appendSurface( band->surface2() ); + } + + return surfaces; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::map + RivSurfaceIntersectionCurveTools::computeSurfaceCurtainPolylines( const std::vector& surfaces, + const RimIntersectionCurtain& curtain, + const std::function& pointTransform ) +{ + std::map surfacePolylines; + + if ( !curtain.isValid() ) return surfacePolylines; + + const auto resampledTrace = RigSurfaceResampler::computeResampledPolylineWithSegmentInfo( curtain.trace, maxLineSegmentLength ); + if ( resampledTrace.empty() ) return surfacePolylines; + + // Both ends of a pillar are interpolated using the position of the resampled point within its trace + // segment, so a resampled pillar stays on the curtain + std::vector> resampledPillars; + std::vector segmentIndices; + resampledPillars.reserve( resampledTrace.size() ); + segmentIndices.reserve( resampledTrace.size() ); + + for ( const auto& [point, segmentIndex] : resampledTrace ) + { + if ( segmentIndex + 1 >= curtain.pillars.size() ) continue; + + const auto& traceStart = curtain.trace[segmentIndex]; + const auto& traceEnd = curtain.trace[segmentIndex + 1]; + + const double segmentLength = ( traceEnd - traceStart ).length(); + const double t = segmentLength > 0.0 ? std::clamp( ( point - traceStart ).length() / segmentLength, 0.0, 1.0 ) : 0.0; + + const auto& pillarStart = curtain.pillars[segmentIndex]; + const auto& pillarEnd = curtain.pillars[segmentIndex + 1]; + + const cvf::Vec3d top = pillarStart.first + t * ( pillarEnd.first - pillarStart.first ); + const cvf::Vec3d bottom = pillarStart.second + t * ( pillarEnd.second - pillarStart.second ); + + resampledPillars.emplace_back( top, bottom ); + segmentIndices.push_back( segmentIndex ); + } + + for ( auto rimSurface : surfaces ) + { + if ( !rimSurface ) continue; + + rimSurface->loadDataIfRequired(); + auto surface = rimSurface->surfaceData(); + if ( !surface ) continue; + + RivSurfaceCurtainPolyline curtainPolyline; + curtainPolyline.points.reserve( resampledPillars.size() ); + + const auto [surfaceLowZ, surfaceHighZ] = surfaceZRange( *surface ); + + bool anyValidPoint = false; + + for ( size_t i = 0; i < resampledPillars.size(); i++ ) + { + const auto& [pillarTop, pillarBottom] = resampledPillars[i]; + + cvf::Vec3d intersectionPoint; + const bool isValid = findSurfacePointOnPillar( *surface, pillarTop, pillarBottom, surfaceLowZ, surfaceHighZ, intersectionPoint ); + + // A point is always appended, also when the surface was not hit, to keep the two curves of a band index aligned + if ( isValid ) + { + intersectionPoint = pointTransform( projectOntoPillarLine( pillarTop, pillarBottom, intersectionPoint ), segmentIndices[i] ); + } + else + { + intersectionPoint = cvf::Vec3d::UNDEFINED; + } + + curtainPolyline.points.push_back( intersectionPoint ); + + anyValidPoint = anyValidPoint || isValid; + } + + if ( anyValidPoint ) surfacePolylines[rimSurface] = curtainPolyline; + } + + return surfacePolylines; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +cvf::Collection + RivSurfaceIntersectionCurveTools::createAnnotationParts( const RimSurfaceIntersectionCollection* surfaceIntersections, + const std::map& surfacePolylines, + cvf::Transform& scaleTransform ) +{ + cvf::Collection parts; + + if ( !surfaceIntersections ) return parts; + + auto appendParts = [&parts]( cvf::Collection& partsToAppend ) + { + for ( size_t i = 0; i < partsToAppend.size(); i++ ) + { + parts.push_back( partsToAppend.at( i ) ); + } + }; + + for ( auto curve : surfaceIntersections->surfaceIntersectionCurves() ) + { + if ( !curve->isChecked() ) continue; + + auto surface = curve->surface(); + if ( !surface ) continue; + + auto it = surfacePolylines.find( surface ); + if ( it == surfacePolylines.end() ) continue; + + auto curveParts = createCurveParts( it->second, + surface->fullName(), + curve->lineAppearance()->color(), + curve->lineAppearance()->thickness(), + scaleTransform ); + appendParts( curveParts ); + } + + for ( auto band : surfaceIntersections->surfaceIntersectionBands() ) + { + if ( !band->isChecked() ) continue; + + auto surface1 = band->surface1(); + auto surface2 = band->surface2(); + if ( !surface1 || !surface2 ) continue; + + auto it1 = surfacePolylines.find( surface1 ); + auto it2 = surfacePolylines.find( surface2 ); + if ( it1 == surfacePolylines.end() || it2 == surfacePolylines.end() ) continue; + + const auto& polylineA = it1->second; + const auto& polylineB = it2->second; + + auto curvePartsA = + createCurveParts( polylineA, surface1->fullName(), band->lineAppearance()->color(), band->lineAppearance()->thickness(), scaleTransform ); + appendParts( curvePartsA ); + + auto curvePartsB = + createCurveParts( polylineB, surface2->fullName(), band->lineAppearance()->color(), band->lineAppearance()->thickness(), scaleTransform ); + appendParts( curvePartsB ); + + auto bandPart = createBandPart( polylineA, polylineB, band->bandColor(), band->bandOpacity(), band->polygonOffsetUnit() ); + if ( bandPart.notNull() ) parts.push_back( bandPart.p() ); + } + + return parts; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +cvf::Collection RivSurfaceIntersectionCurveTools::createAnnotationParts( const RimIntersection* intersection, + cvf::Transform& scaleTransform ) +{ + if ( !intersection || !intersection->supportsSurfaceIntersectionCurves() ) return {}; + + const auto surfaces = referencedSurfaces( intersection->surfaceIntersectionCollection() ); + if ( surfaces.empty() ) return {}; + + const auto curtain = intersection->surfaceCurtain(); + if ( !curtain.isValid() ) return {}; + + // The visualization parts are built in display coordinates + cvf::Vec3d displayOffset( 0.0, 0.0, 0.0 ); + { + auto gridView = intersection->firstAncestorOrThisOfType(); + if ( gridView && gridView->ownerCase() ) displayOffset = gridView->ownerCase()->displayModelOffset(); + } + + auto pointTransform = [displayOffset]( const cvf::Vec3d& point, size_t segmentIndex ) { return point - displayOffset; }; + + const auto surfacePolylines = computeSurfaceCurtainPolylines( surfaces, curtain, pointTransform ); + + return createAnnotationParts( intersection->surfaceIntersectionCollection(), surfacePolylines, scaleTransform ); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +cvf::Collection RivSurfaceIntersectionCurveTools::createCurveParts( const RivSurfaceCurtainPolyline& polyline, + const QString& description, + const cvf::Color3f& color, + float lineWidth, + cvf::Transform& scaleTransform ) +{ + cvf::Collection parts; + + std::vector allDisplayCoords; + + for ( const auto& run : polyline.validRuns() ) + { + auto part = createCurvePart( run, color, lineWidth ); + if ( part.isNull() ) continue; + + part->setName( "Intersection " + description.toStdString() ); + parts.push_back( part.p() ); + + // The polylines are defined in the display coordinate system without Z-scaling. The z-scaling is applied to the + // visualization parts using Part::setTransform(Transform* transform) + // The annotation objects are defined by display coordinates, so apply the Z-scaling to the coordinates + const auto& mat = scaleTransform.worldTransform(); + for ( const auto& p : run ) + { + allDisplayCoords.push_back( p.getTransformedPoint( mat ) ); + } + } + + if ( !parts.empty() && !allDisplayCoords.empty() ) + { + // Add annotation info to be used to display label in Rim3dView::onViewNavigationChanged() + // Set the source info on one part only, as this data is only used for display of labels + auto annoObj = new RivAnnotationSourceInfo( description.toStdString(), allDisplayCoords ); + annoObj->setLabelPositionStrategyHint( RivAnnotationTools::LabelPositionStrategy::RIGHT ); + annoObj->setShowColor( true ); + annoObj->setColor( color ); + + parts[0]->setSourceInfo( annoObj ); + } + + return parts; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +cvf::ref + RivSurfaceIntersectionCurveTools::createCurvePart( const std::vector& polyline, const cvf::Color3f& color, float lineWidth ) +{ + auto polylineGeo = RivPolylineGenerator::createLineAlongPolylineDrawable( polyline ); + if ( polylineGeo.isNull() ) return nullptr; + + polylineGeo->setRenderMode( cvf::DrawableGeo::BUFFER_OBJECT ); + + cvf::ref part = new cvf::Part; + part->setDrawable( polylineGeo.p() ); + + part->updateBoundingBox(); + part->setPriority( RivPartPriority::PartType::FaultMeshLines ); + + caf::MeshEffectGenerator lineEffGen( color ); + lineEffGen.setLineWidth( lineWidth ); + + cvf::ref eff = lineEffGen.generateUnCachedEffect(); + + cvf::ref polyOffset = new cvf::RenderStatePolygonOffset; + polyOffset->enableFillMode( true ); + polyOffset->setFactor( -5 ); + const double maxOffsetFactor = -1000; + polyOffset->setUnits( maxOffsetFactor ); + + eff->setRenderState( polyOffset.p() ); + + part->setEffect( eff.p() ); + + return part; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +cvf::ref RivSurfaceIntersectionCurveTools::createBandPart( const RivSurfaceCurtainPolyline& polylineA, + const RivSurfaceCurtainPolyline& polylineB, + const cvf::Color3f& color, + float opacity, + double polygonOffsetUnit ) +{ + const size_t pointCount = std::min( polylineA.points.size(), polylineB.points.size() ); + if ( pointCount < 2 ) return nullptr; + + cvf::GeometryBuilderDrawableGeo geoBuilder; + + bool anyQuad = false; + for ( size_t i = 1; i < pointCount; i++ ) + { + const auto& pA0 = polylineA.points[i - 1]; + const auto& pA1 = polylineA.points[i]; + const auto& pB0 = polylineB.points[i - 1]; + const auto& pB1 = polylineB.points[i]; + + // A quad requires both surfaces to be present at both ends of the segment + if ( pA0.isUndefined() || pA1.isUndefined() ) continue; + if ( pB0.isUndefined() || pB1.isUndefined() ) continue; + + geoBuilder.addQuadByVertices( cvf::Vec3f( pA0 ), cvf::Vec3f( pA1 ), cvf::Vec3f( pB1 ), cvf::Vec3f( pB0 ) ); + anyQuad = true; + } + + if ( !anyQuad ) return nullptr; + + cvf::ref geo = geoBuilder.drawableGeo(); + if ( geo.isNull() ) return nullptr; + + geo->computeNormals(); + geo->setRenderMode( cvf::DrawableGeo::BUFFER_OBJECT ); + + cvf::ref part = new cvf::Part; + part->setName( "Surface Intersection Band" ); + part->setDrawable( geo.p() ); + part->updateBoundingBox(); + part->setEnableMask( intersectionCellFaceBit ); + part->setPriority( RivPartPriority::PartType::Transparent ); + + caf::SurfaceEffectGenerator geometryEffgen( cvf::Color4f( color, opacity ), caf::PO_NEG_LARGE ); + + cvf::ref geometryOnlyEffect = geometryEffgen.generateUnCachedEffect(); + + { + cvf::ref polyOffset = new cvf::RenderStatePolygonOffset; + + polyOffset->enableFillMode( true ); + + // The factor value is defined by enums in EffectGenerator::createAndConfigurePolygonOffsetRenderState() + // Use a factor that is more negative than the existing enums + const double offsetFactor = -5; + polyOffset->setFactor( offsetFactor ); + + polyOffset->setUnits( polygonOffsetUnit ); + + geometryOnlyEffect->setRenderState( polyOffset.p() ); + } + + part->setEffect( geometryOnlyEffect.p() ); + + return part; +} diff --git a/ApplicationLibCode/ModelVisualization/Surfaces/RivSurfaceIntersectionCurveTools.h b/ApplicationLibCode/ModelVisualization/Surfaces/RivSurfaceIntersectionCurveTools.h new file mode 100644 index 0000000000..ef00f7f815 --- /dev/null +++ b/ApplicationLibCode/ModelVisualization/Surfaces/RivSurfaceIntersectionCurveTools.h @@ -0,0 +1,102 @@ +///////////////////////////////////////////////////////////////////////////////// +// +// 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 +// for more details. +// +///////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "RimIntersection.h" + +#include "cvfCollection.h" +#include "cvfColor3.h" +#include "cvfObject.h" +#include "cvfVector3.h" + +#include + +#include +#include +#include + +class RimSurface; +class RimSurfaceIntersectionCollection; + +namespace cvf +{ +class Part; +class Transform; +} // namespace cvf + +//================================================================================================== +/// The curve created when a surface is projected onto an intersection. One entry per resampled +/// position along the intersection, set to cvf::Vec3d::UNDEFINED where the pillar at that position +/// missed the surface. The missing points are kept in the array instead of being removed, so the two +/// curves of a band stay index aligned. +//================================================================================================== +class RivSurfaceCurtainPolyline +{ +public: + std::vector points; + + /// The stretches of consecutive valid points that are long enough to draw a line + std::vector> validRuns() const; +}; + +//================================================================================================== +/// Creation of surface intersection curves and bands. Shared by all intersection types that produce +/// a curtain, and that therefore can find the curve by looking the surface up along the pillar the +/// curtain is spanned between at each position along the intersection. +//================================================================================================== +class RivSurfaceIntersectionCurveTools +{ +public: + static std::vector referencedSurfaces( const RimSurfaceIntersectionCollection* surfaceIntersections ); + + /// The surface is looked up along the pillars of the curtain, so the curve follows a tilted + /// curtain instead of a vertical plane through it. The curtain is resampled along its trace to + /// the resolution the curve is drawn at, and the pillars are interpolated to match. + /// + /// pointTransform maps (worldPoint, traceSegmentIndex) to the output coordinate system. Use the + /// identity for intersections drawn in 3D only, or the flattening transform when the curve is + /// also displayed in a 2D intersection view. + static std::map + computeSurfaceCurtainPolylines( const std::vector& surfaces, + const RimIntersectionCurtain& curtain, + const std::function& pointTransform ); + + static cvf::Collection createAnnotationParts( const RimSurfaceIntersectionCollection* surfaceIntersections, + const std::map& surfacePolylines, + cvf::Transform& scaleTransform ); + + /// Curves and bands for an intersection that is drawn in 3D only, computed from the pillars + /// reported by the intersection itself + static cvf::Collection createAnnotationParts( const RimIntersection* intersection, cvf::Transform& scaleTransform ); + +private: + static cvf::Collection createCurveParts( const RivSurfaceCurtainPolyline& polyline, + const QString& description, + const cvf::Color3f& color, + float lineWidth, + cvf::Transform& scaleTransform ); + + static cvf::ref createCurvePart( const std::vector& polyline, const cvf::Color3f& color, float lineWidth ); + + static cvf::ref createBandPart( const RivSurfaceCurtainPolyline& polylineA, + const RivSurfaceCurtainPolyline& polylineB, + const cvf::Color3f& color, + float opacity, + double polygonOffsetUnit ); +}; diff --git a/ApplicationLibCode/ProjectDataModel/Intersections/RimBoxIntersection.cpp b/ApplicationLibCode/ProjectDataModel/Intersections/RimBoxIntersection.cpp index 6ec5f151c7..4eb47a66c1 100644 --- a/ApplicationLibCode/ProjectDataModel/Intersections/RimBoxIntersection.cpp +++ b/ApplicationLibCode/ProjectDataModel/Intersections/RimBoxIntersection.cpp @@ -28,10 +28,12 @@ #include "RivBoxIntersectionPartMgr.h" +#include "cafCmdFeatureMenuBuilder.h" #include "cafDisplayCoordTransform.h" #include "cafPdmUiDoubleSliderEditor.h" #include "cafPdmUiPushButtonEditor.h" #include "cafPdmUiSliderEditor.h" +#include "cafPdmUiTreeOrdering.h" namespace caf { @@ -188,6 +190,35 @@ RimBoxIntersection::SinglePlaneState RimBoxIntersection::singlePlaneState() cons return m_singlePlaneState(); } +//-------------------------------------------------------------------------------------------------- +/// Only the single plane states X and Y give a vertical curtain. A depth slice, and the full box, +/// would require the surface to be contoured instead of projected along a vertical ray. +//-------------------------------------------------------------------------------------------------- +bool RimBoxIntersection::supportsSurfaceIntersectionCurves() const +{ + return m_singlePlaneState() == PLANE_STATE_X || m_singlePlaneState() == PLANE_STATE_Y; +} + +//-------------------------------------------------------------------------------------------------- +/// The pillars span the depth range of the box, so the curve is clipped to the box face. The depth +/// fields are positive downwards, while z is positive upwards. +//-------------------------------------------------------------------------------------------------- +RimIntersectionCurtain RimBoxIntersection::surfaceCurtain() const +{ + std::vector trace; + + if ( m_singlePlaneState() == PLANE_STATE_X ) + { + trace = { cvf::Vec3d( m_minXCoord, m_minYCoord, 0.0 ), cvf::Vec3d( m_minXCoord, m_maxYCoord, 0.0 ) }; + } + else if ( m_singlePlaneState() == PLANE_STATE_Y ) + { + trace = { cvf::Vec3d( m_minXCoord, m_minYCoord, 0.0 ), cvf::Vec3d( m_maxXCoord, m_minYCoord, 0.0 ) }; + } + + return verticalCurtain( trace, -m_minDepth(), -m_maxDepth() ); +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- @@ -504,6 +535,26 @@ void RimBoxIntersection::defineUiOrdering( QString uiConfigName, caf::PdmUiOrder defineSeparateDataSourceUi( uiConfigName, uiOrdering ); } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimBoxIntersection::appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const +{ + appendCommonMenuItems( menuBuilder ); + + menuBuilder << "RicCopyIntersectionsToAllViewsInCaseFeature"; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimBoxIntersection::defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering, QString uiConfigName ) +{ + appendSurfaceIntersectionsToTreeOrdering( uiTreeOrdering ); + + uiTreeOrdering.skipRemainingChildren( true ); +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/ProjectDataModel/Intersections/RimBoxIntersection.h b/ApplicationLibCode/ProjectDataModel/Intersections/RimBoxIntersection.h index 251a7bafe7..f2c88fc187 100644 --- a/ApplicationLibCode/ProjectDataModel/Intersections/RimBoxIntersection.h +++ b/ApplicationLibCode/ProjectDataModel/Intersections/RimBoxIntersection.h @@ -80,12 +80,20 @@ public: const RivIntersectionGeometryGeneratorInterface* intersectionGeometryGenerator() const override; + bool supportsSurfaceIntersectionCurves() const override; + RimIntersectionCurtain surfaceCurtain() const override; + + void rebuildGeometryAndScheduleCreateDisplayModel() override; + + void appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) 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; + void defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering, QString uiConfigName = "" ) override; void initAfterRead() override; protected slots: @@ -96,7 +104,6 @@ private: friend class RimIntersectionCollection; void updateBoxManipulatorGeometry(); - void rebuildGeometryAndScheduleCreateDisplayModel(); void updateVisibility(); void clampSinglePlaneValues(); void switchSingelPlaneState(); diff --git a/ApplicationLibCode/ProjectDataModel/Intersections/RimExtrudedCurveIntersection.cpp b/ApplicationLibCode/ProjectDataModel/Intersections/RimExtrudedCurveIntersection.cpp index 480720ada4..b6ad787a0b 100644 --- a/ApplicationLibCode/ProjectDataModel/Intersections/RimExtrudedCurveIntersection.cpp +++ b/ApplicationLibCode/ProjectDataModel/Intersections/RimExtrudedCurveIntersection.cpp @@ -47,6 +47,7 @@ #include "RivExtrudedCurveIntersectionPartMgr.h" +#include "cafCmdFeatureMenuBuilder.h" #include "cafPdmFieldScriptingCapability.h" #include "cafPdmFieldScriptingCapabilityCvfVec3d.h" #include "cafPdmObjectScriptingCapability.h" @@ -253,10 +254,6 @@ RimExtrudedCurveIntersection::RimExtrudedCurveIntersection() caf::PdmUiPushButtonEditor::configureEditorLabelLeft( &m_inputTwoAzimuthPointsFromViewerEnabled ); m_inputTwoAzimuthPointsFromViewerEnabled = false; - CAF_PDM_InitFieldNoDefault( &m_surfaceIntersections, "SurfaceIntersections", "Surface Intersections" ); - m_surfaceIntersections = new RimSurfaceIntersectionCollection; - m_surfaceIntersections->objectChanged.connect( this, &RimExtrudedCurveIntersection::onSurfaceIntersectionsChanged ); - CAF_PDM_InitField( &m_depthUpperThreshold, "UpperThreshold", -300000.0, "Upper Threshold" ); m_depthUpperThreshold.uiCapability()->setUiEditorTypeName( caf::PdmUiDoubleSliderEditor::uiEditorTypeName() ); @@ -731,19 +728,26 @@ QList RimExtrudedCurveIntersection::calculateValueOption return options; } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimExtrudedCurveIntersection::appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const +{ + appendCommonMenuItems( menuBuilder ); + + menuBuilder << "RicSeismicSectionFromIntersectionFeature"; + menuBuilder.addSeparator(); + menuBuilder << "RicNewIntersectionViewFeature"; + menuBuilder.addSeparator(); + menuBuilder << "RicCopyIntersectionsToAllViewsInCaseFeature"; +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RimExtrudedCurveIntersection::defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering, QString uiConfigName ) { - for ( auto c : m_surfaceIntersections->surfaceIntersectionCurves() ) - { - uiTreeOrdering.add( c ); - } - for ( auto c : m_surfaceIntersections->surfaceIntersectionBands() ) - { - uiTreeOrdering.add( c ); - } + appendSurfaceIntersectionsToTreeOrdering( uiTreeOrdering ); uiTreeOrdering.skipRemainingChildren( true ); } @@ -1298,33 +1302,24 @@ bool RimExtrudedCurveIntersection::showIntersectionGeometry() const //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -std::vector RimExtrudedCurveIntersection::surfaceIntersectionCurves() const +bool RimExtrudedCurveIntersection::supportsSurfaceIntersectionCurves() const { - return m_surfaceIntersections->surfaceIntersectionCurves(); + return true; } //-------------------------------------------------------------------------------------------------- -/// +/// The first polyline is used, as the surface intersection curve follows the same line as the +/// intersection geometry itself. The curtain is treated as a vertical extrusion, also when the +/// intersection has a tilted extrusion direction. //-------------------------------------------------------------------------------------------------- -std::vector RimExtrudedCurveIntersection::surfaceIntersectionBands() const +RimIntersectionCurtain RimExtrudedCurveIntersection::surfaceCurtain() const { - return m_surfaceIntersections->surfaceIntersectionBands(); -} + auto lines = polyLines(); + if ( lines.empty() ) return {}; -//-------------------------------------------------------------------------------------------------- -/// -//-------------------------------------------------------------------------------------------------- -RimSurfaceIntersectionCurve* RimExtrudedCurveIntersection::addIntersectionCurve() -{ - return m_surfaceIntersections->addIntersectionCurve(); -} + const double extent = defaultCurtainExtent(); -//-------------------------------------------------------------------------------------------------- -/// -//-------------------------------------------------------------------------------------------------- -RimSurfaceIntersectionBand* RimExtrudedCurveIntersection::addIntersectionBand() -{ - return m_surfaceIntersections->addIntersectionBand(); + return verticalCurtain( lines.front(), extent, -extent ); } //-------------------------------------------------------------------------------------------------- @@ -1388,15 +1383,6 @@ void RimExtrudedCurveIntersection::appendOptionItemsForSources( int } } -//-------------------------------------------------------------------------------------------------- -/// -//-------------------------------------------------------------------------------------------------- -void RimExtrudedCurveIntersection::onSurfaceIntersectionsChanged( const caf::SignalEmitter* emitter ) -{ - updateAllRequiredEditors(); - rebuildGeometryAndScheduleCreateDisplayModel(); -} - //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/ProjectDataModel/Intersections/RimExtrudedCurveIntersection.h b/ApplicationLibCode/ProjectDataModel/Intersections/RimExtrudedCurveIntersection.h index 95f68ac58f..f375a16adf 100644 --- a/ApplicationLibCode/ProjectDataModel/Intersections/RimExtrudedCurveIntersection.h +++ b/ApplicationLibCode/ProjectDataModel/Intersections/RimExtrudedCurveIntersection.h @@ -129,15 +129,15 @@ public: double extentLength(); bool hasDefiningPoints() const; - std::vector surfaceIntersectionCurves() const; - std::vector surfaceIntersectionBands() const; - RimSurfaceIntersectionCurve* addIntersectionCurve(); - RimSurfaceIntersectionBand* addIntersectionBand(); + bool supportsSurfaceIntersectionCurves() const override; + RimIntersectionCurtain surfaceCurtain() const override; bool showIntersectionGeometry() const; int branchIndex() const; - void rebuildGeometryAndScheduleCreateDisplayModel(); + void rebuildGeometryAndScheduleCreateDisplayModel() override; + + void appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const override; private: caf::PdmFieldHandle* userDescriptionField() final; @@ -159,8 +159,6 @@ private: void appendOptionItemsForSources( int currentLevel, RimSurfaceCollection* currentCollection, QList& options ) const; - void onSurfaceIntersectionsChanged( const caf::SignalEmitter* emitter ); - std::vector pointsXYD() const; void setPointsFromXYD( const std::vector& pointsXYD ); @@ -207,8 +205,6 @@ private: caf::PdmField> m_customExtrusionPoints; caf::PdmField> m_twoAzimuthPoints; - caf::PdmChildField m_surfaceIntersections; - cvf::ref m_crossSectionPartMgr; std::vector> m_simulationWellBranchCenterlines; diff --git a/ApplicationLibCode/ProjectDataModel/Intersections/RimIjkIntersection.cpp b/ApplicationLibCode/ProjectDataModel/Intersections/RimIjkIntersection.cpp index f3d5d09d93..3e223ec1cf 100644 --- a/ApplicationLibCode/ProjectDataModel/Intersections/RimIjkIntersection.cpp +++ b/ApplicationLibCode/ProjectDataModel/Intersections/RimIjkIntersection.cpp @@ -25,7 +25,9 @@ #include "RivIjkIntersectionPartMgr.h" +#include "cafCmdFeatureMenuBuilder.h" #include "cafPdmUiSliderEditor.h" +#include "cafPdmUiTreeOrdering.h" #include @@ -201,6 +203,129 @@ void RimIjkIntersection::setToDefaultValues() m_fixedIndex = nk / 2 + 1; } +//-------------------------------------------------------------------------------------------------- +/// The I and J axes give a vertical curtain. A K slice is horizontal, and would require the surface +/// to be contoured instead of projected along a vertical ray. +//-------------------------------------------------------------------------------------------------- +bool RimIjkIntersection::supportsSurfaceIntersectionCurves() const +{ + return m_axis() != GridAxis::AXIS_K; +} + +//-------------------------------------------------------------------------------------------------- +/// Walk the cells of the index plane along the varying axis and pick the cell face corners. Each +/// pillar runs from the top of the first k layer to the bottom of the last k layer, so a tilted +/// pillar gives a curve that lies on the curtain rather than on a vertical plane through it. +//-------------------------------------------------------------------------------------------------- +RimIntersectionCurtain RimIjkIntersection::surfaceCurtain() const +{ + if ( !supportsSurfaceIntersectionCurves() ) return {}; + + RigMainGrid* grid = mainGrid(); + if ( !grid ) return {}; + + const auto cellRange = clampedCellRange( grid ); + if ( !cellRange ) return {}; + + const bool alongJ = m_axis() == GridAxis::AXIS_I; + + // Corner indices of the cell face, see the hex vertex numbering in StructGridInterface::cellFaceVertexIndices(). + // The first corner is at the start of the varying axis, the second at the end of the same axis. Corners 0-3 are + // the low k side of the cell, and adding 4 gives the corner on the same pillar at the high k side. + const int cornersPerKSide = 4; + + int startCorner = 0; + int endCorner = 0; + if ( alongJ ) + { + startCorner = m_useNegativeFace() ? 0 : 1; + endCorner = m_useNegativeFace() ? 3 : 2; + } + else + { + startCorner = m_useNegativeFace() ? 0 : 3; + endCorner = m_useNegativeFace() ? 1 : 2; + } + + // The fixed axis is collapsed to a single index by clampedCellRange() + const size_t fixedI = cellRange->min().i(); + const size_t fixedJ = cellRange->min().j(); + + const size_t firstK = cellRange->min().k(); + const size_t lastK = cellRange->max().k(); + + const size_t varyingMin = alongJ ? cellRange->min().j() : cellRange->min().i(); + const size_t varyingMax = alongJ ? cellRange->max().j() : cellRange->max().i(); + + RimIntersectionCurtain curtain; + + // The pillar at one position is spanned between the first and the last cell of the k range. The + // top of the pillar is used as the trace, so the curve is resampled along the top of the curtain. + auto appendPillar = [&]( size_t i, size_t j, int corner ) + { + const size_t topCellIndex = grid->cellIndexFromIJK( i, j, firstK ); + const size_t bottomCellIndex = grid->cellIndexFromIJK( i, j, lastK ); + + if ( topCellIndex == cvf::UNDEFINED_SIZE_T || bottomCellIndex == cvf::UNDEFINED_SIZE_T ) return; + if ( grid->cell( topCellIndex ).isInvalid() || grid->cell( bottomCellIndex ).isInvalid() ) return; + + const auto topCorners = grid->cellCornerVertices( topCellIndex ); + const auto bottomCorners = grid->cellCornerVertices( bottomCellIndex ); + + curtain.trace.push_back( topCorners[corner] ); + curtain.pillars.emplace_back( topCorners[corner], bottomCorners[corner + cornersPerKSide] ); + }; + + for ( size_t varying = varyingMin; varying <= varyingMax; ++varying ) + { + const size_t i = alongJ ? fixedI : varying; + const size_t j = alongJ ? varying : fixedJ; + + appendPillar( i, j, startCorner ); + + if ( varying == varyingMax ) appendPillar( i, j, endCorner ); + } + + return curtain; +} + +//-------------------------------------------------------------------------------------------------- +/// The index range of the visible cells, with the fixed axis collapsed to the fixed index +//-------------------------------------------------------------------------------------------------- +std::optional> RimIjkIntersection::clampedCellRange( const RigMainGrid* grid ) const +{ + if ( !grid ) return {}; + + const size_t ni = grid->cellCountI(); + const size_t nj = grid->cellCountJ(); + const size_t nk = grid->cellCountK(); + if ( ni == 0 || nj == 0 || nk == 0 ) return {}; + + const auto range = ijkRange(); + + caf::VecIjk0 min = range.min(); + caf::VecIjk0 max = range.max(); + const size_t fixed = static_cast( std::max( 0, fixedIndex() ) ); + + switch ( m_axis() ) + { + case GridAxis::AXIS_I: + min.x() = max.x() = std::min( fixed, ni - 1 ); + break; + case GridAxis::AXIS_J: + min.y() = max.y() = std::min( fixed, nj - 1 ); + break; + case GridAxis::AXIS_K: + default: + min.z() = max.z() = std::min( fixed, nk - 1 ); + break; + } + + const RigBoundingBoxIjk gridBounds( caf::VecIjk0::ZERO, caf::VecIjk0( ni - 1, nj - 1, nk - 1 ) ); + + return RigBoundingBoxIjk( min, max ).clamp( gridBounds ); +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- @@ -347,6 +472,26 @@ void RimIjkIntersection::defineUiOrdering( QString uiConfigName, caf::PdmUiOrder uiOrdering.skipRemainingFields( true ); } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimIjkIntersection::appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const +{ + appendCommonMenuItems( menuBuilder ); + + menuBuilder << "RicCopyIntersectionsToAllViewsInCaseFeature"; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimIjkIntersection::defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering, QString uiConfigName ) +{ + appendSurfaceIntersectionsToTreeOrdering( uiTreeOrdering ); + + uiTreeOrdering.skipRemainingChildren( true ); +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/ProjectDataModel/Intersections/RimIjkIntersection.h b/ApplicationLibCode/ProjectDataModel/Intersections/RimIjkIntersection.h index 65137243ed..a026afeed9 100644 --- a/ApplicationLibCode/ProjectDataModel/Intersections/RimIjkIntersection.h +++ b/ApplicationLibCode/ProjectDataModel/Intersections/RimIjkIntersection.h @@ -26,6 +26,8 @@ #include "cafPdmField.h" #include "cafVecIjk.h" +#include + class RigMainGrid; class RivIjkIntersectionPartMgr; @@ -75,16 +77,27 @@ public: const RivIntersectionGeometryGeneratorInterface* intersectionGeometryGenerator() const override; + bool supportsSurfaceIntersectionCurves() const override; + RimIntersectionCurtain surfaceCurtain() const override; + + void rebuildGeometryAndScheduleCreateDisplayModel() override; + + void appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const override; + + /// The index range of the visible cells, with the fixed axis collapsed to the fixed index. The + /// grid is passed in, as the intersection is not always reachable from a view. + std::optional> clampedCellRange( const RigMainGrid* grid ) const; + 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; + void defineUiTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering, QString uiConfigName = "" ) override; private: - int axisCellCount() const; - void rebuildGeometryAndScheduleCreateDisplayModel(); + int axisCellCount() const; private: caf::PdmField m_name; diff --git a/ApplicationLibCode/ProjectDataModel/Intersections/RimIntersection.cpp b/ApplicationLibCode/ProjectDataModel/Intersections/RimIntersection.cpp index 5eabceaf88..c34958cd10 100644 --- a/ApplicationLibCode/ProjectDataModel/Intersections/RimIntersection.cpp +++ b/ApplicationLibCode/ProjectDataModel/Intersections/RimIntersection.cpp @@ -28,11 +28,16 @@ #include "RimGridView.h" #include "RimIntersectionResultDefinition.h" #include "RimIntersectionResultsDefinitionCollection.h" +#include "RimSurfaceIntersectionBand.h" +#include "RimSurfaceIntersectionCollection.h" +#include "RimSurfaceIntersectionCurve.h" #include "RivEclipseIntersectionGrid.h" #include "RivFemIntersectionGrid.h" +#include "cafCmdFeatureMenuBuilder.h" #include "cafPdmUiCheckBoxEditor.h" +#include "cafPdmUiTreeOrdering.h" CAF_PDM_ABSTRACT_SOURCE_INIT( RimIntersection, "RimIntersectionHandle" ); @@ -49,6 +54,10 @@ RimIntersection::RimIntersection() CAF_PDM_InitField( &m_useSeparateDataSource, "UseSeparateIntersectionDataSource", true, "Enable" ); CAF_PDM_InitFieldNoDefault( &m_separateDataSource, "SeparateIntersectionDataSource", "Source" ); + + CAF_PDM_InitFieldNoDefault( &m_surfaceIntersections, "SurfaceIntersections", "Surface Intersections" ); + m_surfaceIntersections = new RimSurfaceIntersectionCollection; + m_surfaceIntersections->objectChanged.connect( this, &RimIntersection::onSurfaceIntersectionsChanged ); } //-------------------------------------------------------------------------------------------------- @@ -184,6 +193,134 @@ void RimIntersection::updateDefaultSeparateDataSource() } } +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimSurfaceIntersectionCollection* RimIntersection::surfaceIntersectionCollection() const +{ + return m_surfaceIntersections(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector RimIntersection::surfaceIntersectionCurves() const +{ + return m_surfaceIntersections->surfaceIntersectionCurves(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +std::vector RimIntersection::surfaceIntersectionBands() const +{ + return m_surfaceIntersections->surfaceIntersectionBands(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimSurfaceIntersectionCurve* RimIntersection::addIntersectionCurve() +{ + return m_surfaceIntersections->addIntersectionCurve(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimSurfaceIntersectionBand* RimIntersection::addIntersectionBand() +{ + return m_surfaceIntersections->addIntersectionBand(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +bool RimIntersection::supportsSurfaceIntersectionCurves() const +{ + return false; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimIntersectionCurtain RimIntersection::surfaceCurtain() const +{ + return {}; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +double RimIntersection::defaultCurtainExtent() +{ + return 10000.0; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +RimIntersectionCurtain RimIntersection::verticalCurtain( const std::vector& trace, double topZ, double bottomZ ) +{ + RimIntersectionCurtain curtain; + curtain.trace = trace; + curtain.pillars.reserve( trace.size() ); + + for ( const auto& point : trace ) + { + curtain.pillars.emplace_back( cvf::Vec3d( point.x(), point.y(), topZ ), cvf::Vec3d( point.x(), point.y(), bottomZ ) ); + } + + return curtain; +} + +//-------------------------------------------------------------------------------------------------- +/// Overridden by the intersection types that build their geometry from a part manager +//-------------------------------------------------------------------------------------------------- +void RimIntersection::rebuildGeometryAndScheduleCreateDisplayModel() +{ +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimIntersection::appendSurfaceIntersectionsToTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering ) +{ + for ( auto c : m_surfaceIntersections->surfaceIntersectionCurves() ) + { + uiTreeOrdering.add( c ); + } + for ( auto c : m_surfaceIntersections->surfaceIntersectionBands() ) + { + uiTreeOrdering.add( c ); + } +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimIntersection::appendCommonMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const +{ + menuBuilder << "RicCreateSurfaceIntersectionBandFeature"; + menuBuilder << "RicCreateSurfaceIntersectionCurveFeature"; + menuBuilder.addSeparator(); + menuBuilder << "RicPasteIntersectionsFeature"; + menuBuilder.addSeparator(); + menuBuilder << "RicAppendIntersectionFeature"; + menuBuilder << "RicAppendIntersectionBoxFeature"; + menuBuilder << "RicAppendIjkIntersectionFeature"; + menuBuilder.addSeparator(); +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimIntersection::onSurfaceIntersectionsChanged( const caf::SignalEmitter* emitter ) +{ + updateAllRequiredEditors(); + rebuildGeometryAndScheduleCreateDisplayModel(); +} + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- diff --git a/ApplicationLibCode/ProjectDataModel/Intersections/RimIntersection.h b/ApplicationLibCode/ProjectDataModel/Intersections/RimIntersection.h index df8b70d7ba..35e912fb85 100644 --- a/ApplicationLibCode/ProjectDataModel/Intersections/RimIntersection.h +++ b/ApplicationLibCode/ProjectDataModel/Intersections/RimIntersection.h @@ -17,17 +17,49 @@ ///////////////////////////////////////////////////////////////////////////////// #pragma once +#include "cafPdmChildField.h" #include "cafPdmField.h" #include "cafPdmObject.h" #include "cafPdmPtrField.h" #include "cvfObject.h" +#include "cvfVector3.h" + +#include +#include class RimIntersectionResultDefinition; class RivIntersectionHexGridInterface; class RimIntersectionResultsDefinitionCollection; +class RimSurfaceIntersectionBand; +class RimSurfaceIntersectionCollection; +class RimSurfaceIntersectionCurve; class RivIntersectionGeometryGeneratorInterface; +namespace caf +{ +class CmdFeatureMenuBuilder; +class PdmUiTreeOrdering; +} // namespace caf + +//================================================================================================== +/// The curtain of an intersection, described at a set of positions along it +//================================================================================================== +class RimIntersectionCurtain +{ +public: + /// The polyline the curve is resampled along, in 3D. Kept apart from the pillars so the samples + /// are distributed along the intersection itself, also where the pillars are long. + std::vector trace; + + /// For each point of the trace, the segment the surface is looked up along, from the top to the + /// bottom of the curtain. Tilted for an intersection that follows the grid pillars, which is + /// what puts the curve on the curtain instead of on a vertical plane through it. + std::vector> pillars; + + bool isValid() const { return trace.size() == pillars.size() && trace.size() > 1; } +}; + class RimIntersection : public caf::PdmObject { CAF_PDM_HEADER_INIT; @@ -47,6 +79,20 @@ public: virtual const RivIntersectionGeometryGeneratorInterface* intersectionGeometryGenerator() const = 0; + RimSurfaceIntersectionCollection* surfaceIntersectionCollection() const; + std::vector surfaceIntersectionCurves() const; + std::vector surfaceIntersectionBands() const; + RimSurfaceIntersectionCurve* addIntersectionCurve(); + RimSurfaceIntersectionBand* addIntersectionBand(); + + /// True if the intersection is a vertical curtain, and therefore can display surface intersection + /// curves and bands. Horizontal sections would require contouring instead of a vertical ray cast. + virtual bool supportsSurfaceIntersectionCurves() const; + + virtual RimIntersectionCurtain surfaceCurtain() const; + + virtual void rebuildGeometryAndScheduleCreateDisplayModel(); + protected: virtual RimIntersectionResultsDefinitionCollection* findSeparateResultsCollection(); @@ -56,8 +102,25 @@ protected: void defineSeparateDataSourceUi( QString uiConfigName, caf::PdmUiOrdering& uiOrdering ); void updateDefaultSeparateDataSource(); + void appendSurfaceIntersectionsToTreeOrdering( caf::PdmUiTreeOrdering& uiTreeOrdering ); + + /// The context menu entries shared by all intersection types, ending with a separator so the + /// caller can add its own entries before the trailing copy-to-all-views command + void appendCommonMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const; + + /// A curtain that is a vertical extrusion of the trace, spanning [bottomZ, topZ] + static RimIntersectionCurtain verticalCurtain( const std::vector& trace, double topZ, double bottomZ ); + + /// Reaches past any reservoir depth, used when the curtain has no defined vertical extent + static double defaultCurtainExtent(); + caf::PdmField m_isActive; caf::PdmField m_showInactiveCells; caf::PdmField m_useSeparateDataSource; caf::PdmPtrField m_separateDataSource; + + caf::PdmChildField m_surfaceIntersections; + +private: + void onSurfaceIntersectionsChanged( const caf::SignalEmitter* emitter ); }; diff --git a/ApplicationLibCode/ProjectDataModel/Intersections/RimIntersectionCollection.cpp b/ApplicationLibCode/ProjectDataModel/Intersections/RimIntersectionCollection.cpp index 6fefbb6c76..c92362776d 100644 --- a/ApplicationLibCode/ProjectDataModel/Intersections/RimIntersectionCollection.cpp +++ b/ApplicationLibCode/ProjectDataModel/Intersections/RimIntersectionCollection.cpp @@ -41,6 +41,7 @@ #include "RivExtrudedCurveIntersectionPartMgr.h" #include "RivIjkIntersectionPartMgr.h" +#include "cafCmdFeatureMenuBuilder.h" #include "cafPdmObjectScriptingCapability.h" #include "cafPdmUiCheckBoxEditor.h" #include "cafPdmUiDoubleSliderEditor.h" @@ -246,6 +247,20 @@ bool RimIntersectionCollection::hasAnyActiveSeparateResults() //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimIntersectionCollection::appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const +{ + menuBuilder << "RicPasteIntersectionsFeature"; + menuBuilder.addSeparator(); + menuBuilder << "RicAppendIntersectionFeature"; + menuBuilder << "RicAppendIntersectionBoxFeature"; + menuBuilder << "RicAppendIjkIntersectionFeature"; + menuBuilder.addSeparator(); + menuBuilder << "RicCopyIntersectionsToAllViewsInCaseFeature"; +} + void RimIntersectionCollection::appendPartsToModel( Rim3dView& view, cvf::ModelBasicList* model, cvf::Transform* scaleTransform ) { if ( !isActive() ) return; @@ -291,9 +306,10 @@ void RimIntersectionCollection::appendDynamicPartsToModel( cvf::ModelBasicList* { if ( cs->isActive() ) { - cs->intersectionBoxPartMgr()->generatePartGeometry( visibleCells ); + cs->intersectionBoxPartMgr()->generatePartGeometry( visibleCells, scaleTransform ); cs->intersectionBoxPartMgr()->appendNativeIntersectionFacesToModel( model, scaleTransform ); cs->intersectionBoxPartMgr()->appendMeshLinePartsToModel( model, scaleTransform ); + cs->intersectionBoxPartMgr()->appendAnnotationPartsToModel( model, scaleTransform ); } } @@ -301,9 +317,10 @@ void RimIntersectionCollection::appendDynamicPartsToModel( cvf::ModelBasicList* { if ( cs->isActive() ) { - cs->intersectionPartMgr()->generatePartGeometry( visibleCells ); + cs->intersectionPartMgr()->generatePartGeometry( visibleCells, scaleTransform ); cs->intersectionPartMgr()->appendNativeIntersectionFacesToModel( model, scaleTransform ); cs->intersectionPartMgr()->appendMeshLinePartsToModel( model, scaleTransform ); + cs->intersectionPartMgr()->appendAnnotationPartsToModel( model, scaleTransform ); } } } diff --git a/ApplicationLibCode/ProjectDataModel/Intersections/RimIntersectionCollection.h b/ApplicationLibCode/ProjectDataModel/Intersections/RimIntersectionCollection.h index 2fa38102ec..ebafa35be2 100644 --- a/ApplicationLibCode/ProjectDataModel/Intersections/RimIntersectionCollection.h +++ b/ApplicationLibCode/ProjectDataModel/Intersections/RimIntersectionCollection.h @@ -36,6 +36,11 @@ class RimEclipseCellColors; class RimSimWellInView; class RivTernaryScalarMapper; +namespace caf +{ +class CmdFeatureMenuBuilder; +} + namespace cvf { class ModelBasicList; @@ -93,6 +98,8 @@ public: void onChildAdded( caf::PdmFieldHandle* containerForNewObject ) override; + void appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const override; + bool isActive() const; void setActive( bool active ); diff --git a/ApplicationLibCode/ProjectDataModel/Intersections/RimIntersectionResultDefinition.cpp b/ApplicationLibCode/ProjectDataModel/Intersections/RimIntersectionResultDefinition.cpp index dcf99cf899..87bb0dd7b5 100644 --- a/ApplicationLibCode/ProjectDataModel/Intersections/RimIntersectionResultDefinition.cpp +++ b/ApplicationLibCode/ProjectDataModel/Intersections/RimIntersectionResultDefinition.cpp @@ -36,6 +36,7 @@ #include "RiuViewer.h" +#include "cafCmdFeatureMenuBuilder.h" #include "cafPdmUiTreeOrdering.h" CAF_PDM_SOURCE_INIT( RimIntersectionResultDefinition, "IntersectionResultDefinition" ); @@ -88,6 +89,14 @@ RimIntersectionResultDefinition::~RimIntersectionResultDefinition() //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimIntersectionResultDefinition::appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const +{ + menuBuilder << "RicAppendSeparateIntersectionResultFeature"; +} + bool RimIntersectionResultDefinition::isActive() const { return m_isActive(); diff --git a/ApplicationLibCode/ProjectDataModel/Intersections/RimIntersectionResultDefinition.h b/ApplicationLibCode/ProjectDataModel/Intersections/RimIntersectionResultDefinition.h index ea4191d1b7..1a5ef49160 100644 --- a/ApplicationLibCode/ProjectDataModel/Intersections/RimIntersectionResultDefinition.h +++ b/ApplicationLibCode/ProjectDataModel/Intersections/RimIntersectionResultDefinition.h @@ -31,6 +31,11 @@ class RimRegularLegendConfig; class RimTernaryLegendConfig; class RiuViewer; +namespace caf +{ +class CmdFeatureMenuBuilder; +} + class RimIntersectionResultDefinition : public caf::PdmObject { CAF_PDM_HEADER_INIT; @@ -39,6 +44,8 @@ public: RimIntersectionResultDefinition(); ~RimIntersectionResultDefinition() override; + void appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const override; + bool isActive() const; bool isInAction() const; QString autoName() const; diff --git a/ApplicationLibCode/ProjectDataModel/Intersections/RimIntersectionResultsDefinitionCollection.cpp b/ApplicationLibCode/ProjectDataModel/Intersections/RimIntersectionResultsDefinitionCollection.cpp index 6e88eb5571..cd304ba9c4 100644 --- a/ApplicationLibCode/ProjectDataModel/Intersections/RimIntersectionResultsDefinitionCollection.cpp +++ b/ApplicationLibCode/ProjectDataModel/Intersections/RimIntersectionResultsDefinitionCollection.cpp @@ -22,6 +22,7 @@ #include "RimGridView.h" #include "RimIntersectionCollection.h" #include "RimIntersectionResultDefinition.h" +#include "cafCmdFeatureMenuBuilder.h" CAF_PDM_SOURCE_INIT( RimIntersectionResultsDefinitionCollection, "RimIntersectionResultsDefinitionCollection" ); @@ -50,6 +51,14 @@ RimIntersectionResultsDefinitionCollection::~RimIntersectionResultsDefinitionCol //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +void RimIntersectionResultsDefinitionCollection::appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const +{ + menuBuilder << "RicAppendSeparateIntersectionResultFeature"; +} + bool RimIntersectionResultsDefinitionCollection::isActive() const { return m_isActive(); diff --git a/ApplicationLibCode/ProjectDataModel/Intersections/RimIntersectionResultsDefinitionCollection.h b/ApplicationLibCode/ProjectDataModel/Intersections/RimIntersectionResultsDefinitionCollection.h index 2dea1fea27..8650ad1d76 100644 --- a/ApplicationLibCode/ProjectDataModel/Intersections/RimIntersectionResultsDefinitionCollection.h +++ b/ApplicationLibCode/ProjectDataModel/Intersections/RimIntersectionResultsDefinitionCollection.h @@ -22,6 +22,11 @@ class RimIntersectionResultDefinition; +namespace caf +{ +class CmdFeatureMenuBuilder; +} + class RimIntersectionResultsDefinitionCollection : public caf::PdmObjectCollection { CAF_PDM_HEADER_INIT; @@ -30,6 +35,8 @@ public: RimIntersectionResultsDefinitionCollection(); ~RimIntersectionResultsDefinitionCollection() override; + void appendMenuItems( caf::CmdFeatureMenuBuilder& menuBuilder ) const override; + bool isActive() const; std::vector intersectionResultsDefinitions() const; diff --git a/ApplicationLibCode/ProjectDataModel/RimContextCommandBuilder.cpp b/ApplicationLibCode/ProjectDataModel/RimContextCommandBuilder.cpp index 00ff4662d7..5fbd921f9d 100644 --- a/ApplicationLibCode/ProjectDataModel/RimContextCommandBuilder.cpp +++ b/ApplicationLibCode/ProjectDataModel/RimContextCommandBuilder.cpp @@ -41,7 +41,6 @@ #include "RimAnnotationCollection.h" #include "RimAnnotationGroupCollection.h" #include "RimAnnotationInViewCollection.h" -#include "RimBoxIntersection.h" #include "RimCalcScript.h" #include "RimCaseCollection.h" #include "RimCellEdgeColors.h" @@ -68,7 +67,6 @@ #include "RimEnsembleCurveFilterCollection.h" #include "RimEnsembleCurveSetCollection.h" #include "RimEnsembleFractureStatisticsCollection.h" -#include "RimExtrudedCurveIntersection.h" #include "RimFaultInView.h" #include "RimFaultReactivationModel.h" #include "RimFileWellPath.h" @@ -90,10 +88,6 @@ #include "RimGridCrossPlotCollection.h" #include "RimGridCrossPlotDataSet.h" #include "RimIdenticalGridCaseGroup.h" -#include "RimIjkIntersection.h" -#include "RimIntersectionCollection.h" -#include "RimIntersectionResultDefinition.h" -#include "RimIntersectionResultsDefinitionCollection.h" #include "RimModeledWellPath.h" #include "RimMultiPlot.h" #include "RimObservedSummaryData.h" @@ -764,58 +758,6 @@ caf::CmdFeatureMenuBuilder RimContextCommandBuilder::commandsFromSelection() { menuBuilder << "RicAddWellLogToPlotFeature"; } - else if ( dynamic_cast( firstUiItem ) ) - { - menuBuilder << "RicPasteIntersectionsFeature"; - menuBuilder.addSeparator(); - menuBuilder << "RicAppendIntersectionFeature"; - menuBuilder << "RicAppendIntersectionBoxFeature"; - menuBuilder << "RicAppendIjkIntersectionFeature"; - menuBuilder.addSeparator(); - menuBuilder << "RicCopyIntersectionsToAllViewsInCaseFeature"; - } - else if ( dynamic_cast( firstUiItem ) ) - { - menuBuilder << "RicCreateSurfaceIntersectionBandFeature"; - menuBuilder << "RicCreateSurfaceIntersectionCurveFeature"; - menuBuilder.addSeparator(); - menuBuilder << "RicPasteIntersectionsFeature"; - menuBuilder.addSeparator(); - menuBuilder << "RicAppendIntersectionFeature"; - menuBuilder << "RicAppendIntersectionBoxFeature"; - menuBuilder << "RicAppendIjkIntersectionFeature"; - menuBuilder.addSeparator(); - menuBuilder << "RicSeismicSectionFromIntersectionFeature"; - menuBuilder.addSeparator(); - menuBuilder << "RicNewIntersectionViewFeature"; - menuBuilder.addSeparator(); - menuBuilder << "RicCopyIntersectionsToAllViewsInCaseFeature"; - } - else if ( dynamic_cast( firstUiItem ) ) - { - menuBuilder << "RicPasteIntersectionsFeature"; - menuBuilder.addSeparator(); - menuBuilder << "RicAppendIntersectionFeature"; - menuBuilder << "RicAppendIntersectionBoxFeature"; - menuBuilder << "RicAppendIjkIntersectionFeature"; - menuBuilder.addSeparator(); - menuBuilder << "RicCopyIntersectionsToAllViewsInCaseFeature"; - } - else if ( dynamic_cast( firstUiItem ) ) - { - menuBuilder << "RicPasteIntersectionsFeature"; - menuBuilder.addSeparator(); - menuBuilder << "RicAppendIntersectionFeature"; - menuBuilder << "RicAppendIntersectionBoxFeature"; - menuBuilder << "RicAppendIjkIntersectionFeature"; - menuBuilder.addSeparator(); - menuBuilder << "RicCopyIntersectionsToAllViewsInCaseFeature"; - } - else if ( dynamic_cast( firstUiItem ) || - dynamic_cast( firstUiItem ) ) - { - menuBuilder << "RicAppendSeparateIntersectionResultFeature"; - } else if ( dynamic_cast( firstUiItem ) ) { menuBuilder << "RicNewWellLogExtractionCurveFeature";