///////////////////////////////////////////////////////////////////////////////// // // Copyright (C) 2018- 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 "RivWindowEdgeAxesOverlayItem.h" #include "cvfBufferObjectManaged.h" #include "cvfCamera.h" #include "cvfFont.h" #include "cvfGeometryBuilderDrawableGeo.h" #include "cvfGeometryUtils.h" #include "cvfGlyph.h" #include "cvfMatrixState.h" #include "cvfOpenGL.h" #include "cvfOpenGLResourceManager.h" #include "cvfRenderStateDepth.h" #include "cvfRenderStateLine.h" #include "cvfShaderProgram.h" #include "cvfShaderProgramGenerator.h" #include "cvfShaderSourceProvider.h" #include "cvfShaderSourceRepository.h" #include "cvfTextDrawer.h" #include "cvfUniform.h" #include "cvfViewport.h" #ifndef CVF_OPENGL_ES #include "cvfRenderState_FF.h" #endif #include "cafTickMarkGenerator.h" #include "cvfRenderStateBlending.h" #include "cvfScalarMapper.h" #include using namespace cvf; //================================================================================================== /// /// \class cvf::OverlayColorLegend /// \ingroup Render /// /// /// //================================================================================================== //-------------------------------------------------------------------------------------------------- /// Constructor //-------------------------------------------------------------------------------------------------- RivWindowEdgeAxesOverlayItem::RivWindowEdgeAxesOverlayItem( Font* font ) : m_windowSize( 600, 600 ) , m_textColor( Color3::BLACK ) , m_lineColor( Color3::BLACK ) , m_frameColor( Color3::WHITE ) , m_lineWidth( 1 ) , m_font( font ) , m_isSwitchingYAxisValueSign( true ) , m_showAxisLines( false ) , m_domainAxes( XZ_AXES ) { CVF_ASSERT( font ); CVF_ASSERT( !font->isEmpty() ); setLayoutFixedPosition( { 0, 0 } ); updateGeomerySizes(); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- RivWindowEdgeAxesOverlayItem::~RivWindowEdgeAxesOverlayItem() { } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RivWindowEdgeAxesOverlayItem::setDisplayCoordTransform( const caf::DisplayCoordTransform* displayCoordTransform ) { m_dispalyCoordsTransform = displayCoordTransform; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RivWindowEdgeAxesOverlayItem::updateGeomerySizes() { String str = String::number( -1.999e-17 ); m_textSize = m_font->textExtent( str ); m_pixelSpacing = 5.0f; m_tickLineLength = m_textSize.y() * 0.3f; m_frameBorderHeight = m_pixelSpacing + m_textSize.y() + m_pixelSpacing + m_tickLineLength + m_lineWidth; m_frameBorderWidth = m_pixelSpacing + m_textSize.x() + m_pixelSpacing + m_tickLineLength + m_lineWidth; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RivWindowEdgeAxesOverlayItem::updateFromCamera( const Camera* camera ) { if ( !camera || camera->projection() != Camera::ORTHO ) { m_domainCoordsXValues.clear(); m_domainCoordsYValues.clear(); m_windowTickXValues.clear(); m_windowTickYValues.clear(); return; } m_windowSize = Vec2ui( camera->viewport()->width(), camera->viewport()->height() ); Vec3d windowOrigoInDomain; Vec3d windowMaxInDomain; camera->unproject( Vec3d( 0, 0, 0 ), &windowOrigoInDomain ); camera->unproject( Vec3d( m_windowSize.x(), m_windowSize.y(), 0 ), &windowMaxInDomain ); if ( m_dispalyCoordsTransform.notNull() ) { windowOrigoInDomain = m_dispalyCoordsTransform->transformToDomainCoord( windowOrigoInDomain ); windowMaxInDomain = m_dispalyCoordsTransform->transformToDomainCoord( windowMaxInDomain ); } // For extreme zoom factors we might end up with both variables as zero. Return to avoid divide by zero. if ( windowOrigoInDomain == windowMaxInDomain ) return; double domainMinX = windowOrigoInDomain.x(); double domainMaxX = windowMaxInDomain.x(); double domainMinY = m_domainAxes == XY_AXES ? windowOrigoInDomain.y() : windowOrigoInDomain.z(); double domainMaxY = m_domainAxes == XY_AXES ? windowMaxInDomain.y() : windowMaxInDomain.z(); int xTickMaxCount = m_windowSize.x() / ( 2 * m_textSize.x() ); int yTickMaxCount = m_windowSize.y() / ( 2 * m_textSize.x() ); double minDomainXStepSize = ( domainMaxX - domainMinX ) / xTickMaxCount; caf::TickMarkGenerator xTickCreator( domainMinX, domainMaxX, minDomainXStepSize ); m_domainCoordsXValues = xTickCreator.tickMarkValues(); double minDomainYStepSize = ( domainMaxY - domainMinY ) / yTickMaxCount; caf::TickMarkGenerator yTickCreator( domainMinY, domainMaxY, minDomainYStepSize ); m_domainCoordsYValues = yTickCreator.tickMarkValues(); m_windowTickXValues.clear(); Vec3d windowPoint; for ( double domainX : m_domainCoordsXValues ) { Vec3d displayDomainTick; if ( m_domainAxes == XY_AXES ) { displayDomainTick = Vec3d( domainX, domainMinY, 0 ); } else { displayDomainTick = Vec3d( domainX, 0, domainMinY ); } if ( m_dispalyCoordsTransform.notNull() ) { displayDomainTick = m_dispalyCoordsTransform->transformToDisplayCoord( displayDomainTick ); } camera->project( displayDomainTick, &windowPoint ); m_windowTickXValues.push_back( windowPoint.x() ); } m_windowTickYValues.clear(); for ( double domainY : m_domainCoordsYValues ) { Vec3d displayDomainTick; if ( m_domainAxes == XY_AXES ) { displayDomainTick = Vec3d( domainMinX, domainY, 0 ); } else { displayDomainTick = Vec3d( domainMinX, 0, domainY ); } if ( m_dispalyCoordsTransform.notNull() ) { displayDomainTick = m_dispalyCoordsTransform->transformToDisplayCoord( displayDomainTick ); } camera->project( displayDomainTick, &windowPoint ); m_windowTickYValues.push_back( windowPoint.y() ); } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- cvf::Vec2ui RivWindowEdgeAxesOverlayItem::sizeHint() { return m_windowSize; } //-------------------------------------------------------------------------------------------------- /// Set color of the text and lines to be rendered //-------------------------------------------------------------------------------------------------- void RivWindowEdgeAxesOverlayItem::setTextColor( const Color3f& color ) { m_textColor = color; } //-------------------------------------------------------------------------------------------------- /// Returns the color of the text and lines //-------------------------------------------------------------------------------------------------- const Color3f& RivWindowEdgeAxesOverlayItem::textColor() const { return m_textColor; } //-------------------------------------------------------------------------------------------------- /// Hardware rendering using shader programs //-------------------------------------------------------------------------------------------------- void RivWindowEdgeAxesOverlayItem::render( OpenGLContext* oglContext, const Vec2i& position, const Vec2ui& size ) { renderGeneric( oglContext, position, size, false ); } //-------------------------------------------------------------------------------------------------- /// Software rendering using software //-------------------------------------------------------------------------------------------------- void RivWindowEdgeAxesOverlayItem::renderSoftware( OpenGLContext* oglContext, const Vec2i& position, const Vec2ui& size ) { renderGeneric( oglContext, position, size, true ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- bool RivWindowEdgeAxesOverlayItem::pick( int oglXCoord, int oglYCoord, const Vec2i& position, const Vec2ui& size ) { return false; } //-------------------------------------------------------------------------------------------------- /// Set up camera/viewport and render //-------------------------------------------------------------------------------------------------- void RivWindowEdgeAxesOverlayItem::renderGeneric( OpenGLContext* oglContext, const Vec2i& position, const Vec2ui& size, bool software ) { if ( size.x() <= 0 || size.y() <= 0 || ( m_windowTickXValues.size() == 0 && m_windowTickYValues.size() == 0 ) ) { return; } Camera camera; camera.setViewport( position.x(), position.y(), size.x(), size.y() ); camera.setProjectionAsPixelExact2D(); camera.setViewMatrix( Mat4d::IDENTITY ); camera.applyOpenGL(); camera.viewport()->applyOpenGL( oglContext, Viewport::CLEAR_DEPTH ); TextDrawer textDrawer( m_font.p() ); addTextToTextDrawer( &textDrawer ); if ( software ) { renderSoftwareFrameAndTickLines( oglContext ); textDrawer.renderSoftware( oglContext, camera ); } else { const MatrixState matrixState( camera ); renderShaderFrameAndTickLines( oglContext, matrixState ); textDrawer.render( oglContext, camera ); } CVF_CHECK_OGL( oglContext ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RivWindowEdgeAxesOverlayItem::addTextToTextDrawer( TextDrawer* textDrawer ) { textDrawer->setVerticalAlignment( TextDrawer::CENTER ); textDrawer->setTextColor( m_textColor ); // Bottom X - axis text { const float textYTop = m_windowSize.y() - m_pixelSpacing - m_textSize.y() * 0.5f; const float textYBott = m_pixelSpacing + m_textSize.y() * 0.5f; size_t numTicks = m_domainCoordsXValues.size(); size_t i; for ( i = 0; i < numTicks; i++ ) { float textX = static_cast( m_windowTickXValues[i] ); double tickValue = m_domainCoordsXValues[i]; String valueString; valueString = String::number( tickValue, 'f', 0 ); auto labelSize = m_font->textExtent( valueString ); Vec2f pos( textX - labelSize.x() * 0.5f, textYBott ); textDrawer->addText( valueString, pos ); pos[1] = textYTop; textDrawer->addText( valueString, pos ); } } // Right Y - axis texts { const float textXRight = m_windowSize.x() - m_pixelSpacing - m_textSize.x(); const float textXLeft = m_frameBorderWidth - m_tickLineLength - m_pixelSpacing; size_t numTicks = m_domainCoordsYValues.size(); size_t i; for ( i = 0; i < numTicks; i++ ) { float textY = static_cast( m_windowTickYValues[i] ); double tickValue = m_isSwitchingYAxisValueSign ? -m_domainCoordsYValues[i] : m_domainCoordsYValues[i]; String valueString; valueString = String::number( tickValue, 'f', 0 ); auto labelSize = m_font->textExtent( valueString ); Vec2f pos( textXRight, textY ); textDrawer->addText( valueString, pos ); Vec2f posl( textXLeft - labelSize.x(), textY ); textDrawer->addText( valueString, posl ); } } } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- std::array RivWindowEdgeAxesOverlayItem::frameVertexArray() { float windowWidth = static_cast( m_windowSize.x() ); float windowHeight = static_cast( m_windowSize.y() ); // 3 2 // 7 6 // // 4 5 // 0 1 std::array vertexArray = { Vec3f( 0.0f, 0.0f, 0.0f ), Vec3f( windowWidth, 0.0f, 0.0f ), Vec3f( windowWidth, windowHeight, 0.0f ), Vec3f( 0.0f, windowHeight, 0.0f ), Vec3f( m_frameBorderWidth, m_frameBorderHeight, 0.0f ), Vec3f( windowWidth - m_frameBorderWidth, m_frameBorderHeight, 0.0f ), Vec3f( windowWidth - m_frameBorderWidth, windowHeight - m_frameBorderHeight, 0.0f ), Vec3f( m_frameBorderWidth, windowHeight - m_frameBorderHeight, 0.0f ), }; return vertexArray; } //-------------------------------------------------------------------------------------------------- /// Draw the legend using immediate mode OpenGL //-------------------------------------------------------------------------------------------------- void RivWindowEdgeAxesOverlayItem::renderSoftwareFrameAndTickLines( OpenGLContext* oglContext ) { RenderStateDepth depth( false ); depth.applyOpenGL( oglContext ); RenderStateLighting_FF lighting( false ); lighting.applyOpenGL( oglContext ); RenderStateBlending blend; blend.configureTransparencyBlending(); blend.applyOpenGL( oglContext ); // Frame vertices std::array vertexArray = frameVertexArray(); glColor4fv( m_frameColor.ptr() ); glBegin( GL_TRIANGLE_FAN ); glVertex3fv( vertexArray[0].ptr() ); glVertex3fv( vertexArray[1].ptr() ); glVertex3fv( vertexArray[5].ptr() ); glVertex3fv( vertexArray[4].ptr() ); glEnd(); glBegin( GL_TRIANGLE_FAN ); glVertex3fv( vertexArray[1].ptr() ); glVertex3fv( vertexArray[2].ptr() ); glVertex3fv( vertexArray[6].ptr() ); glVertex3fv( vertexArray[5].ptr() ); glEnd(); glBegin( GL_TRIANGLE_FAN ); glVertex3fv( vertexArray[3].ptr() ); glVertex3fv( vertexArray[0].ptr() ); glVertex3fv( vertexArray[4].ptr() ); glVertex3fv( vertexArray[7].ptr() ); glEnd(); glBegin( GL_TRIANGLE_FAN ); glVertex3fv( vertexArray[2].ptr() ); glVertex3fv( vertexArray[3].ptr() ); glVertex3fv( vertexArray[7].ptr() ); glVertex3fv( vertexArray[6].ptr() ); glEnd(); // Render Line around { cvf::Color4f lineColorWithAlpha( m_lineColor, m_showAxisLines ? 0.25f : 1.0f ); glColor4fv( lineColorWithAlpha.ptr() ); glBegin( GL_LINES ); // Frame lines glVertex3fv( vertexArray[7].ptr() ); glVertex3fv( vertexArray[4].ptr() ); glVertex3fv( vertexArray[4].ptr() ); glVertex3fv( vertexArray[5].ptr() ); glVertex3fv( vertexArray[5].ptr() ); glVertex3fv( vertexArray[6].ptr() ); glVertex3fv( vertexArray[6].ptr() ); glVertex3fv( vertexArray[7].ptr() ); // X - axis Tick lines for ( double txpos : m_windowTickXValues ) { if ( m_showAxisLines ) { Vec3f p1( Vec3f::ZERO ); Vec3f p2( Vec3f::ZERO ); p1[0] = (float)txpos; p1[1] = m_frameBorderHeight - m_tickLineLength; p2[0] = (float)txpos; p2[1] = m_windowSize.y() - m_frameBorderHeight + m_tickLineLength; glVertex3fv( p1.ptr() ); glVertex3fv( p2.ptr() ); } else { Vec3f p1( Vec3f::ZERO ); Vec3f p2( Vec3f::ZERO ); p1[0] = (float)txpos; p1[1] = m_frameBorderHeight; p2[0] = (float)txpos; p2[1] = m_frameBorderHeight - m_tickLineLength; glVertex3fv( p1.ptr() ); glVertex3fv( p2.ptr() ); p1[0] = (float)txpos; p1[1] = m_windowSize.y() - m_frameBorderHeight; p2[0] = (float)txpos; p2[1] = m_windowSize.y() - m_frameBorderHeight + m_tickLineLength; glVertex3fv( p1.ptr() ); glVertex3fv( p2.ptr() ); } } // Left Y - axis Tick lines for ( double typos : m_windowTickYValues ) { if ( m_showAxisLines ) { Vec3f p1( Vec3f::ZERO ); Vec3f p2( Vec3f::ZERO ); p1[0] = m_frameBorderWidth - m_tickLineLength; p1[1] = (float)typos; p2[0] = m_windowSize.x() - m_frameBorderWidth + m_tickLineLength; p2[1] = (float)typos; glVertex3fv( p1.ptr() ); glVertex3fv( p2.ptr() ); } else { Vec3f p1( Vec3f::ZERO ); Vec3f p2( Vec3f::ZERO ); p1[0] = m_frameBorderWidth; p1[1] = (float)typos; p2[0] = m_frameBorderWidth - m_tickLineLength; p2[1] = (float)typos; glVertex3fv( p1.ptr() ); glVertex3fv( p2.ptr() ); p1[0] = m_windowSize.x() - m_frameBorderWidth; p1[1] = (float)typos; p2[0] = m_windowSize.x() - m_frameBorderWidth + m_tickLineLength; p2[1] = (float)typos; glVertex3fv( p1.ptr() ); glVertex3fv( p2.ptr() ); } } glEnd(); } // Reset render states RenderStateLighting_FF resetLighting; resetLighting.applyOpenGL( oglContext ); RenderStateDepth resetDepth; resetDepth.applyOpenGL( oglContext ); RenderStateBlending resetblend; resetblend.applyOpenGL( oglContext ); CVF_CHECK_OGL( oglContext ); } //-------------------------------------------------------------------------------------------------- /// Draw the frame using shader programs //-------------------------------------------------------------------------------------------------- void RivWindowEdgeAxesOverlayItem::renderShaderFrameAndTickLines( OpenGLContext* oglContext, const MatrixState& matrixState ) { CVF_CALLSITE_OPENGL( oglContext ); RenderStateDepth depth( false ); depth.applyOpenGL( oglContext ); RenderStateLine line( static_cast( m_lineWidth ) ); line.applyOpenGL( oglContext ); RenderStateBlending blend; blend.configureTransparencyBlending(); blend.applyOpenGL( oglContext ); // Shader program ref shaderProgram = oglContext->resourceManager()->getLinkedUnlitColorShaderProgram( oglContext ); CVF_TIGHT_ASSERT( shaderProgram.notNull() ); if ( shaderProgram->useProgram( oglContext ) ) { shaderProgram->clearUniformApplyTracking(); shaderProgram->applyFixedUniforms( oglContext, matrixState ); } // Frame vertices std::array vertexArray = frameVertexArray(); glBindBuffer( GL_ARRAY_BUFFER, 0 ); glEnableVertexAttribArray( ShaderProgram::VERTEX ); glVertexAttribPointer( ShaderProgram::VERTEX, 3, GL_FLOAT, GL_FALSE, 0, vertexArray.data() ); // Draw frame background UniformFloat backgroundColorUniform( "u_color", m_frameColor ); shaderProgram->applyUniform( oglContext, backgroundColorUniform ); // Triangle indices for the frame background static const ushort backgroundTriangleIndices[] = { 0, 1, 5, 0, 5, 4, 1, 2, 6, 1, 6, 5, 3, 0, 4, 3, 4, 7, 2, 3, 6, 3, 7, 6 }; glDrawRangeElements( GL_TRIANGLES, 0, 7, 24, GL_UNSIGNED_SHORT, backgroundTriangleIndices ); // Draw frame border lines UniformFloat uniformColor( "u_color", Color4f( m_lineColor, m_showAxisLines ? 0.25f : 1.0f ) ); shaderProgram->applyUniform( oglContext, uniformColor ); static const ushort frameLineIndices[] = { 7, 4, 4, 5, 5, 6, 6, 7 }; glDrawRangeElements( GL_LINES, 0, 7, 8, GL_UNSIGNED_SHORT, frameLineIndices ); // Render tickmarks static const ushort tickLineIndices[] = { 0, 1 }; // X - axis Tick lines for ( double txpos : m_windowTickXValues ) { if ( m_showAxisLines ) { vertexArray[0][0] = (float)txpos; vertexArray[0][1] = m_frameBorderHeight - m_tickLineLength; vertexArray[1][0] = (float)txpos; vertexArray[1][1] = m_windowSize.y() - m_frameBorderHeight + m_tickLineLength; glDrawRangeElements( GL_LINES, 0, 1, 2, GL_UNSIGNED_SHORT, tickLineIndices ); } else { vertexArray[0][0] = (float)txpos; vertexArray[0][1] = m_frameBorderHeight; vertexArray[1][0] = (float)txpos; vertexArray[1][1] = m_frameBorderHeight - m_tickLineLength; glDrawRangeElements( GL_LINES, 0, 1, 2, GL_UNSIGNED_SHORT, tickLineIndices ); vertexArray[0][0] = (float)txpos; vertexArray[0][1] = m_windowSize.y() - m_frameBorderHeight; vertexArray[1][0] = (float)txpos; vertexArray[1][1] = m_windowSize.y() - m_frameBorderHeight + m_tickLineLength; glDrawRangeElements( GL_LINES, 0, 1, 2, GL_UNSIGNED_SHORT, tickLineIndices ); } } // Left Y - axis Tick lines for ( double typos : m_windowTickYValues ) { if ( m_showAxisLines ) { vertexArray[0][0] = m_frameBorderWidth - m_tickLineLength; vertexArray[0][1] = (float)typos; vertexArray[1][0] = m_windowSize.x() - m_frameBorderWidth + m_tickLineLength; vertexArray[1][1] = (float)typos; glDrawRangeElements( GL_LINES, 0, 1, 2, GL_UNSIGNED_SHORT, tickLineIndices ); } else { vertexArray[0][0] = m_frameBorderWidth; vertexArray[0][1] = (float)typos; vertexArray[1][0] = m_frameBorderWidth - m_tickLineLength; vertexArray[1][1] = (float)typos; glDrawRangeElements( GL_LINES, 0, 1, 2, GL_UNSIGNED_SHORT, tickLineIndices ); vertexArray[0][0] = m_windowSize.x() - m_frameBorderWidth; vertexArray[0][1] = (float)typos; vertexArray[1][0] = m_windowSize.x() - m_frameBorderWidth + m_tickLineLength; vertexArray[1][1] = (float)typos; glDrawRangeElements( GL_LINES, 0, 1, 2, GL_UNSIGNED_SHORT, tickLineIndices ); } } glDisableVertexAttribArray( ShaderProgram::VERTEX ); CVF_TIGHT_ASSERT( shaderProgram.notNull() ); shaderProgram->useNoProgram( oglContext ); // Reset render states RenderStateDepth resetDepth; resetDepth.applyOpenGL( oglContext ); RenderStateLine resetLine; resetLine.applyOpenGL( oglContext ); RenderStateBlending resetblend; resetblend.applyOpenGL( oglContext ); CVF_CHECK_OGL( oglContext ); } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RivWindowEdgeAxesOverlayItem::setLineColor( const Color3f& lineColor ) { m_lineColor = lineColor; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- const Color3f& RivWindowEdgeAxesOverlayItem::lineColor() const { return m_lineColor; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RivWindowEdgeAxesOverlayItem::setFrameColor( const Color4f& frameColor ) { m_frameColor = frameColor; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RivWindowEdgeAxesOverlayItem::setDomainAxes( DomainAxes axes ) { m_domainAxes = axes; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RivWindowEdgeAxesOverlayItem::setIsSwitchingYAxisSign( bool switchSign ) { m_isSwitchingYAxisValueSign = switchSign; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- void RivWindowEdgeAxesOverlayItem::setShowAxisLines( bool showAxisLines ) { m_showAxisLines = showAxisLines; }