Make contour maps available in 3D views - #1045
Conversation
|
The clang-19 build failure is fixed in 1162eae. One check is still red, and it is not from this branch:
The same job, same step and same 404s fail on |
Contour map geometry is generated in a flat, contour map local coordinate space where z is always zero, and consumers lift it to the minimum z of the expanded bounding box. Introduce RivContourMapElevationProvider so the vertical placement becomes a decision made in one place, and route the four local-to-domain conversions in RivContourMapProjectionPartMgr through a pair of shared helpers. Without a provider the resulting coordinates are unchanged, so the 2d contour map views render exactly as before. Geometry is dropped where the provider reports no elevation. This splits a draped polyline into separate pieces wherever there is nothing underneath it. Also assign the polygon offset render state to the contour line effect. The return value of createAndConfigurePolygonOffsetRenderState was discarded, so the lines had no offset at all. This is invisible in the 2d views where the lines are coplanar with the map, but causes z-fighting once the lines are draped on grid geometry.
Sample the top of the visible grid geometry on the vertices of a contour map grid, by dropping a vertical ray at every vertex and keeping the highest intersection with a visible cell. The result is a raster that is cheap to interpolate, which is what draping contour lines needs. Positions with no visible cell underneath stay undefined so the consumer can leave a gap. The visibility array is supplied by the caller and is the authority on what the view is showing, so the drape follows cell filters and property filters.
RimContourMapInView points at a contour map view and renders the geometry that view has already computed. Keeping the contour map itself in one place means there is no second projection to configure and no result to recompute, so the wrapper only carries the state that belongs to the 3d view: where the map is placed vertically, and which parts of it are drawn. The map plane is placed at the top of the case by default, since the contour map coordinate space would otherwise put it below the reservoir. Contour lines can follow the top of the visible geometry instead of the map plane. Contour labels are left out. Their placement depends on the camera and is only kept correct in the 2d views, which regenerate them while navigating.
The project level collection of contour map views owns the contour maps. This collection mirrors it into a single 3d view, holding one wrapper per contour map. The sync sweeps wrappers whose contour map has been deleted, creates wrappers for new ones, and reorders to follow the source. The source collection is flat rather than nested, so the sync is modelled on RimSurfaceInViewCollection rather than the RimNestedMirrorCollectionInView template. Contour maps of every case are mirrored, not only those of the case the view belongs to, so an ensemble statistics map can be overlaid on a view of a single realization. New wrappers start out unchecked.
Give RimGridView the in-view collection, sync it from the project contour maps through the existing updateViewTreeItems mechanism, and render it from RimEclipseView. The parts go into a frame scene model rather than a static one, since contour map results depend on the time step. The 2d contour map views return null for the collection. They already show their own contour map, and should not offer to show the other contour maps of the project.
The 3d views mirror the project contour map collection, so they have to be told when a contour map appears or disappears. All creation paths go through addView, including the new contour map feature and paste, so the notification covers them all. Only deletion schedules a redraw. A newly mirrored contour map starts out unchecked and therefore draws nothing.
The contour lines were drawn as hairlines coloured to contrast the map level underneath them. That works on the flat 2d map, but reads poorly in a 3d view where the lines are draped on grid geometry of arbitrary colour. Give RimContourMapInView a line colour mode, a colour and a thickness, and pass them to the part manager as an appearance struct. The defaults reproduce the existing appearance, so the 2d contour map views, which pass no appearance at all, are unaffected.
The name field is the user description field, and is what the project tree displays. It was never set, so the collection showed up without a label.
…the surface Contour lines were sometimes hidden by the map surface. The two are coplanar, and the polygon offset added earlier to pull the lines forward never had any effect: OpenGL polygon offset applies to polygon primitives only, and the enable flags select which polygon mode is offset, not which primitive type. These are line primitives, so no polygon offset can reach them. Drop the render state and lift the lines above the surface geometrically instead, by a fraction of the sample spacing so the lift scales with the model. Also add Project Surface on Geometry, so the map surface can follow the top of the visible geometry the same way the contour lines already could. Triangles with no visible cell underneath are dropped, which the part manager already handled. This makes the lift matter more, since a draped surface and draped lines are coplanar everywhere. The topography raster is now rebuilt when the view produces a new cell visibility array or the projection a new map grid, so the drape follows cell and property filter changes rather than keeping the raster it was first built with.
Labels were left out on the assumption that they would not survive navigation. They do: DrawableText re-projects each anchor to window coordinates every frame and draws screen aligned text, so a label stays readable and stays on its contour line whatever the camera does. What is decided once, from the camera as it is when the display model is built, is the layout: which labels are dropped for overlapping each other, and the in plane rotation of the text. Rotating the view can therefore leave labels overlapping or turned at an odd angle until the model is rebuilt. Off by default, and follows the elevation of the contour lines, so the labels are lifted clear of the map surface along with them.
The labels derived their own color from the map level, so a label kept the contrasting color even when the lines had been given an explicit one, and the two disagreed. Both now go through RivContourLineAppearance::colorForLevel, so a label cannot drift away from the line it belongs to. Behaviour is unchanged when no explicit color is set, which is what the 2d contour map views use.
Contour polygons are simplified with a Douglas-Peucker epsilon of up to a fifth of the sample spacing, so a straight stretch of a contour line collapses to a single long segment. Elevating only the end points of such a segment leaves it cutting straight through everything in between, which is why the lines did not look draped. Resample each segment before elevating it, so the line picks up the shape of the geometry along its whole length. How densely is asked of the elevation provider: the topography provider samples eight times per raster cell, and the flat provider asks for no resampling at all, since a plane is followed exactly by its end points. Samples where no elevation is defined still leave a gap, so a draped line is now split at the true edge of the visible geometry rather than at the nearest polygon vertex.
The topography only searched grid cells, filtered by the cell visibility of the view. Hiding the grid cells makes that visibility all false, so nothing was hit anywhere, every elevation came back undefined, and the gap handling swallowed the whole contour line. A view showing only a surface therefore drew no lines at all. Search the visible surfaces of the view too, and keep whichever of the two is higher, so the geometry follows what is actually on screen. Each surface gets a ray range derived from its own vertices, since a surface can sit well outside the depth range of the case. Surfaces are collected through a new visibleSurfacesInView on RimSurfaceInViewCollection, which recurses the checked sub collections the same way the existing appendPartsToModel and legendConfigs do. The cached raster is rebuilt when that set changes, so checking a surface on or off updates the drape.
RimNamedObject declares name as virtual, so the wrapper was overriding it implicitly. Clang rejects that under -Winconsistent-missing-override with warnings as errors, which broke the Linux clang build. MSVC accepts it, so it was not caught locally. Moved to the public section along with the override, matching how RimSurfaceInViewCollection exposes the same thing. The name still follows the contour map the wrapper points at.
Draped contour lines disappeared into the geometry wherever the cells step, because the elevation raster was built on the contour map grid vertices. That spacing, not the sampling of the curve, is what limits how closely a draped curve can follow the geometry: between two raster vertices the interpolated elevation ramps smoothly while the cells step, so the curve sinks below the top face of the cell it just stepped down from and is hidden by it. Build the raster on its own lattice at a quarter of the contour map sample spacing, over the same extent. Curve sampling follows automatically, since the resampling distance is derived from the raster spacing rather than from the contour map grid. Costs the refinement squared in rays, sixteen times as many, which is why the factor is not larger. The raster is cached and only rebuilt when the visible geometry changes, so this is paid once rather than per redraw.
The label direction handed to the text drawer is a screen space one, but it was being filled in with the contour segment direction taken from domain space. Those only agree while the camera looks straight down at the map, which is always true in the 2d contour map views and almost never true in a 3d view, so the text came out rotated arbitrarily. Keep the labels upright on screen in the 3d views. The 2d views are unchanged and still run their labels along the contour lines, which is what a map should look like when seen from above. The font size was fixed at 10 points, too small to read against the geometry. It is now a field on the wrapper, so the labels can be sized to the view.
Projected onto the geometry the map surface took its elevation straight from the topography, leaving it coplanar with the cell faces it follows, so the two z-fought. Lift it by a fraction of the sample spacing instead, in the same way the contour lines already are. The lift is geometric rather than a depth buffer adjustment, so it holds from any camera angle and does not depend on render state. The contour line lift is now expressed as twice the surface lift, which keeps the ordering of geometry, then map surface, then lines explicit, and keeps the lines on top of the surface whether it is draped or flat.
White lines of thickness two, with labels shown, read better against grid geometry than the previous single pixel contrast coloured lines with labels switched off. The line color mode defaults to Single Color as well, since the white default has no effect while every level takes a color contrasting the map underneath it. Only affects contour maps added to a view from now on. Ones already stored in a project keep the values they were saved with.
Uses RiaDefines::betaFeaturePostfix, the same marker the cloud data and OSDU features carry. Applied both to the object name and to the name field, since the project tree reads the label from the latter.
A contour map read from a project was not drawn in the 3d views showing it. RimEclipseCase::allSpecialViews returns the ordinary 3d views before the contour map views, and the project loader loads them in that order. A 3d view therefore built its display model while the contour map view it mirrors had not been loaded, and the projection derives its cell visibility from that view. Cell visibility is accumulated over the visible grid parts, which are only populated once the view has created its display model, so every cell read as hidden and the projection produced no results. Both the empty visibility and the empty result were then cached, and nothing invalidated them once the contour map view did load. Tell the mirroring views once the contour map view is ready, so they drop the geometry they built too early and rebuild against real data. Creating a contour map interactively was never affected, since the contour map view is loaded before it can be checked in a 3d view.
The previous attempt at this notified the 3d views from onLoadDataAndUpdate, and asked them for a scheduled redraw. Both were wrong, and logging each early return against a project that reproduces it showed why. onLoadDataAndUpdate only schedules the contour map view's own rebuild, so the results and the geometry did not exist yet when the 3d views were told to redraw. They rebuilt against an empty projection, zero contour polygons and zero triangles, and nothing invalidated that afterwards. A scheduled redraw would not have helped either. It reaches createDisplayModelAndRedraw, which never calls onUpdateDisplayModelForCurrent TimeStep, and that is where the contour map parts are appended, since they belong to the frame scene. Notify from the end of updateGeometry instead, where the results and the geometry have just been generated, and ask the views for the time step update directly. This also covers changes to the contour map itself, which previously left the 3d views showing what they had built earlier.
Drop the map edge no longer. The topography raster spans the full extent of the map, so a position exactly on the far edge is inside it and belongs to the last cell. Clamp the cell index instead of rejecting the position, which silently discarded the outermost row and column of every draped map. Cast the drape rays over the range of the grid they are cast against. The range came from the contour map grid, which belongs to the case the map was computed from rather than to the view showing it. A ray that starts inside a cell only reports its exit point, and cells above it are missed entirely. Mirror the ensemble statistics contour maps as well. They live on their ensemble rather than in the oil field collection, so enumerate every contour map view of the project instead. Their collection now refreshes the 3d views on add and delete, as the oil field collection already did. Append the contour maps in onCreateDisplayModel for a view showing only static results. Such a view has no frame scenes at all, and never reaches onUpdateDisplayModelForCurrentTimeStep, so nothing was drawn and no user action recovered it. Fall back to the main scene for the same reason. Generate the projection for the time step the contour map view is on, not for the host view's. The projection is owned by that view and shared with it, so driving it elsewhere left it holding results for a step it was not showing, and made the two views recompute against each other on every redraw. Keep the topography raster when only the contour map has changed. It follows the host view, which has not, and rebuilding it on every time step change of the contour map view re-rasters a ray per lattice vertex against the whole main grid. Use the documented defaults for the line appearance. The enum default and the field default disagreed, and the field default won. Drop the context menu entry, which could never be enabled from that node, and the unused collection level clearGeometry. What it was reaching for is a main grid identity in the topography cache key, which is now there. Return no contour map collection for a GeoMech view, as the contour map views already do. It cannot render them, and mirroring them filled the view and the project file with entries that could never be shown.
d5461ae to
a9b7d63
Compare
Makes contour maps available for visualization inside normal 3D views.
Implements the plan in #1044, which addresses #1043.
What it does
A new
Contour Mapscollection in every 3D view mirrors the project level contour maps. Each entry is a thin wrapper pointing at an existing contour map view, so the contour map itself, its result, aggregation and computed geometry, stays in one place. The 3D view only renders it, and carries the presentation state that belongs to that view.Contour maps of every case are mirrored, not only those of the case the view belongs to, so an ensemble statistics map can be overlaid on a view of a single realization. New entries start unchecked.
How it fits together
The contour map coordinate space is flat, with
zalways zero, lifted by consumers toRigContourMapGrid::origin3d(), which is the minimum z of the expanded bounding box. In a 3D view that puts the map under the reservoir, so vertical placement had to become a decision rather than a constant.RivContourMapElevationProvideris that decision point.RivContourMapProjectionPartMgrroutes all four of its local to domain conversions through one pair of helpers that consult the provider, and drops geometry where the provider reports no elevation. Passing no provider reproduces the previous coordinates exactly, which is what the 2D contour map views do, so they are unaffected throughout.Two providers exist: a flat plane, and
RigContourMapTopography, a raster of the top of the visible geometry sampled on the contour map vertices. The raster is built by dropping a vertical ray at each vertex and keeping the highest hit among the visible grid cells and the visible surfaces, so the drape follows what is actually on screen, including cell and property filters. It is rebuilt when the visibility array, the map grid or the surface set changes.Since contour polygons are simplified with a Douglas-Peucker epsilon of up to a fifth of the sample spacing, a straight stretch of a contour line collapses to one long segment. Elevating only its end points would leave it cutting through the geometry, so segments are resampled before being elevated, at a density the provider asks for.
Notes for review
Three things are worth a second opinion.
Polygon offset does not work on contour lines. They are
PT_LINESprimitives, and OpenGL polygon offset applies to polygon primitives only, the enable flags selecting which polygon mode is offset rather than which primitive type. The existing call inRivContourMapProjectionPartMgrdiscarded its return value and was inert in any case. Contour lines are therefore lifted above the surface geometrically, by0.02 * sampleSpacing, rather than through a render state.Two tuning constants are judgement calls made without a real model to look at: the line lift above, and eight samples per raster cell for the resampling density.
Contour label layout is fixed when the display model is built.
cvf::DrawableTextre-projects its anchors every frame, so labels stay readable and correctly placed while navigating, but which labels were dropped for overlapping, and the in plane rotation of the text, reflect the camera at build time. The 2D views rebuild on a camera z change above five percent throughonViewNavigationChanged. That hook is deliberately not added here, since it would rebuild the whole contour map model on navigation for every 3D view.Scope
Eclipse contour maps and ensemble statistics contour maps. GeoMech contour maps are not covered: their collection lives per case on
RimGeoMechCaserather than onRimOilField, and there is no common view base type across the two families.Verification
Clean build, and the 1023 unit tests in
ResInsight-testspass. A snapshot run overTEST10K_FLT_LGR_NNCloads the case, builds the display model through the new rendering and legend paths and exits cleanly.The visual behaviour has not been verified end to end. #1044 carries a checklist for that, covering placement, the two drape options, filters, time steps, ensembles, deletion and project round trip.