From de6c1742740e35b7dcf54d1c24d2975415bab544 Mon Sep 17 00:00:00 2001 From: Paul-George Roberts Date: Mon, 11 Nov 2024 17:24:51 -0500 Subject: [PATCH 1/2] Visualisers : Contribute viewer visualisers --- contrib/visualisers/CsVisualiseOrientTool.cpp | 930 ++++++++++ contrib/visualisers/CsVisualiseOrientTool.h | 186 ++ contrib/visualisers/CsVisualiseValueTool.cpp | 1573 +++++++++++++++++ contrib/visualisers/CsVisualiseValueTool.h | 226 +++ contrib/visualisers/CsVisualiseVectorTool.cpp | 973 ++++++++++ contrib/visualisers/CsVisualiseVectorTool.h | 195 ++ .../visualisers/CsVisualiseVertexIdTool.cpp | 1373 ++++++++++++++ contrib/visualisers/CsVisualiseVertexIdTool.h | 223 +++ 8 files changed, 5679 insertions(+) create mode 100644 contrib/visualisers/CsVisualiseOrientTool.cpp create mode 100644 contrib/visualisers/CsVisualiseOrientTool.h create mode 100644 contrib/visualisers/CsVisualiseValueTool.cpp create mode 100644 contrib/visualisers/CsVisualiseValueTool.h create mode 100644 contrib/visualisers/CsVisualiseVectorTool.cpp create mode 100644 contrib/visualisers/CsVisualiseVectorTool.h create mode 100644 contrib/visualisers/CsVisualiseVertexIdTool.cpp create mode 100644 contrib/visualisers/CsVisualiseVertexIdTool.h diff --git a/contrib/visualisers/CsVisualiseOrientTool.cpp b/contrib/visualisers/CsVisualiseOrientTool.cpp new file mode 100644 index 00000000000..0f58f37b3c7 --- /dev/null +++ b/contrib/visualisers/CsVisualiseOrientTool.cpp @@ -0,0 +1,930 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2024, Cinesite VFX Ltd. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above +// copyright notice, this list of conditions and the following +// disclaimer. +// +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided with +// the distribution. +// +// * Neither the name of John Haddon nor the names of +// any other contributors to this software may be used to endorse or +// promote products derived from this software without specific prior +// written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +IECORE_PUSH_DEFAULT_VISIBILITY +#if GAFFER_COMPATIBILITY_VERSION < MAKE_GAFFER_COMPATIBILITY_VERSION( 1, 3 ) +#include +#else +#include +#endif +IECORE_POP_DEFAULT_VISIBILITY + +#define private public +#include +#include +#undef private + +#include "CsVisualiseOrientTool.h" + +#include +#include +#include +#include +#include +#include +#if GAFFER_COMPATIBILITY_VERSION >= MAKE_GAFFER_COMPATIBILITY_VERSION( 1, 5 ) +#include +#else +#include +#endif + +#include +#include + +#include +#include +//#include +//#include +//#include +#include +#if ( GAFFER_MILESTONE_VERSION == 0 && GAFFER_MAJOR_VERSION < 61 ) +#include +#endif + +#if GAFFER_COMPATIBILITY_VERSION < MAKE_GAFFER_COMPATIBILITY_VERSION( 1, 3 ) +#include +#include +#else +#include +#include +#endif + +#include +#include +#include + +#include +#include +#include +#include + +namespace +{ + // scale and colour constants + + float const g_scaleDefault = 1.f; + float const g_scaleMin = 10.f * std::numeric_limits< float >::min(); + float const g_scaleInc = 0.01f; + + Imath::Color3f const g_colourXDefault( 1.f, 0.f, 0.f ); + Imath::Color3f const g_colourYDefault( 0.f, 1.f, 0.f ); + Imath::Color3f const g_colourZDefault( 0.f, 0.f, 1.f ); + + // name of P primitive variable + + std::string const g_pName( "P" ); + + // uniform block structure (std140 layout) + + struct UniformBlock + { + alignas( 16 ) Imath::M44f o2c; + alignas( 16 ) Imath::Color3f colourX; + alignas( 16 ) Imath::Color3f colourY; + alignas( 16 ) Imath::Color3f colourZ; + alignas( 16 ) float scale; // NOTE : following vec3 array must align to 16 byte address + }; + + GLuint const g_uniformBlockBindingIndex = 0; + +# define UNIFORM_BLOCK_GLSL_SOURCE \ + "layout( std140, row_major ) uniform UniformBlock\n" \ + "{\n" \ + " mat4 o2c;\n" \ + " vec3 colour[ 3 ];\n" \ + " float scale;\n" \ + "} uniforms;\n" + +# define ATTRIB_GLSL_LOCATION_PS 0 +# define ATTRIB_GLSL_LOCATION_QS 1 + +# define ATTRIB_GLSL_SOURCE \ + "layout( location = " BOOST_PP_STRINGIZE( ATTRIB_GLSL_LOCATION_PS ) " ) in vec3 ps;\n" \ + "layout( location = " BOOST_PP_STRINGIZE( ATTRIB_GLSL_LOCATION_QS ) " ) in vec4 qs;\n" + +# define INTERFACE_BLOCK_GLSL_SOURCE( STORAGE, NAME ) \ + BOOST_PP_STRINGIZE( STORAGE ) " InterfaceBlock\n" \ + "{\n" \ + " flat vec3 colour;\n" \ + "} " BOOST_PP_STRINGIZE( NAME ) ";\n" + + // opengl vertex shader code + + std::string const g_vertSource + ( + "#version 330\n" + + UNIFORM_BLOCK_GLSL_SOURCE + + ATTRIB_GLSL_SOURCE + + INTERFACE_BLOCK_GLSL_SOURCE( out, outputs ) + + "void main()\n" + "{\n" + " vec3 position = ps;\n" + " int axis = gl_VertexID / 2;\n" + + " if( ( gl_VertexID % 2 ) > 0 )\n" + " {\n" + " float r = qs.x;\n" + " vec3 v = qs.yzw;\n" + " mat3 m = mat3(\n" + " 1.0 - 2.0 * dot( v.yz, v.yz ),\n" + " 2.0 * dot( v.xz, vec2( v.y, r ) ),\n" + " 2.0 * dot( v.zy, vec2( v.x, -r ) ),\n" + + " 2.0 * dot( v.xz, vec2( v.y, -r ) ),\n" + " 1.0 - 2.0 * dot( v.zx, v.zx ),\n" + " 2.0 * dot( v.yx, vec2( v.z, r ) ),\n" + + " 2.0 * dot( v.zy, vec2( v.x, r ) ),\n" + " 2.0 * dot( v.yx, vec2( v.z, -r ) ),\n" + " 1.0 - 2.0 * dot( v.yx, v.yx ) );\n" + + " position += normalize( m[ axis ] ) * uniforms.scale;\n" + " }\n" + + " gl_Position = vec4( position, 1.0 ) * uniforms.o2c;\n" + " outputs.colour = uniforms.colour[ axis ];\n" + "}\n" + ); + + // opengl fragment shader code + + std::string const g_fragSource + ( + "#version 330\n" + + UNIFORM_BLOCK_GLSL_SOURCE + + INTERFACE_BLOCK_GLSL_SOURCE( in, inputs ) + + "layout( location = 0 ) out vec4 cs;\n" + + "void main()\n" + "{\n" + " cs = vec4( inputs.colour, 1.0 );\n" + "}\n" + ); + + // the gadget that does the actual opengl drawing of the orientation frames + + struct Gadget + : public GafferUI::Gadget + { + explicit + Gadget + ( + CSGafferUI::CsVisualiseOrientTool const& tool, + std::string const& name = "CsVisualiseOrientGadget" + ) + : GafferUI::Gadget( name ) + , m_tool( & tool ) + , m_shader() + , m_uniformBuffer() + {} + + void resetTool() + { + m_tool = nullptr; + } + + protected: + + void +# if ( GAFFER_MILESTONE_VERSION > 0 || GAFFER_MAJOR_VERSION >= 61 ) + renderLayer +# else + doRenderLayer +# endif + ( + GafferUI::Gadget::Layer layer, + GafferUI::Style const* style +# if ( GAFFER_MILESTONE_VERSION > 0 || GAFFER_MAJOR_VERSION >= 61 ) + , GafferUI::Gadget::RenderReason reason +# endif + ) + const override + { + if( ( layer != GafferUI::Gadget::Layer::MidFront ) || +# if ( GAFFER_MILESTONE_VERSION > 0 || GAFFER_MAJOR_VERSION >= 61 ) + ( GafferUI::Gadget::isSelectionRender( reason ) ) ) +# else + ( IECoreGL::Selector::currentSelector() ) ) +# endif + { + return; + } + + // check tool reference valid + + if( m_tool == nullptr ) + { + return; + } + + // get parent viewport gadget + + GafferUI::ViewportGadget const* const viewportGadget = + ancestor< GafferUI::ViewportGadget >(); + if( viewportGadget == nullptr ) + { + return; + } + + // bootleg shader + + buildShader(); + + if( ! m_shader ) + { + return; + } + + // get the cached converter from IECoreGL, this is used to convert primitive + // variable data to opengl buffers which will be shared with the IECoreGL renderer + + IECoreGL::CachedConverter* const converter = + IECoreGL::CachedConverter::defaultCachedConverter(); + + // bootleg uniform buffer + + GLint uniformBinding; + glGetIntegerv( GL_UNIFORM_BUFFER_BINDING, & uniformBinding ); + + if( ! m_uniformBuffer ) + { + GLuint buffer = 0u; + glGenBuffers( 1, & buffer ); + glBindBuffer( GL_UNIFORM_BUFFER, buffer ); + glBufferData( GL_UNIFORM_BUFFER, sizeof( UniformBlock ), 0, GL_DYNAMIC_DRAW ); + m_uniformBuffer.reset( new IECoreGL::Buffer( buffer ) ); + } + + glBindBufferBase( GL_UNIFORM_BUFFER, g_uniformBlockBindingIndex, m_uniformBuffer->m_buffer ); + + // get the name of the primitive variable to visualise + + std::string const& name = m_tool->namePlug()->getValue(); + + // get scale factor and colour + + UniformBlock uniforms; + uniforms.colourX = m_tool->colourXPlug()->getValue(); + uniforms.colourY = m_tool->colourYPlug()->getValue(); + uniforms.colourZ = m_tool->colourZPlug()->getValue(); + uniforms.scale = m_tool->scalePlug()->getValue(); + + // get the world to clip space matrix + + Imath::M44f v2c; + glGetFloatv( GL_PROJECTION_MATRIX, v2c.getValue() ); + Imath::M44f const w2c = viewportGadget->getCameraTransform().gjInverse() * v2c; + + // set opengl state + + GLfloat lineWidth; + glGetFloatv( GL_LINE_WIDTH, & lineWidth ); + glLineWidth( 1.f ); + + GLboolean const depthEnabled = glIsEnabled( GL_DEPTH_TEST ); + if( ! depthEnabled ) glEnable( GL_DEPTH_TEST ); + + GLboolean depthWriteEnabled; + glGetBooleanv( GL_DEPTH_WRITEMASK, & depthWriteEnabled ); + if( depthWriteEnabled ) glDepthMask( GL_FALSE ); + + GLboolean lineSmooth; + glGetBooleanv( GL_LINE_SMOOTH, & lineSmooth ); + if( lineSmooth ) glDisable( GL_LINE_SMOOTH ); + + GLboolean const blendEnabled = glIsEnabled( GL_BLEND ); + if( blendEnabled ) glDisable( GL_BLEND ); + + // enable shader program + + GLint shaderProgram; + glGetIntegerv( GL_CURRENT_PROGRAM, & shaderProgram ); + glUseProgram( m_shader->program() ); + + // set opengl vertex attribute array state + + GLint arrayBinding; + glGetIntegerv( GL_ARRAY_BUFFER_BINDING, & arrayBinding ); + + glPushClientAttrib( GL_CLIENT_VERTEX_ARRAY_BIT ); + + glVertexAttribDivisor( ATTRIB_GLSL_LOCATION_PS, 1 ); + glEnableVertexAttribArray( ATTRIB_GLSL_LOCATION_PS ); + glVertexAttribDivisor( ATTRIB_GLSL_LOCATION_QS, 1 ); + glEnableVertexAttribArray( ATTRIB_GLSL_LOCATION_QS ); + + // loop through current selection + + for( std::vector< CSGafferUI::CsVisualiseOrientTool::Selection >::const_iterator + it = m_tool->selection().begin(), + itEnd = m_tool->selection().end(); it != itEnd; ++it ) + { + GafferScene::ScenePlug::PathScope scope( &( ( *it ).context() ), &( ( *it ).path() ) ); + + // check path exists + + if( !( ( *it ).scene().existsPlug()->getValue() ) ) + { + continue; + } + + // extract primitive + + IECoreScene::ConstPrimitivePtr const primitive = + IECore::runTimeCast< IECoreScene::Primitive const >( + ( *it ).scene().objectPlug()->getValue() ); + + if( ! primitive ) + { + continue; + } + + // retrieve cached IECoreGL primitive + + IECoreGL::ConstPrimitivePtr const primitiveGL = + IECore::runTimeCast< IECoreGL::Primitive const >( + converter->convert( primitive.get() ) ); + + if( ! primitiveGL ) + { + continue; + } + + // find "P" vertex attribute + + IECoreGL::Primitive::AttributeMap::const_iterator const pit = + primitiveGL->m_vertexAttributes.find( g_pName ); + if( pit == primitiveGL->m_vertexAttributes.end() ) + { + continue; + } + + IECore::ConstV3fVectorDataPtr const pData = + IECore::runTimeCast< IECore::V3fVectorData const >( ( *pit ).second ); + if( ! pData ) + { + continue; + } + + // find named vertex attribute + // + // NOTE : conversion to IECoreGL mesh may generate vertex attributes (eg. "N") + // so check named primitive variable exists on IECore mesh primitive as well. + + IECoreGL::Primitive::AttributeMap::const_iterator const qit = + primitiveGL->m_vertexAttributes.find( name ); + if( ( qit == primitiveGL->m_vertexAttributes.end() ) || + ( primitive->variables.find( name ) == primitive->variables.end() ) ) + { + continue; + } + + IECore::ConstQuatfVectorDataPtr const qData = + IECore::runTimeCast< IECore::QuatfVectorData const >( ( *qit ).second ); + if( ! qData ) + { + continue; + } + + // retrieve cached opengl buffer data + + IECoreGL::ConstBufferPtr const pBuffer = + IECore::runTimeCast< IECoreGL::Buffer const >( converter->convert( pData.get() ) ); + IECoreGL::ConstBufferPtr const qBuffer = + IECore::runTimeCast< IECoreGL::Buffer const >( converter->convert( qData.get() ) ); + + // get the object to world transform + + Imath::M44f o2w; + GafferScene::ScenePlug::ScenePath path( ( *it ).path() ); + while( ! path.empty() ) + { + scope.setPath( & path ); + o2w = o2w * ( *it ).scene().transformPlug()->getValue(); + path.pop_back(); + } + + // compute object to clip matrix + + uniforms.o2c = o2w * w2c; + + // upload opengl uniform block data + + glBufferData( GL_UNIFORM_BUFFER, sizeof( UniformBlock ), & uniforms, GL_DYNAMIC_DRAW ); + + // instance a three line segments for each element of orientation data + + glBindBuffer( GL_ARRAY_BUFFER, pBuffer->m_buffer ); + glVertexAttribPointer( ATTRIB_GLSL_LOCATION_PS, 3, GL_FLOAT, GL_FALSE, 0, + static_cast< void const* >( 0 ) ); + glBindBuffer( GL_ARRAY_BUFFER, qBuffer->m_buffer ); + glVertexAttribPointer( ATTRIB_GLSL_LOCATION_QS, 4, GL_FLOAT, GL_FALSE, 0, + static_cast< void const* >( 0 ) ); + glDrawArraysInstanced( GL_LINES, 0, 6, static_cast< GLsizei >( pData->readable().size() ) ); + } + + // restore opengl state + + glPopClientAttrib(); + glBindBuffer( GL_ARRAY_BUFFER, arrayBinding ); + glBindBuffer( GL_UNIFORM_BUFFER, uniformBinding ); + + glLineWidth( lineWidth ); + + if( lineSmooth ) glEnable( GL_LINE_SMOOTH ); + if( blendEnabled ) glEnable( GL_BLEND ); + if( ! depthEnabled ) glDisable( GL_DEPTH_TEST ); + if( depthWriteEnabled ) glDepthMask( GL_TRUE ); + glUseProgram( shaderProgram ); + } + +# if ( GAFFER_MILESTONE_VERSION > 0 || GAFFER_MAJOR_VERSION >= 61 ) + Imath::Box3f renderBound() const override + { + // NOTE : for now just return an infinite box + + Imath::Box3f b; + b.makeInfinite(); + return b; + } +# endif + +# if ( GAFFER_MILESTONE_VERSION > 0 || GAFFER_MAJOR_VERSION >= 61 ) + unsigned layerMask() const override + { + return ( m_tool ) + ? static_cast< unsigned >( GafferUI::Gadget::Layer::MidFront ) + : static_cast< unsigned >( 0 ); + } +# else + bool hasLayer( GafferUI::Gadget::Layer layer ) + { + return ( m_tool && + ( layer == GafferUI::Gadget::Layer::MidFront ) ); + } +# endif + + private: + + void buildShader() const + { + if( ! m_shader ) + { + m_shader = IECoreGL::ShaderLoader::defaultShaderLoader()->create( + g_vertSource, std::string(), g_fragSource ); + if( m_shader ) + { + GLuint const program = m_shader->program(); + GLuint const blockIndex = glGetUniformBlockIndex( program, "UniformBlock" ); + if( blockIndex != GL_INVALID_INDEX ) + { + glUniformBlockBinding( program, blockIndex, g_uniformBlockBindingIndex ); + } + } + } + } + + CSGafferUI::CsVisualiseOrientTool const* m_tool; + mutable IECoreGL::ConstShaderPtr m_shader; + mutable IECoreGL::ConstBufferPtr m_uniformBuffer; + }; + +} // namespace + +namespace CSGafferUI +{ + GAFFER_NODE_DEFINE_TYPE( CsVisualiseOrientTool ) + + GafferUI::Tool::ToolDescription< CsVisualiseOrientTool, GafferSceneUI::SceneView > CsVisualiseOrientTool::m_toolDescription; + + size_t CsVisualiseOrientTool::m_firstPlugIndex = 0; + + CsVisualiseOrientTool::CsVisualiseOrientTool + ( + GafferSceneUI::SceneView* const view, + std::string const& name + ) + : GafferSceneUI::SelectionTool( view, name ) +# if GAFFER_COMPATIBILITY_VERSION < MAKE_GAFFER_COMPATIBILITY_VERSION( 1, 5 ) + , m_contextChangedConnection() +# endif + , m_preRenderConnection() + , m_gadget( new Gadget( *this ) ) + , m_selection() + , m_gadgetDirty( true ) + , m_selectionDirty( true ) + , m_priorityPathsDirty( true ) + { + // add gadget to view and hide + + view->viewportGadget()->addChild( m_gadget ); + m_gadget->setVisible( false ); + + // store offset of first plug + + storeIndexOfNextChild( m_firstPlugIndex ); + + // add child plugs + + addChild( new Gaffer::StringPlug( "name", Gaffer::Plug::In, "orient" ) ); + addChild( new Gaffer::FloatPlug( "scale", Gaffer::Plug::In, g_scaleDefault, g_scaleMin ) ); + addChild( new Gaffer::Color3fPlug( "colourX", Gaffer::Plug::In, g_colourXDefault ) ); + addChild( new Gaffer::Color3fPlug( "colourY", Gaffer::Plug::In, g_colourYDefault ) ); + addChild( new Gaffer::Color3fPlug( "colourZ", Gaffer::Plug::In, g_colourZDefault ) ); + addChild( new GafferScene::ScenePlug( "__scene", Gaffer::Plug::In ) ); + + // connect out internal scene plug to the parent view's scene plug + + internalScenePlug()->setInput( view->inPlug< GafferScene::ScenePlug >() ); + + // connect signal handlers + + view->viewportGadget()->keyPressSignal().connect( + boost::bind( & CsVisualiseOrientTool::keyPress, this, boost::placeholders::_2 ) ); + + plugDirtiedSignal().connect( + boost::bind( & CsVisualiseOrientTool::plugDirtied, this, boost::placeholders::_1 ) ); + +# if GAFFER_COMPATIBILITY_VERSION >= MAKE_GAFFER_COMPATIBILITY_VERSION( 1, 5 ) + view->contextChangedSignal().connect( + boost::bind( & CsVisualiseOrientTool::contextChanged, this ) ); + GafferSceneUI::ScriptNodeAlgo::selectedPathsChangedSignal( view->scriptNode() ).connect( + boost::bind( &CsVisualiseOrientTool::selectedPathsChanged, this ) ); +# else + connectToViewContext(); + view->contextChangedSignal().connect( + boost::bind( & CsVisualiseOrientTool::connectToViewContext, this ) ); +# endif + + Gaffer::Metadata::plugValueChangedSignal().connect( + boost::bind( & CsVisualiseOrientTool::metadataChanged, this, boost::placeholders::_3 ) ); + Gaffer::Metadata::nodeValueChangedSignal().connect( + boost::bind( & CsVisualiseOrientTool::metadataChanged, this, boost::placeholders::_2 ) ); + } + + CsVisualiseOrientTool::~CsVisualiseOrientTool() + { + // NOTE : ensure that the gadget's reference to the tool is reset + + static_cast< Gadget* >( m_gadget.get() )->resetTool(); + } + + Gaffer::StringPlug* CsVisualiseOrientTool::namePlug() + { + return const_cast< Gaffer::StringPlug* >( + static_cast< CsVisualiseOrientTool const* >( this )->namePlug() ); + } + + Gaffer::StringPlug const* CsVisualiseOrientTool::namePlug() const + { + return getChild< Gaffer::StringPlug >( m_firstPlugIndex + 0 ); + } + + Gaffer::FloatPlug* CsVisualiseOrientTool::scalePlug() + { + return const_cast< Gaffer::FloatPlug* >( + static_cast< CsVisualiseOrientTool const* >( this )->scalePlug() ); + } + + Gaffer::FloatPlug const* CsVisualiseOrientTool::scalePlug() const + { + return getChild< Gaffer::FloatPlug >( m_firstPlugIndex + 1 ); + } + + Gaffer::Color3fPlug* CsVisualiseOrientTool::colourXPlug() + { + return const_cast< Gaffer::Color3fPlug* >( + static_cast< CsVisualiseOrientTool const* >( this )->colourXPlug() ); + } + + Gaffer::Color3fPlug const* CsVisualiseOrientTool::colourXPlug() const + { + return getChild< Gaffer::Color3fPlug >( m_firstPlugIndex + 2 ); + } + + Gaffer::Color3fPlug* CsVisualiseOrientTool::colourYPlug() + { + return const_cast< Gaffer::Color3fPlug* >( + static_cast< CsVisualiseOrientTool const* >( this )->colourYPlug() ); + } + + Gaffer::Color3fPlug const* CsVisualiseOrientTool::colourYPlug() const + { + return getChild< Gaffer::Color3fPlug >( m_firstPlugIndex + 3 ); + } + + Gaffer::Color3fPlug* CsVisualiseOrientTool::colourZPlug() + { + return const_cast< Gaffer::Color3fPlug* >( + static_cast< CsVisualiseOrientTool const* >( this )->colourZPlug() ); + } + + Gaffer::Color3fPlug const* CsVisualiseOrientTool::colourZPlug() const + { + return getChild< Gaffer::Color3fPlug >( m_firstPlugIndex + 4 ); + } + + GafferScene::ScenePlug* CsVisualiseOrientTool::internalScenePlug() + { + return const_cast< GafferScene::ScenePlug* >( + static_cast< CsVisualiseOrientTool const* >( this )->internalScenePlug() ); + } + + GafferScene::ScenePlug const* CsVisualiseOrientTool::internalScenePlug() const + { + return getChild< GafferScene::ScenePlug >( m_firstPlugIndex + 5 ); + } + + std::vector< CsVisualiseOrientTool::Selection > const& CsVisualiseOrientTool::selection() const + { + return m_selection; + } + +# if GAFFER_COMPATIBILITY_VERSION >= MAKE_GAFFER_COMPATIBILITY_VERSION( 1, 5 ) + void CsVisualiseOrientTool::contextChanged() + { + // Context changes can change the scene, which in turn + // dirties our selection. + selectedPathsChanged(); + } + + void CsVisualiseOrientTool::selectedPathsChanged() + { + m_selectionDirty = true; + m_gadgetDirty = true; + m_priorityPathsDirty = true; + } +# else + void CsVisualiseOrientTool::connectToViewContext() + { + m_contextChangedConnection = view()->getContext()->changedSignal().connect( + boost::bind( & CsVisualiseOrientTool::contextChanged, this, boost::placeholders::_2 ) ); + } + + void CsVisualiseOrientTool::contextChanged + ( + IECore::InternedString const& name + ) + { + if( GafferSceneUI::ContextAlgo::affectsSelectedPaths( name ) || + GafferSceneUI::ContextAlgo::affectsLastSelectedPath( name ) || + ! boost::starts_with( name.string(), "ui:" ) ) + { + m_selectionDirty = true; + m_gadgetDirty = true; + m_priorityPathsDirty = true; + } + } +# endif + + void CsVisualiseOrientTool::plugDirtied + ( + Gaffer::Plug const* const plug + ) + { + if( ( plug == activePlug() ) || + ( plug == internalScenePlug()->objectPlug() ) || + ( plug == internalScenePlug()->transformPlug() ) ) + { + m_selectionDirty = true; + m_gadgetDirty = true; + m_priorityPathsDirty = true; + } + else + if( ( plug == namePlug() ) || + ( plug == scalePlug() ) || + ( plug == colourXPlug() ) || + ( plug == colourYPlug() ) || + ( plug == colourZPlug() ) ) + { + m_gadgetDirty = true; + view()->viewportGadget()->renderRequestSignal()( view()->viewportGadget() ); + } + + if( plug == activePlug() ) + { + if( activePlug()->getValue() ) + { + m_preRenderConnection = view()->viewportGadget()->preRenderSignal().connect( + boost::bind( & CsVisualiseOrientTool::preRender, this ) ); + } + else + { + m_preRenderConnection.disconnect(); + m_gadget->setVisible( false ); + + static_cast< GafferSceneUI::SceneGadget* >( view()->viewportGadget()->getPrimaryChild() ) + ->setPriorityPaths( IECore::PathMatcher() ); + } + } + } + + void CsVisualiseOrientTool::metadataChanged + ( + IECore::InternedString const& key + ) + { + if( ! Gaffer::MetadataAlgo::readOnlyAffectedByChange( key ) ) + { + return; + } + + if( ! m_selectionDirty ) + { + m_selectionDirty = true; + } + + if( ! m_gadgetDirty ) + { + m_gadgetDirty = true; + view()->viewportGadget()->renderRequestSignal()( view()->viewportGadget() ); + } + } + + void CsVisualiseOrientTool::updateSelection() const + { + if( ! m_selectionDirty ) + { + return; + } + + m_selection.clear(); + m_selectionDirty = false; + + if( ! activePlug()->getValue() ) + { + return; + } + + GafferScene::ScenePlug const* scene = + internalScenePlug()->getInput< GafferScene::ScenePlug >(); + + if( !( scene ) || + !( scene = scene->getInput< GafferScene::ScenePlug >() ) ) + { + return; + } + +# if GAFFER_COMPATIBILITY_VERSION >= MAKE_GAFFER_COMPATIBILITY_VERSION( 1, 5 ) + IECore::PathMatcher const selectedPaths = + GafferSceneUI::ScriptNodeAlgo::getSelectedPaths( view()->scriptNode() ); +# else + IECore::PathMatcher const selectedPaths = + GafferSceneUI::ContextAlgo::getSelectedPaths( view()->getContext() ); +# endif + + if( selectedPaths.isEmpty() ) + { + return; + } + + for( IECore::PathMatcher::Iterator it = selectedPaths.begin(), + itEnd = selectedPaths.end(); it != itEnd; ++it ) + { +# if GAFFER_COMPATIBILITY_VERSION >= MAKE_GAFFER_COMPATIBILITY_VERSION( 1, 5 ) + m_selection.emplace_back( *( scene ), *it, *( view()->context() ) ); +# else + m_selection.emplace_back( *( scene ), *it, *( view()->getContext() ) ); +# endif + } + } + + void CsVisualiseOrientTool::preRender() + { + updateSelection(); + + if( m_priorityPathsDirty ) + { +# if GAFFER_COMPATIBILITY_VERSION >= MAKE_GAFFER_COMPATIBILITY_VERSION( 1, 5 ) + static_cast< GafferSceneUI::SceneGadget* >( view()->viewportGadget()->getPrimaryChild() ) + ->setPriorityPaths( ( m_selection.empty() ) + ? IECore::PathMatcher() + : GafferSceneUI::ScriptNodeAlgo::getSelectedPaths( view()->scriptNode() ) ); +# else + static_cast< GafferSceneUI::SceneGadget* >( view()->viewportGadget()->getPrimaryChild() ) + ->setPriorityPaths( ( m_selection.empty() ) + ? IECore::PathMatcher() + : GafferSceneUI::ContextAlgo::getSelectedPaths( view()->getContext() ) ); +# endif + m_priorityPathsDirty = false; + } + + if( m_selection.empty() ) + { + m_gadget->setVisible( false ); + return; + } + + m_gadget->setVisible( true ); + + if( m_gadgetDirty ) + { + m_gadgetDirty = false; + } + } + + bool CsVisualiseOrientTool::keyPress + ( + GafferUI::KeyEvent const& event + ) + { + if( ! activePlug()->getValue() ) + { + return false; + } + + // allow user to scale vectors with +/- keys + + if( event.key == "Plus" || event.key == "Equal" ) + { + scalePlug()->setValue( scalePlug()->getValue() + g_scaleInc ); + } + else + if( event.key == "Minus" || event.key == "Underscore" ) + { + scalePlug()->setValue( std::max( scalePlug()->getValue() - g_scaleInc, g_scaleMin ) ); + } + + return false; + } + + CsVisualiseOrientTool::Selection::Selection + ( + GafferScene::ScenePlug const& scene, + GafferScene::ScenePlug::ScenePath const& path, + Gaffer::Context const& context + ) + : m_scene( & scene ) + , m_path( path ) + , m_context( & context ) + {} + + GafferScene::ScenePlug const& CsVisualiseOrientTool::Selection::scene() const + { + return *( m_scene ); + } + + GafferScene::ScenePlug::ScenePath const& CsVisualiseOrientTool::Selection::path() const + { + return m_path; + } + + Gaffer::Context const& CsVisualiseOrientTool::Selection::context() const + { + return *( m_context ); + } + +} // CSGafferUI diff --git a/contrib/visualisers/CsVisualiseOrientTool.h b/contrib/visualisers/CsVisualiseOrientTool.h new file mode 100644 index 00000000000..5a14798a909 --- /dev/null +++ b/contrib/visualisers/CsVisualiseOrientTool.h @@ -0,0 +1,186 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2024, Cinesite VFX Ltd. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above +// copyright notice, this list of conditions and the following +// disclaimer. +// +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided with +// the distribution. +// +// * Neither the name of John Haddon nor the names of +// any other contributors to this software may be used to endorse or +// promote products derived from this software without specific prior +// written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +////////////////////////////////////////////////////////////////////////// + +#ifndef CSGAFFERUI_TOOLS_CSVISUALISEORIENTTOOL_H +#define CSGAFFERUI_TOOLS_CSVISUALISEORIENTTOOL_H + +#include "../../GafferTypeIds.h" + +#include +#include +#include +#include +#include +#include + +namespace CSGafferUI +{ + /** + * @brief Tool that displays a named primitive variable of type Imath::Quatf. + */ + struct CsVisualiseOrientTool + : public GafferSceneUI::SelectionTool + { + /** + * @brief ctor + * @param view parent view + * @param name name for node + */ + explicit + CsVisualiseOrientTool + ( + GafferSceneUI::SceneView* view, + std::string const& name = Gaffer::GraphComponent::defaultName< CsVisualiseOrientTool >() + ); + + /** + * @brief dtor + */ + ~CsVisualiseOrientTool() override; + + /** + * @name GafferPlugAccessors + * @brief Gaffer plug accessor functions + * @{ + */ + + Gaffer::StringPlug* namePlug(); + Gaffer::StringPlug const* namePlug() const; + + Gaffer::FloatPlug* scalePlug(); + Gaffer::FloatPlug const* scalePlug() const; + + Gaffer::Color3fPlug* colourXPlug(); + Gaffer::Color3fPlug const* colourXPlug() const; + + Gaffer::Color3fPlug* colourYPlug(); + Gaffer::Color3fPlug const* colourYPlug() const; + + Gaffer::Color3fPlug* colourZPlug(); + Gaffer::Color3fPlug const* colourZPlug() const; + + /** + * @} + */ + + GAFFER_NODE_DECLARE_TYPE( + CSGafferUI::CsVisualiseOrientTool, + CSInternalTypes::CsVisualiseOrientToolTypeId, + GafferSceneUI::SelectionTool ); + + /** + * @brief Class encapsulating a selected scene location + */ + struct Selection + { + /** + * @brief ctor + * @param scene scene + * @param path scene path + * @param context context + */ + Selection + ( + GafferScene::ScenePlug const& scene, + GafferScene::ScenePlug::ScenePath const& path, + Gaffer::Context const& context + ); + + /** + * @brief Get the scene + * @return scene + */ + GafferScene::ScenePlug const& scene() const; + + /** + * @brief Get the scene path + * @return scene path + */ + GafferScene::ScenePlug::ScenePath const& path() const; + + /** + * @brief Get the context + * @return context + */ + Gaffer::Context const& context() const; + + private: + + GafferScene::ConstScenePlugPtr m_scene; + GafferScene::ScenePlug::ScenePath m_path; + Gaffer::ConstContextPtr m_context; + }; + + /** + * @brief Get the current selection + * @return current selection + */ + std::vector< Selection > const& selection() const; + + private: + + GafferScene::ScenePlug* internalScenePlug(); + GafferScene::ScenePlug const* internalScenePlug() const; + + void plugDirtied( Gaffer::Plug const* plug ); + void metadataChanged( IECore::InternedString const& key ); + void updateSelection() const; + void preRender(); + bool keyPress( GafferUI::KeyEvent const& event ); + +# if GAFFER_COMPATIBILITY_VERSION >= MAKE_GAFFER_COMPATIBILITY_VERSION( 1, 5 ) + void contextChanged(); + void selectedPathsChanged(); +# else + void connectToViewContext(); + void contextChanged( IECore::InternedString const& name ); + Gaffer::Signals::ScopedConnection m_contextChangedConnection; +# endif + Gaffer::Signals::ScopedConnection m_preRenderConnection; + + GafferUI::GadgetPtr m_gadget; + mutable std::vector< Selection > m_selection; + bool m_gadgetDirty; + mutable bool m_selectionDirty; + bool m_priorityPathsDirty; + + static ToolDescription< CsVisualiseOrientTool, GafferSceneUI::SceneView > m_toolDescription; + static size_t m_firstPlugIndex; + }; + +} // CSGafferUI + +#endif // CSGAFFERUI_TOOLS_CSVISUALISEORIENTTOOL_H diff --git a/contrib/visualisers/CsVisualiseValueTool.cpp b/contrib/visualisers/CsVisualiseValueTool.cpp new file mode 100644 index 00000000000..5a2fdd02759 --- /dev/null +++ b/contrib/visualisers/CsVisualiseValueTool.cpp @@ -0,0 +1,1573 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2024, Cinesite VFX Ltd. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above +// copyright notice, this list of conditions and the following +// disclaimer. +// +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided with +// the distribution. +// +// * Neither the name of John Haddon nor the names of +// any other contributors to this software may be used to endorse or +// promote products derived from this software without specific prior +// written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +IECORE_PUSH_DEFAULT_VISIBILITY +#if GAFFER_COMPATIBILITY_VERSION < MAKE_GAFFER_COMPATIBILITY_VERSION( 1, 3 ) +#include +#else +#include +#endif +IECORE_POP_DEFAULT_VISIBILITY + +#define private public +#include +#include +#undef private + +#include "CsVisualiseValueTool.h" + +#include +#include +#include +#include +#include +#include +#include +#if GAFFER_COMPATIBILITY_VERSION >= MAKE_GAFFER_COMPATIBILITY_VERSION( 1, 5 ) +#include +#else +#include +#endif + +#include +#include +#include +#include + +//#include +//#include +#include +#include +//#include +#include +#if ( GAFFER_MILESTONE_VERSION == 0 && GAFFER_MAJOR_VERSION < 61 ) +#include +#endif + +#if GAFFER_COMPATIBILITY_VERSION < MAKE_GAFFER_COMPATIBILITY_VERSION( 1, 3 ) +#include +#else +#include +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace +{ + // text and size constants + + float const g_textSizeDefault = 9.0f; + float const g_textSizeMin = 6.0f; + float const g_textSizeInc = 0.5f; + + Imath::Color3f g_colourDefault( 1.f, 1.f, 1.f ); + + // opacity and value constants + + float const g_opacityDefault = 0.5f; + float const g_opacityMin = 0.0f; + float const g_opacityMax = 1.0f; + + Imath::V3f const g_valueMinDefault( 0.f ); + Imath::V3f const g_valueMaxDefault( 1.f ); + + // convert three component colour to four component colour with full opacity + + Imath::Color4f convertToColor4f + ( + Imath::Color3f const& c + ) + { + return Imath::Color4f( c[ 0 ], c[ 1 ], c[ 2 ], 1.f ); + } + + // name of P primitive variable + + std::string const g_pName( "P" ); + + // uniform block structure (std140 layout) + + struct UniformBlock + { + alignas( 16 ) Imath::M44f o2c; + alignas( 16 ) Imath::V3f valueMin; + alignas( 16 ) Imath::V3f valueRange; + alignas( 4 ) float opacity; + }; + + GLuint const g_uniformBlockBindingIndex = 0; + +# define UNIFORM_BLOCK_GLSL_SOURCE \ + "layout( std140, row_major ) uniform UniformBlock\n" \ + "{\n" \ + " mat4 o2c;\n" \ + " vec3 valueMin;\n" \ + " vec3 valueRange;\n" \ + " float opacity;\n" \ + "} uniforms;\n" + +# define ATTRIB_GLSL_LOCATION_PS 0 +# define ATTRIB_GLSL_LOCATION_VSX 1 +# define ATTRIB_GLSL_LOCATION_VSY 2 +# define ATTRIB_GLSL_LOCATION_VSZ 3 + +# define ATTRIB_GLSL_SOURCE \ + "layout( location = " BOOST_PP_STRINGIZE( ATTRIB_GLSL_LOCATION_PS ) " ) in vec3 ps;\n" \ + "layout( location = " BOOST_PP_STRINGIZE( ATTRIB_GLSL_LOCATION_VSX ) " ) in float vsx;\n" \ + "layout( location = " BOOST_PP_STRINGIZE( ATTRIB_GLSL_LOCATION_VSY ) " ) in float vsy;\n" \ + "layout( location = " BOOST_PP_STRINGIZE( ATTRIB_GLSL_LOCATION_VSZ ) " ) in float vsz;\n" \ + +# define INTERFACE_BLOCK_GLSL_SOURCE( STORAGE, NAME ) \ + BOOST_PP_STRINGIZE( STORAGE ) " InterfaceBlock\n" \ + "{\n" \ + " smooth vec3 value;\n" \ + "} " BOOST_PP_STRINGIZE( NAME ) ";\n" + + // opengl vertex shader code + + std::string const g_vertSource + ( + "#version 330\n" + + UNIFORM_BLOCK_GLSL_SOURCE + + ATTRIB_GLSL_SOURCE + + INTERFACE_BLOCK_GLSL_SOURCE( out, outputs ) + + "void main()\n" + "{\n" + " outputs.value = clamp( ( vec3( vsx, vsy, vsz ) - uniforms.valueMin )\n" + " * uniforms.valueRange, 0.0, 1.0 );\n" + " gl_Position = vec4( ps, 1.0 ) * uniforms.o2c;\n" + "}\n" + ); + + // opengl fragment shader code + + std::string const g_fragSource + ( + "#version 330\n" + + UNIFORM_BLOCK_GLSL_SOURCE + + INTERFACE_BLOCK_GLSL_SOURCE( in, inputs ) + + "layout( location = 0 ) out vec4 cs;\n" + + "void main()\n" + "{\n" + " cs = vec4( inputs.value, uniforms.opacity );\n" + "}\n" + ); + + // the gadget that does the actual opengl drawing of the shaded primitive + + struct Gadget + : public GafferUI::Gadget + { + explicit + Gadget + ( + CSGafferUI::CsVisualiseValueTool const& tool, + std::string const& name = "CsVisualiseValueGadget" + ) + : GafferUI::Gadget( name ) + , m_tool( & tool ) + , m_shader() + , m_uniformBuffer() + {} + + void resetTool() + { + m_tool = nullptr; + } + + protected: + + void +# if ( GAFFER_MILESTONE_VERSION > 0 || GAFFER_MAJOR_VERSION >= 61 ) + renderLayer +# else + doRenderLayer +# endif + ( + GafferUI::Gadget::Layer layer, + GafferUI::Style const* style +# if ( GAFFER_MILESTONE_VERSION > 0 || GAFFER_MAJOR_VERSION >= 61 ) + , GafferUI::Gadget::RenderReason reason +# endif + ) + const override + { + if( ( layer != GafferUI::Gadget::Layer::MidFront ) || +# if ( GAFFER_MILESTONE_VERSION > 0 || GAFFER_MAJOR_VERSION >= 61 ) + ( GafferUI::Gadget::isSelectionRender( reason ) ) ) +# else + ( IECoreGL::Selector::currentSelector() ) ) +# endif + { + return; + } + + // check tool reference valid + + if( m_tool == nullptr ) + { + return; + } + + // get parent viewport gadget + + GafferUI::ViewportGadget const* const viewportGadget = + ancestor< GafferUI::ViewportGadget >(); + if( viewportGadget == nullptr ) + { + return; + } + + // bootleg shader + + buildShader(); + + if( ! m_shader ) + { + return; + } + + // get the cached converter from IECoreGL, this is used to convert primitive + // variable data to opengl buffers which will be shared with the IECoreGL renderer + + IECoreGL::CachedConverter* const converter = + IECoreGL::CachedConverter::defaultCachedConverter(); + + // bootleg uniform buffer + + GLint uniformBinding; + glGetIntegerv( GL_UNIFORM_BUFFER_BINDING, & uniformBinding ); + + if( ! m_uniformBuffer ) + { + GLuint buffer = 0u; + glGenBuffers( 1, & buffer ); + glBindBuffer( GL_UNIFORM_BUFFER, buffer ); + glBufferData( GL_UNIFORM_BUFFER, sizeof( UniformBlock ), 0, GL_DYNAMIC_DRAW ); + m_uniformBuffer.reset( new IECoreGL::Buffer( buffer ) ); + } + + glBindBufferBase( GL_UNIFORM_BUFFER, g_uniformBlockBindingIndex, m_uniformBuffer->m_buffer ); + + // get the name of the primitive variable to visualise + + std::string const& name = m_tool->namePlug()->getValue(); + + // get min/max values and colours and opacity + + UniformBlock uniforms; + Imath::V3f const valueMin = m_tool->valueMinPlug()->getValue(); + Imath::V3f const valueMax = m_tool->valueMaxPlug()->getValue(); + uniforms.opacity = m_tool->opacityPlug()->getValue(); + + // compute value range reciprocal + // + // NOTE : when range is <= 0 set the reciprocal to 0 so that value becomes 0 (minimum) + + Imath::V3f valueRange = ( valueMax - valueMin ); + for( int i = 0; i < 3; ++i ) + { + valueRange[ i ] = ( valueRange[ i ] > 0.f ) + ? ( 1.f / valueRange[ i ] ) : 0.f; + } + + // get the world to clip space matrix + + Imath::M44f v2c; + glGetFloatv( GL_PROJECTION_MATRIX, v2c.getValue() ); + Imath::M44f const w2c = viewportGadget->getCameraTransform().gjInverse() * v2c; + + // set opengl polygon and blend state + // + // NOTE : use polygon offset to ensure that any discrepancies between the transform + // from object to clip space do not cause z-fighting. This is necessary as + // the shader uses an object to clip matrix which may give slighly different + // depth results to the transformation used in the IECoreGL renderer. + + GLint blendEqRgb, blendEqAlpha; + glGetIntegerv( GL_BLEND_EQUATION_RGB, & blendEqRgb ); + glGetIntegerv( GL_BLEND_EQUATION_ALPHA, & blendEqAlpha ); + glBlendEquation( GL_FUNC_ADD ); + + GLint blendSrcRgb, blendSrcAlpha, blendDstRgb, blendDstAlpha; + glGetIntegerv( GL_BLEND_SRC_RGB, & blendSrcRgb ); + glGetIntegerv( GL_BLEND_SRC_ALPHA, & blendSrcAlpha ); + glGetIntegerv( GL_BLEND_DST_RGB, & blendDstRgb ); + glGetIntegerv( GL_BLEND_DST_ALPHA, & blendDstAlpha ); + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + + GLboolean const depthEnabled = glIsEnabled( GL_DEPTH_TEST ); + if( ! depthEnabled ) glEnable( GL_DEPTH_TEST ); + + GLint depthFunc; + glGetIntegerv( GL_DEPTH_FUNC, & depthFunc ); + glDepthFunc( GL_LEQUAL ); + + GLboolean depthWriteEnabled; + glGetBooleanv( GL_DEPTH_WRITEMASK, & depthWriteEnabled ); + if( depthWriteEnabled ) glDepthMask( GL_FALSE ); + + GLboolean const blendEnabled = glIsEnabled( GL_BLEND ); + if( ! blendEnabled ) glEnable( GL_BLEND ); + + GLint polygonMode; + glGetIntegerv( GL_POLYGON_MODE, & polygonMode ); + glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + + GLboolean const cullFaceEnabled = glIsEnabled( GL_CULL_FACE ); + if( cullFaceEnabled ) glDisable( GL_CULL_FACE ); + + GLboolean const polgonOffsetFillEnabled = glIsEnabled( GL_POLYGON_OFFSET_FILL ); + if( ! polgonOffsetFillEnabled ) glEnable( GL_POLYGON_OFFSET_FILL ); + + GLfloat polygonOffsetFactor, polygonOffsetUnits; + glGetFloatv( GL_POLYGON_OFFSET_FACTOR, & polygonOffsetFactor ); + glGetFloatv( GL_POLYGON_OFFSET_UNITS, & polygonOffsetUnits ); + glPolygonOffset( -1, -1 ); + + // enable shader program + + GLint shaderProgram; + glGetIntegerv( GL_CURRENT_PROGRAM, & shaderProgram ); + glUseProgram( m_shader->program() ); + + // set opengl vertex attribute array state + + GLint arrayBinding; + glGetIntegerv( GL_ARRAY_BUFFER_BINDING, & arrayBinding ); + + glPushClientAttrib( GL_CLIENT_VERTEX_ARRAY_BIT ); + + glVertexAttribDivisor( ATTRIB_GLSL_LOCATION_PS, 0 ); + glEnableVertexAttribArray( ATTRIB_GLSL_LOCATION_PS ); + glVertexAttribDivisor( ATTRIB_GLSL_LOCATION_VSX, 0 ); + glEnableVertexAttribArray( ATTRIB_GLSL_LOCATION_VSX ); + glVertexAttribDivisor( ATTRIB_GLSL_LOCATION_VSY, 0 ); + glEnableVertexAttribArray( ATTRIB_GLSL_LOCATION_VSY ); + glVertexAttribDivisor( ATTRIB_GLSL_LOCATION_VSZ, 0 ); + + // loop through current selection + + for( std::vector< CSGafferUI::CsVisualiseValueTool::Selection >::const_iterator + it = m_tool->selection().begin(), + itEnd = m_tool->selection().end(); it != itEnd; ++it ) + { + GafferScene::ScenePlug::PathScope scope( &( ( *it ).context() ), &( ( *it ).path() ) ); + + // check path exists + + if( !( ( *it ).scene().existsPlug()->getValue() ) ) + { + continue; + } + + // extract mesh primitive + + IECoreScene::ConstMeshPrimitivePtr const mesh = + IECore::runTimeCast< IECoreScene::MeshPrimitive const >( + ( *it ).scene().objectPlug()->getValue() ); + + if( ! mesh ) + { + continue; + } + + // retrieve cached IECoreGL mesh primitive + + IECoreGL::ConstPrimitivePtr const meshGL = + IECore::runTimeCast< IECoreGL::MeshPrimitive const >( + converter->convert( mesh.get() ) ); + + if( ! meshGL ) + { + continue; + } + + // find "P" vertex attribute + + IECoreGL::Primitive::AttributeMap::const_iterator const pit = + meshGL->m_vertexAttributes.find( g_pName ); + if( pit == meshGL->m_vertexAttributes.end() ) + { + continue; + } + + IECore::ConstV3fVectorDataPtr const pData = + IECore::runTimeCast< IECore::V3fVectorData const >( ( *pit ).second ); + if( ! pData ) + { + continue; + } + + // find named vertex attribute (FloatVectorData, V2fVectorData or V3fVectorData) + // + // NOTE : conversion to IECoreGL mesh may generate vertex attributes (eg. "N") + // so check named primitive variable exists on IECore mesh primitive. + + IECoreGL::Primitive::AttributeMap::const_iterator const vit = + meshGL->m_vertexAttributes.find( name ); + if( ( vit == meshGL->m_vertexAttributes.end() ) || !( ( *vit ).second ) || + ( mesh->variables.find( name ) == mesh->variables.end() ) ) + { + continue; + } + + IECore::ConstDataPtr const vData = ( *vit ).second; + GLsizei stride = 0; + GLenum type = GL_FLOAT; + bool offset = false; + bool enableVSZ = false; + switch( vData->typeId() ) + { + case IECore::IntVectorDataTypeId: + type = GL_INT; + case IECore::FloatVectorDataTypeId: + enableVSZ = true; + uniforms.valueMin = Imath::V3f( valueMin.x ); + uniforms.valueRange = Imath::V3f( valueRange.x ); + break; + case IECore::V2fVectorDataTypeId: + stride = 2; + offset = true; + uniforms.valueMin = Imath::V3f( valueMin.x, valueMin.y, 0.f ); + uniforms.valueRange = Imath::V3f( valueRange.x, valueRange.y, 0.f ); + break; + case IECore::V3fVectorDataTypeId: + stride = 3; + offset = true; + enableVSZ = true; + uniforms.valueMin = valueMin; + uniforms.valueRange = valueRange; + break; + default: + continue; + } + + // retrieve cached opengl buffer data + + IECoreGL::ConstBufferPtr const pBuffer = + IECore::runTimeCast< IECoreGL::Buffer const >( converter->convert( pData.get() ) ); + IECoreGL::ConstBufferPtr const vBuffer = + IECore::runTimeCast< IECoreGL::Buffer const >( converter->convert( vData.get() ) ); + + // get the object to world transform + + Imath::M44f o2w; + GafferScene::ScenePlug::ScenePath path( ( *it ).path() ); + while( ! path.empty() ) + { + scope.setPath( & path ); + o2w = o2w * ( *it ).scene().transformPlug()->getValue(); + path.pop_back(); + } + + // compute object to clip matrix + + uniforms.o2c = o2w * w2c; + + // upload opengl uniform block data + + glBufferData( GL_UNIFORM_BUFFER, sizeof( UniformBlock ), & uniforms, GL_DYNAMIC_DRAW ); + + // draw primitive + + glBindBuffer( GL_ARRAY_BUFFER, pBuffer->m_buffer ); + glVertexAttribPointer( ATTRIB_GLSL_LOCATION_PS, 3, GL_FLOAT, GL_FALSE, + 0, ( void const* )( 0 ) ); + glBindBuffer( GL_ARRAY_BUFFER, vBuffer->m_buffer ); + glVertexAttribPointer( ATTRIB_GLSL_LOCATION_VSX, 1, type, GL_FALSE, + stride * sizeof( GLfloat ), ( void const* )( 0 ) ); + glVertexAttribPointer( ATTRIB_GLSL_LOCATION_VSY, 1, type, GL_FALSE, + stride * sizeof( GLfloat ), ( void const* )( ( offset ? 1 : 0 ) * sizeof( GLfloat ) ) ); + if( enableVSZ ) + { + glEnableVertexAttribArray( ATTRIB_GLSL_LOCATION_VSZ ); + glVertexAttribPointer( ATTRIB_GLSL_LOCATION_VSZ, 1, type, GL_FALSE, + stride * sizeof( GLfloat ), ( void const* )( ( offset ? 2 : 0 ) * sizeof( GLfloat ) ) ); + } + else + { + glDisableVertexAttribArray( ATTRIB_GLSL_LOCATION_VSZ ); + glVertexAttrib1f( ATTRIB_GLSL_LOCATION_VSZ, 0.f ); + } + + meshGL->renderInstances( 1 ); + } + + // restore opengl state + + glPopClientAttrib(); + glBindBuffer( GL_ARRAY_BUFFER, arrayBinding ); + glBindBuffer( GL_UNIFORM_BUFFER, uniformBinding ); + + glDepthFunc( depthFunc ); + glBlendEquationSeparate( blendEqRgb, blendEqAlpha ); + glBlendFuncSeparate( blendSrcRgb, blendDstRgb, blendSrcAlpha, blendDstAlpha ); + glPolygonMode( GL_FRONT_AND_BACK, polygonMode ); + if( cullFaceEnabled ) glEnable( GL_CULL_FACE ); + if( ! polgonOffsetFillEnabled ) glDisable( GL_POLYGON_OFFSET_FILL ); + glPolygonOffset( polygonOffsetFactor, polygonOffsetUnits ); + + if( ! blendEnabled ) glDisable( GL_BLEND ); + if( ! depthEnabled ) glDisable( GL_DEPTH_TEST ); + if( depthWriteEnabled ) glDepthMask( GL_TRUE ); + glUseProgram( shaderProgram ); + + // display value at cursor as text + + IECore::Data const* const value = m_tool->cursorValue(); + if( value ) + { + std::ostringstream oss; + switch( value->typeId() ) + { + case IECore::IntDataTypeId: + oss << ( IECore::assertedStaticCast< IECore::IntData const >( value )->readable() ); + break; + case IECore::FloatDataTypeId: + oss << ( IECore::assertedStaticCast< IECore::FloatData const >( value )->readable() ); + break; + case IECore::V2fDataTypeId: + oss << ( IECore::assertedStaticCast< IECore::V2fData const >( value )->readable() ); + break; + case IECore::V3fDataTypeId: + oss << ( IECore::assertedStaticCast< IECore::V3fData const >( value )->readable() ); + break; + default: + break; + } + + std::string const text = oss.str(); + if( ! text.empty() ) + { + // draw in raster space + // + // NOTE : It seems that Gaffer defines the origin of raster space as the top left corner + // of the viewport, however the style text drawing functions assume that y increases + // "up" the screen rather than "down", so invert y to ensure text is not upside down. + + GafferUI::ViewportGadget::RasterScope raster( viewportGadget ); + float const size = m_tool->sizePlug()->getValue(); + Imath::V3f const scale( size, -size, 1.f ); + Imath::Color4f const colour = convertToColor4f( m_tool->colourPlug()->getValue() ); + Imath::V2f const& rp = m_tool->cursorPos(); + + glPushMatrix(); + glTranslatef( rp.x, rp.y, 0.f ); + glScalef( scale.x, scale.y, scale.z ); + style->renderText( GafferUI::Style::LabelText, text, GafferUI::Style::NormalState, & colour ); + glPopMatrix(); + } + } + } + +# if ( GAFFER_MILESTONE_VERSION > 0 || GAFFER_MAJOR_VERSION >= 61 ) + Imath::Box3f renderBound() const override + { + // NOTE : for now just return an infinite box + + Imath::Box3f b; + b.makeInfinite(); + return b; + } +# endif + +# if ( GAFFER_MILESTONE_VERSION > 0 || GAFFER_MAJOR_VERSION >= 61 ) + unsigned layerMask() const override + { + return ( m_tool ) + ? static_cast< unsigned >( GafferUI::Gadget::Layer::MidFront ) + : static_cast< unsigned >( 0 ); + } +# else + bool hasLayer( GafferUI::Gadget::Layer layer ) + { + return ( m_tool && + ( layer == GafferUI::Gadget::Layer::MidFront ) ); + } +# endif + + private: + + void buildShader() const + { + if( ! m_shader ) + { + m_shader = IECoreGL::ShaderLoader::defaultShaderLoader()->create( + g_vertSource, std::string(), g_fragSource ); + if( m_shader ) + { + GLuint const program = m_shader->program(); + GLuint const blockIndex = glGetUniformBlockIndex( program, "UniformBlock" ); + if( blockIndex != GL_INVALID_INDEX ) + { + glUniformBlockBinding( program, blockIndex, g_uniformBlockBindingIndex ); + } + } + } + } + + CSGafferUI::CsVisualiseValueTool const* m_tool; + mutable IECoreGL::ConstShaderPtr m_shader; + mutable IECoreGL::ConstBufferPtr m_uniformBuffer; + }; + + // cache for mesh evaluators + + struct EvaluationData + { + IECoreScene::ConstMeshPrimitivePtr triMesh; + IECoreScene::ConstMeshPrimitiveEvaluatorPtr evaluator; + }; + + IECore::LRUCache< + IECoreScene::ConstMeshPrimitivePtr, + EvaluationData > g_evaluatorCache( [] + ( + IECoreScene::ConstMeshPrimitivePtr const mesh, + size_t& cost + ) -> EvaluationData + { + cost = 1; + EvaluationData data; + data.triMesh = mesh->copy(); + data.triMesh = IECoreScene::MeshAlgo::triangulate( data.triMesh.get() ); + data.evaluator = new IECoreScene::MeshPrimitiveEvaluator( data.triMesh ); + return data; + }, 10 ); + +} // namespace + +namespace CSGafferUI +{ + GAFFER_NODE_DEFINE_TYPE( CsVisualiseValueTool ) + + GafferUI::Tool::ToolDescription< CsVisualiseValueTool, GafferSceneUI::SceneView > CsVisualiseValueTool::m_toolDescription; + + size_t CsVisualiseValueTool::m_firstPlugIndex = 0; + + CsVisualiseValueTool::CsVisualiseValueTool + ( + GafferSceneUI::SceneView* const view, + std::string const& name + ) + : GafferSceneUI::SelectionTool( view, name ) +# if GAFFER_COMPATIBILITY_VERSION < MAKE_GAFFER_COMPATIBILITY_VERSION( 1, 5 ) + , m_contextChangedConnection() +# endif + , m_preRenderConnection() + , m_buttonPressConnection() + , m_dragBeginConnection() + , m_gadget( new Gadget( *this ) ) + , m_selection() + , m_cursorPos( -1, -1 ) + , m_cursorPosValid( false ) + , m_cursorValue() + , m_gadgetDirty( true ) + , m_selectionDirty( true ) + , m_priorityPathsDirty( true ) + , m_acceptedButtonPress( false ) + , m_initiatedDrag( false ) + { + // add gadget to view and hide + + view->viewportGadget()->addChild( m_gadget ); + m_gadget->setVisible( false ); + + // store offset of first plug + + storeIndexOfNextChild( m_firstPlugIndex ); + + // add child plugs + + addChild( new Gaffer::StringPlug( "name", Gaffer::Plug::In, "uv" ) ); + addChild( new Gaffer::FloatPlug( "opacity", Gaffer::Plug::In, g_opacityDefault, g_opacityMin, g_opacityMax ) ); + addChild( new Gaffer::V3fPlug( "valueMin", Gaffer::Plug::In, g_valueMinDefault ) ); + addChild( new Gaffer::V3fPlug( "valueMax", Gaffer::Plug::In, g_valueMaxDefault ) ); + addChild( new Gaffer::FloatPlug( "size", Gaffer::Plug::In, g_textSizeDefault, g_textSizeMin ) ); + addChild( new Gaffer::Color3fPlug( "colour", Gaffer::Plug::In, g_colourDefault ) ); + addChild( new GafferScene::ScenePlug( "__scene", Gaffer::Plug::In ) ); + + // connect out internal scene plug to the parent view's scene plug + + internalScenePlug()->setInput( view->inPlug< GafferScene::ScenePlug >() ); + + // connect signal handlers + // + // NOTE : connecting to the viewport gadget means we will get called for all events + // which makes sense for key events, however we do not want to display value + // text when the mouse is over another gadget, (eg. Transform Tool handle) + // so instead connect to scene gadget signal. + // NOTE : There are other handlers that will attempt to consume button and drag + // events so connect handlers at the front of button/drag signal handler queues. + + view->viewportGadget()->keyPressSignal().connect( + boost::bind( & CsVisualiseValueTool::keyPress, this, boost::placeholders::_2 ) ); + + // NOTE : drag end and button release handlers remain whilst tool inactive in case tool + // is made inactive after button pressed or drag initiated in which case these + // handlers still need to tidy up state. + + sceneGadget()->buttonReleaseSignal(). +# if ( GAFFER_MILESTONE_VERSION > 0 || GAFFER_MAJOR_VERSION >= 62 ) + connectFront +# else + connect +# endif + ( boost::bind( & CsVisualiseValueTool::buttonRelease, this, boost::placeholders::_2 ) +# if ( GAFFER_MILESTONE_VERSION == 0 && GAFFER_MAJOR_VERSION < 62 ) + , boost::signals::at_front +# endif + ); + + sceneGadget()->dragEndSignal(). +# if ( GAFFER_MILESTONE_VERSION > 0 || GAFFER_MAJOR_VERSION >= 62 ) + connectFront +# else + connect +# endif + ( boost::bind( & CsVisualiseValueTool::dragEnd, this, boost::placeholders::_2 ) +# if ( GAFFER_MILESTONE_VERSION == 0 && GAFFER_MAJOR_VERSION < 62 ) + , boost::signals::at_front +# endif + ); + + // NOTE : mouse tracking handlers remain connected whilst tool inactive as they track the cursor + // line and whether its valid or not. This prevents the value display from "sticking" to + // edge of viewport when cursor leaves viewport's screen space. It also means that we do + // not have to work out the cursor line and whether its valid when tool is made active. + + sceneGadget()->enterSignal().connect( + boost::bind( & CsVisualiseValueTool::enter, this, boost::placeholders::_2 ) ); + sceneGadget()->leaveSignal().connect( + boost::bind( & CsVisualiseValueTool::leave, this, boost::placeholders::_2 ) ); + sceneGadget()->mouseMoveSignal().connect( + boost::bind( & CsVisualiseValueTool::mouseMove, this, boost::placeholders::_2 ) ); + + plugDirtiedSignal().connect( + boost::bind( & CsVisualiseValueTool::plugDirtied, this, boost::placeholders::_1 ) ); + plugSetSignal().connect( + boost::bind( & CsVisualiseValueTool::plugSet, this, boost::placeholders::_1 ) ); + +# if GAFFER_COMPATIBILITY_VERSION >= MAKE_GAFFER_COMPATIBILITY_VERSION( 1, 5 ) + view->contextChangedSignal().connect( + boost::bind( & CsVisualiseValueTool::contextChanged, this ) ); + GafferSceneUI::ScriptNodeAlgo::selectedPathsChangedSignal( view->scriptNode() ).connect( + boost::bind( &CsVisualiseValueTool::selectedPathsChanged, this ) ); +# else + connectToViewContext(); + view->contextChangedSignal().connect( + boost::bind( & CsVisualiseValueTool::connectToViewContext, this ) ); +# endif + + Gaffer::Metadata::plugValueChangedSignal().connect( + boost::bind( & CsVisualiseValueTool::metadataChanged, this, boost::placeholders::_3 ) ); + Gaffer::Metadata::nodeValueChangedSignal().connect( + boost::bind( & CsVisualiseValueTool::metadataChanged, this, boost::placeholders::_2 ) ); + } + + CsVisualiseValueTool::~CsVisualiseValueTool() + { + // NOTE : ensure that the gadget's reference to the tool is reset + + static_cast< Gadget* >( m_gadget.get() )->resetTool(); + } + + Gaffer::StringPlug* CsVisualiseValueTool::namePlug() + { + return const_cast< Gaffer::StringPlug* >( + static_cast< CsVisualiseValueTool const* >( this )->namePlug() ); + } + + Gaffer::StringPlug const* CsVisualiseValueTool::namePlug() const + { + return getChild< Gaffer::StringPlug >( m_firstPlugIndex + 0 ); + } + + Gaffer::FloatPlug* CsVisualiseValueTool::opacityPlug() + { + return const_cast< Gaffer::FloatPlug* >( + static_cast< CsVisualiseValueTool const* >( this )->opacityPlug() ); + } + + Gaffer::FloatPlug const* CsVisualiseValueTool::opacityPlug() const + { + return getChild< Gaffer::FloatPlug >( m_firstPlugIndex + 1 ); + } + + Gaffer::V3fPlug* CsVisualiseValueTool::valueMinPlug() + { + return const_cast< Gaffer::V3fPlug* >( + static_cast< CsVisualiseValueTool const* >( this )->valueMinPlug() ); + } + + Gaffer::V3fPlug const* CsVisualiseValueTool::valueMinPlug() const + { + return getChild< Gaffer::V3fPlug >( m_firstPlugIndex + 2 ); + } + + Gaffer::V3fPlug* CsVisualiseValueTool::valueMaxPlug() + { + return const_cast< Gaffer::V3fPlug* >( + static_cast< CsVisualiseValueTool const* >( this )->valueMaxPlug() ); + } + + Gaffer::V3fPlug const* CsVisualiseValueTool::valueMaxPlug() const + { + return getChild< Gaffer::V3fPlug >( m_firstPlugIndex + 3 ); + } + + Gaffer::FloatPlug* CsVisualiseValueTool::sizePlug() + { + return const_cast< Gaffer::FloatPlug* >( + static_cast< CsVisualiseValueTool const* >( this )->sizePlug() ); + } + + Gaffer::FloatPlug const* CsVisualiseValueTool::sizePlug() const + { + return getChild< Gaffer::FloatPlug >( m_firstPlugIndex + 4 ); + } + + Gaffer::Color3fPlug* CsVisualiseValueTool::colourPlug() + { + return const_cast< Gaffer::Color3fPlug* >( + static_cast< CsVisualiseValueTool const* >( this )->colourPlug() ); + } + + Gaffer::Color3fPlug const* CsVisualiseValueTool::colourPlug() const + { + return getChild< Gaffer::Color3fPlug >( m_firstPlugIndex + 5 ); + } + + GafferScene::ScenePlug* CsVisualiseValueTool::internalScenePlug() + { + return const_cast< GafferScene::ScenePlug* >( + static_cast< CsVisualiseValueTool const* >( this )->internalScenePlug() ); + } + + GafferScene::ScenePlug const* CsVisualiseValueTool::internalScenePlug() const + { + return getChild< GafferScene::ScenePlug >( m_firstPlugIndex + 6 ); + } + + std::vector< CsVisualiseValueTool::Selection > const& CsVisualiseValueTool::selection() const + { + return m_selection; + } + + Imath::V2f CsVisualiseValueTool::cursorPos() const + { + return m_cursorPos; + } + + IECore::Data const* CsVisualiseValueTool::cursorValue() const + { + return m_cursorValue.get(); + } + + void CsVisualiseValueTool::connectOnActive() + { + // NOTE : There are other handlers that will attempt to consume button and drag events + // so connect handlers at the front of button/drag signal handler queues. + + m_buttonPressConnection = sceneGadget()->buttonPressSignal(). +# if ( GAFFER_MILESTONE_VERSION > 0 || GAFFER_MAJOR_VERSION >= 62 ) + connectFront +# else + connect +# endif + ( boost::bind( & CsVisualiseValueTool::buttonPress, this, boost::placeholders::_2 ) +# if ( GAFFER_MILESTONE_VERSION == 0 && GAFFER_MAJOR_VERSION < 62 ) + , boost::signals::at_front +# endif + ); + m_dragBeginConnection = sceneGadget()->dragBeginSignal(). +# if ( GAFFER_MILESTONE_VERSION > 0 || GAFFER_MAJOR_VERSION >= 62 ) + connectFront +# else + connect +# endif + ( boost::bind( & CsVisualiseValueTool::dragBegin, this, boost::placeholders::_2 ) +# if ( GAFFER_MILESTONE_VERSION == 0 && GAFFER_MAJOR_VERSION < 62 ) + , boost::signals::at_front +# endif + ); + + m_preRenderConnection = view()->viewportGadget()->preRenderSignal().connect( + boost::bind( & CsVisualiseValueTool::preRender, this ) ); + + // NOTE : redraw necessary to ensure value display updated. + + view()->viewportGadget()->renderRequestSignal()( view()->viewportGadget() ); + } + + void CsVisualiseValueTool::disconnectOnInactive() + { + m_preRenderConnection.disconnect(); + m_buttonPressConnection.disconnect(); + m_dragBeginConnection.disconnect(); + } + +# if GAFFER_COMPATIBILITY_VERSION >= MAKE_GAFFER_COMPATIBILITY_VERSION( 1, 5 ) + void CsVisualiseValueTool::contextChanged() + { + // Context changes can change the scene, which in turn + // dirties our selection. + selectedPathsChanged(); + } + + void CsVisualiseValueTool::selectedPathsChanged() + { + m_selectionDirty = true; + m_gadgetDirty = true; + m_priorityPathsDirty = true; + } +# else + void CsVisualiseValueTool::connectToViewContext() + { + m_contextChangedConnection = view()->getContext()->changedSignal().connect( + boost::bind( & CsVisualiseValueTool::contextChanged, this, boost::placeholders::_2 ) ); + } + + void CsVisualiseValueTool::contextChanged + ( + IECore::InternedString const& name + ) + { + if( GafferSceneUI::ContextAlgo::affectsSelectedPaths( name ) || + GafferSceneUI::ContextAlgo::affectsLastSelectedPath( name ) || + ! boost::starts_with( name.string(), "ui:" ) ) + { + m_selectionDirty = true; + m_gadgetDirty = true; + m_priorityPathsDirty = true; + } + } +# endif + + bool CsVisualiseValueTool::mouseMove + ( + GafferUI::ButtonEvent const& event + ) + { + if( m_initiatedDrag ) + { + return false; + } + + updateCursorPos( event ); + m_cursorPosValid = true; + + // NOTE : only schedule redraw if tool active + + if( activePlug()->getValue() ) + { + view()->viewportGadget()->renderRequestSignal()( view()->viewportGadget() ); + } + + return false; + } + + void CsVisualiseValueTool::enter + ( + GafferUI::ButtonEvent const& event + ) + { + updateCursorPos( event ); + m_cursorPosValid = true; + + // NOTE : only schedule redraw if tool active + + if( activePlug()->getValue() ) + { + view()->viewportGadget()->renderRequestSignal()( view()->viewportGadget() ); + } + } + + void CsVisualiseValueTool::leave + ( + GafferUI::ButtonEvent const& event + ) + { + updateCursorPos( event ); + m_cursorPosValid = false; + + // NOTE : only schedule redraw if tool active + + if( activePlug()->getValue() ) + { + view()->viewportGadget()->renderRequestSignal()( view()->viewportGadget() ); + } + } + + bool CsVisualiseValueTool::keyPress + ( + GafferUI::KeyEvent const& event + ) + { + if( ! activePlug()->getValue() ) + { + return false; + } + + // allow user to scale text with +/- keys + + if( event.key == "Plus" || event.key == "Equal" ) + { + sizePlug()->setValue( sizePlug()->getValue() + g_textSizeInc ); + } + else + if( event.key == "Minus" || event.key == "Underscore" ) + { + sizePlug()->setValue( std::max( sizePlug()->getValue() - g_textSizeInc, g_textSizeMin ) ); + } + + return false; + } + + bool CsVisualiseValueTool::buttonPress + ( + GafferUI::ButtonEvent const& event + ) + { + m_acceptedButtonPress = false; + m_initiatedDrag = false; + + if( ( event.button & GafferUI::ButtonEvent::Left ) ) + { + updateCursorValue(); + if( m_cursorValue ) + { + m_acceptedButtonPress = true; + return true; + } + } + + return false; + } + + bool CsVisualiseValueTool::buttonRelease + ( + GafferUI::ButtonEvent const& event + ) + { + m_acceptedButtonPress = false; + m_initiatedDrag = false; + + return false; + } + + IECore::RunTimeTypedPtr + CsVisualiseValueTool::dragBegin + ( + GafferUI::DragDropEvent const& event + ) + { + m_initiatedDrag = false; + + if( ! m_acceptedButtonPress ) + { + return IECore::RunTimeTypedPtr(); + } + + m_acceptedButtonPress = false; + + if( m_cursorValue ) + { + // NOTE : There is a possibility that the tool has become inactive since the button + // press event that triggered the drag was accepted, the cutoff point is the + // button press event, so any change to the active state after that does not + // affect an ongoing drag operation. We therefore always request a redraw + // here so that the displayed value is cleared. + + m_initiatedDrag = true; + view()->viewportGadget()->renderRequestSignal()( view()->viewportGadget() ); + GafferUI::Pointer::setCurrent( "values" ); + } + + return m_cursorValue; + } + + bool CsVisualiseValueTool::dragEnd + ( + GafferUI::DragDropEvent const& event + ) + { + if( ! m_initiatedDrag ) + { + return false; + } + + m_initiatedDrag = false; + GafferUI::Pointer::setCurrent( "" ); + return true; + } + + void CsVisualiseValueTool::plugDirtied + ( + Gaffer::Plug const* const plug + ) + { + if( ( plug == activePlug() ) || + ( plug == internalScenePlug()->objectPlug() ) || + ( plug == internalScenePlug()->transformPlug() ) ) + { + m_selectionDirty = true; + m_gadgetDirty = true; + m_priorityPathsDirty = true; + } + else + if( ( plug == namePlug() ) || + ( plug == opacityPlug() ) || + ( plug == valueMinPlug() ) || + ( plug == valueMaxPlug() ) || + ( plug == sizePlug() ) || + ( plug == colourPlug() ) ) + { + m_gadgetDirty = true; + view()->viewportGadget()->renderRequestSignal()( view()->viewportGadget() ); + } + + if( plug == activePlug() ) + { + if( activePlug()->getValue() ) + { + connectOnActive(); + } + else + { + disconnectOnInactive(); + m_gadget->setVisible( false ); + + sceneGadget()->setPriorityPaths( IECore::PathMatcher() ); + } + } + } + + void CsVisualiseValueTool::plugSet( Gaffer::Plug* const plug ) + { + // ensure that the min value does not exceed the max and vice-versa + + if( plug == valueMinPlug() ) + { + Imath::V3f const valueMin = valueMinPlug()->getValue(); + Imath::V3f valueMax = valueMaxPlug()->getValue(); + + for( int i = 0; i < 3; ++i ) + { + valueMax[ i ] = std::max( valueMin[ i ], valueMax[ i ] ); + } + + valueMaxPlug()->setValue( valueMax ); + } + else + if( plug == valueMaxPlug() ) + { + Imath::V3f valueMin = valueMinPlug()->getValue(); + Imath::V3f const valueMax = valueMaxPlug()->getValue(); + + for( int i = 0; i < 3; ++i ) + { + valueMin[ i ] = std::min( valueMin[ i ], valueMax[ i ] ); + } + + valueMinPlug()->setValue( valueMin ); + } + } + + void CsVisualiseValueTool::metadataChanged + ( + IECore::InternedString const& key + ) + { + if( ! Gaffer::MetadataAlgo::readOnlyAffectedByChange( key ) ) + { + return; + } + + if( ! m_selectionDirty ) + { + m_selectionDirty = true; + } + + if( ! m_gadgetDirty ) + { + m_gadgetDirty = true; + view()->viewportGadget()->renderRequestSignal()( view()->viewportGadget() ); + } + } + + void CsVisualiseValueTool::updateSelection() const + { + if( ! m_selectionDirty ) + { + return; + } + + m_selection.clear(); + m_selectionDirty = false; + + if( ! activePlug()->getValue() ) + { + return; + } + + GafferScene::ScenePlug const* scene = + internalScenePlug()->getInput< GafferScene::ScenePlug >(); + + if( !( scene ) || + !( scene = scene->getInput< GafferScene::ScenePlug >() ) ) + { + return; + } + +# if GAFFER_COMPATIBILITY_VERSION >= MAKE_GAFFER_COMPATIBILITY_VERSION( 1, 5 ) + IECore::PathMatcher const selectedPaths = + GafferSceneUI::ScriptNodeAlgo::getSelectedPaths( view()->scriptNode() ); +# else + IECore::PathMatcher const selectedPaths = + GafferSceneUI::ContextAlgo::getSelectedPaths( view()->getContext() ); +# endif + + if( selectedPaths.isEmpty() ) + { + return; + } + + for( IECore::PathMatcher::Iterator it = selectedPaths.begin(), + itEnd = selectedPaths.end(); it != itEnd; ++it ) + { +# if GAFFER_COMPATIBILITY_VERSION >= MAKE_GAFFER_COMPATIBILITY_VERSION( 1, 5 ) + m_selection.emplace_back( *( scene ), *it, *( view()->context() ) ); +# else + m_selection.emplace_back( *( scene ), *it, *( view()->getContext() ) ); +# endif + + } + } + + void CsVisualiseValueTool::preRender() + { + updateSelection(); + + if( m_priorityPathsDirty ) + { +# if GAFFER_COMPATIBILITY_VERSION >= MAKE_GAFFER_COMPATIBILITY_VERSION( 1, 5 ) + sceneGadget()->setPriorityPaths( ( m_selection.empty() ) + ? IECore::PathMatcher() + : GafferSceneUI::ScriptNodeAlgo::getSelectedPaths( view()->scriptNode() ) ); +# else + sceneGadget()->setPriorityPaths( ( m_selection.empty() ) + ? IECore::PathMatcher() + : GafferSceneUI::ContextAlgo::getSelectedPaths( view()->getContext() ) ); +# endif + + m_priorityPathsDirty = false; + } + + if( m_selection.empty() ) + { + m_gadget->setVisible( false ); + return; + } + + m_gadget->setVisible( true ); + + if( m_gadgetDirty ) + { + m_gadgetDirty = false; + } + + updateCursorValue(); + } + + void CsVisualiseValueTool::updateCursorPos + ( + GafferUI::ButtonEvent const& event + ) + { + // update cursor raster position + // + // NOTE : the cursor position is stored in raster space so it is free of camera + // transformations so we do not need to track camera changes. + + assert( view() ); + assert( view()->viewportGadget() ); + + m_cursorPos = view()->viewportGadget()->gadgetToRasterSpace( event.line.p1, sceneGadget() ); + } + + void CsVisualiseValueTool::updateCursorValue() + { + IECore::DataPtr cursorValue = m_cursorValue; + m_cursorValue.reset(); + + // NOTE : during a drag do not update the cursor value + + if( m_initiatedDrag || ! m_cursorPosValid ) + { + return; + } + + // get scene gadget and viewport gadgets + + GafferSceneUI::SceneGadget* const sg = sceneGadget(); + if( ! sg || ! view() || !( view()->viewportGadget() ) ) + { + return; + } + + // clear any existing selection mask + + IECore::StringVectorData const* const selectionMask = sg->getSelectionMask(); + sg->setSelectionMask( nullptr ); + + // get the current object at cursor + + GafferScene::ScenePlug::ScenePath path; + + try + { + if( ! sg->objectAt( view()->viewportGadget()->rasterToGadgetSpace( m_cursorPos, sg ), path ) ) + { + return; + } + } + catch( IECore::Exception const& e ) + { + // NOTE : objectAt seems to write to the OpenGL color buffer so if there was an + // error the OpenGL color buffer will contain the remnants of the failed + // object id pass. If we are being called from preRender() the color buffer + // would normally be cleared after the preRender callback has finished so + // catch the exception and return. If we are being called from button press + // we don't want the exception to propagate so again catch and return. In + // both cases the error should happen again during the next render pass. + + return; + } + + // check current object is included in selection + + std::vector< Selection >::const_iterator const sit = + std::find_if( m_selection.begin(), m_selection.end(), + [ & path ]( Selection const& item ) -> bool + { + return item.path() == path; + } ); + if( sit == m_selection.end() ) + { + return; + } + + // check scene location exists + + Selection const& item = ( *sit ); + GafferScene::ScenePlug::PathScope scope( &( item.context() ), & path ); + if( !( item.scene().existsPlug()->getValue() ) ) + { + return; + } + + // extract mesh primitive object + + IECoreScene::ConstMeshPrimitivePtr const mesh = + IECore::runTimeCast< IECoreScene::MeshPrimitive const >( + item.scene().objectPlug()->getValue() ); + if( ! mesh ) + { + return; + } + + // check mesh has named primitive variable + + std::string const& name = namePlug()->getValue(); + IECoreScene::PrimitiveVariableMap::const_iterator const vit = mesh->variables.find( name ); + if( vit == mesh->variables.end() || !( ( *vit ).second.data ) ) + { + return; + } + + // check type of data + + switch( ( *vit ).second.data->typeId() ) + { + case IECore::IntVectorDataTypeId: + case IECore::FloatVectorDataTypeId: + case IECore::V2fVectorDataTypeId: + case IECore::V3fVectorDataTypeId: + break; + default: + return; + } + + // create a mesh primitive evaluator + // + // NOTE : In order to create an evaluator we need a triangulated mesh + // this processing is expensive so we cache the created evaluator in an LRU cache + + EvaluationData const evalData = g_evaluatorCache.get( mesh ); + IECoreScene::PrimitiveEvaluator::ResultPtr const result = evalData.evaluator->createResult(); + + // intersect line from cursor with mesh in object space using evaluator + + IECore::LineSegment3f const line = + view()->viewportGadget()->rasterToWorldSpace( cursorPos() ) * + item.scene().fullTransform( path ).gjInverse(); + if( ! evalData.evaluator->intersectionPoint( line.p0, line.direction(), result.get() ) ) + { + return; + } + + // update value from intersection result + + switch( ( *vit ).second.data->typeId() ) + { + case IECore::IntVectorDataTypeId: + { + IECore::IntDataPtr data = + IECore::runTimeCast< IECore::IntData >( cursorValue ); + if( ! data ) data.reset( new IECore::IntData() ); + data->writable() = result->intPrimVar( evalData.triMesh->variables.at( name ) ); + cursorValue = data; + break; + } + case IECore::FloatVectorDataTypeId: + { + IECore::FloatDataPtr data = + IECore::runTimeCast< IECore::FloatData >( cursorValue ); + if( ! data ) data.reset( new IECore::FloatData() ); + data->writable() = result->floatPrimVar( evalData.triMesh->variables.at( name ) ); + cursorValue = data; + break; + } + case IECore::V2fVectorDataTypeId: + { + IECore::V2fDataPtr data = + IECore::runTimeCast< IECore::V2fData >( cursorValue ); + if( ! data ) data.reset( new IECore::V2fData() ); + data->writable() = result->vec2PrimVar( evalData.triMesh->variables.at( name ) ); + cursorValue = data; + break; + } + case IECore::V3fVectorDataTypeId: + { + IECore::V3fDataPtr data = + IECore::runTimeCast< IECore::V3fData >( cursorValue ); + if( ! data ) data.reset( new IECore::V3fData() ); + data->writable() = result->vectorPrimVar( evalData.triMesh->variables.at( name ) ); + cursorValue = data; + break; + } + default: + return; + } + + m_cursorValue = cursorValue; + + // restore selection mask + + sg->setSelectionMask( selectionMask ); + } + + GafferSceneUI::SceneGadget* CsVisualiseValueTool::sceneGadget() + { + return const_cast< GafferSceneUI::SceneGadget* >( + static_cast< CsVisualiseValueTool const* >( this )->sceneGadget() ); + } + + GafferSceneUI::SceneGadget const* CsVisualiseValueTool::sceneGadget() const + { + return IECore::runTimeCast< GafferSceneUI::SceneGadget const >( + view()->viewportGadget()->getPrimaryChild() ); + } + + CsVisualiseValueTool::Selection::Selection + ( + GafferScene::ScenePlug const& scene, + GafferScene::ScenePlug::ScenePath const& path, + Gaffer::Context const& context + ) + : m_scene( & scene ) + , m_path( path ) + , m_context( & context ) + {} + + GafferScene::ScenePlug const& CsVisualiseValueTool::Selection::scene() const + { + return *( m_scene ); + } + + GafferScene::ScenePlug::ScenePath const& CsVisualiseValueTool::Selection::path() const + { + return m_path; + } + + Gaffer::Context const& CsVisualiseValueTool::Selection::context() const + { + return *( m_context ); + } + +} // CSGafferUI diff --git a/contrib/visualisers/CsVisualiseValueTool.h b/contrib/visualisers/CsVisualiseValueTool.h new file mode 100644 index 00000000000..7dbbdd832a2 --- /dev/null +++ b/contrib/visualisers/CsVisualiseValueTool.h @@ -0,0 +1,226 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2024, Cinesite VFX Ltd. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above +// copyright notice, this list of conditions and the following +// disclaimer. +// +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided with +// the distribution. +// +// * Neither the name of John Haddon nor the names of +// any other contributors to this software may be used to endorse or +// promote products derived from this software without specific prior +// written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +////////////////////////////////////////////////////////////////////////// + +#ifndef CSGAFFERUI_TOOLS_CSVISUALISEVALUETOOL_H +#define CSGAFFERUI_TOOLS_CSVISUALISEVALUETOOL_H + +#include "../../GafferTypeIds.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace CSGafferUI +{ + /** + * @brief Tool that displays a named primitive variable of type float/V2f/V3f + * of a MeshPrimitive as a coloured overlay. + */ + struct CsVisualiseValueTool + : public GafferSceneUI::SelectionTool + { + /** + * @brief ctor + * @param view parent view + * @param name name for node + */ + explicit + CsVisualiseValueTool + ( + GafferSceneUI::SceneView* view, + std::string const& name = Gaffer::GraphComponent::defaultName< CsVisualiseValueTool >() + ); + + /** + * @brief dtor + */ + ~CsVisualiseValueTool() override; + + /** + * @name GafferPlugAccessors + * @brief Gaffer plug accessor functions + * @{ + */ + + Gaffer::StringPlug* namePlug(); + Gaffer::StringPlug const* namePlug() const; + + Gaffer::FloatPlug* opacityPlug(); + Gaffer::FloatPlug const* opacityPlug() const; + + Gaffer::V3fPlug* valueMinPlug(); + Gaffer::V3fPlug const* valueMinPlug() const; + + Gaffer::V3fPlug* valueMaxPlug(); + Gaffer::V3fPlug const* valueMaxPlug() const; + + Gaffer::FloatPlug* sizePlug(); + Gaffer::FloatPlug const* sizePlug() const; + + Gaffer::Color3fPlug* colourPlug(); + Gaffer::Color3fPlug const* colourPlug() const; + + /** + * @} + */ + + GAFFER_NODE_DECLARE_TYPE( + CSGafferUI::CsVisualiseValueTool, + CSInternalTypes::CsVisualiseValueToolTypeId, + GafferSceneUI::SelectionTool ); + + /** + * @brief Class encapsulating a selected scene location + */ + struct Selection + { + /** + * @brief ctor + * @param scene scene + * @param path scene path + * @param context context + */ + Selection + ( + GafferScene::ScenePlug const& scene, + GafferScene::ScenePlug::ScenePath const& path, + Gaffer::Context const& context + ); + + /** + * @brief Get the scene + * @return scene + */ + GafferScene::ScenePlug const& scene() const; + + /** + * @brief Get the scene path + * @return scene path + */ + GafferScene::ScenePlug::ScenePath const& path() const; + + /** + * @brief Get the context + * @return context + */ + Gaffer::Context const& context() const; + + private: + + GafferScene::ConstScenePlugPtr m_scene; + GafferScene::ScenePlug::ScenePath m_path; + Gaffer::ConstContextPtr m_context; + }; + + /** + * @brief Get the current selection + * @return current selection + */ + std::vector< Selection > const& selection() const; + + /** + * @brief Get the cursor position in raster space + * @return cursor position in raster space + */ + Imath::V2f cursorPos() const; + + /** + * @brief Get the value at current cursor position + * @return value at current cursor position (nullptr if invalid) + */ + IECore::Data const* cursorValue() const; + + private: + + GafferScene::ScenePlug* internalScenePlug(); + GafferScene::ScenePlug const* internalScenePlug() const; + + void connectOnActive(); + void disconnectOnInactive(); + bool mouseMove( GafferUI::ButtonEvent const& event ); + void enter( GafferUI::ButtonEvent const& event ); + void leave( GafferUI::ButtonEvent const& event ); + bool keyPress( GafferUI::KeyEvent const& event ); + bool buttonPress( GafferUI::ButtonEvent const& event ); + bool buttonRelease( GafferUI::ButtonEvent const& event ); + IECore::RunTimeTypedPtr dragBegin( GafferUI::DragDropEvent const& event ); + bool dragEnd( GafferUI::DragDropEvent const& event ); + void plugDirtied( Gaffer::Plug const* plug ); + void plugSet( Gaffer::Plug* plug ); + void metadataChanged( IECore::InternedString const& key ); + void updateSelection() const; + void preRender(); + void updateCursorPos( GafferUI::ButtonEvent const& event ); + void updateCursorValue(); + GafferSceneUI::SceneGadget* sceneGadget(); + GafferSceneUI::SceneGadget const* sceneGadget() const; + +# if GAFFER_COMPATIBILITY_VERSION >= MAKE_GAFFER_COMPATIBILITY_VERSION( 1, 5 ) + void contextChanged(); + void selectedPathsChanged(); +# else + void connectToViewContext(); + void contextChanged( IECore::InternedString const& name ); + Gaffer::Signals::ScopedConnection m_contextChangedConnection; +# endif + Gaffer::Signals::ScopedConnection m_preRenderConnection; + Gaffer::Signals::ScopedConnection m_buttonPressConnection; + Gaffer::Signals::ScopedConnection m_dragBeginConnection; + + GafferUI::GadgetPtr m_gadget; + mutable std::vector< Selection > m_selection; + Imath::V2i m_cursorPos; + bool m_cursorPosValid; + IECore::DataPtr m_cursorValue; + bool m_gadgetDirty; + mutable bool m_selectionDirty; + bool m_priorityPathsDirty; + bool m_acceptedButtonPress; + bool m_initiatedDrag; + + static ToolDescription< CsVisualiseValueTool, GafferSceneUI::SceneView > m_toolDescription; + static size_t m_firstPlugIndex; + }; + +} // CSGafferUI + +#endif // CSGAFFERUI_TOOLS_CSVISUALISEVALUETOOL_H diff --git a/contrib/visualisers/CsVisualiseVectorTool.cpp b/contrib/visualisers/CsVisualiseVectorTool.cpp new file mode 100644 index 00000000000..3d3094cafd2 --- /dev/null +++ b/contrib/visualisers/CsVisualiseVectorTool.cpp @@ -0,0 +1,973 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2024, Cinesite VFX Ltd. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above +// copyright notice, this list of conditions and the following +// disclaimer. +// +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided with +// the distribution. +// +// * Neither the name of John Haddon nor the names of +// any other contributors to this software may be used to endorse or +// promote products derived from this software without specific prior +// written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +IECORE_PUSH_DEFAULT_VISIBILITY +#if GAFFER_COMPATIBILITY_VERSION < MAKE_GAFFER_COMPATIBILITY_VERSION( 1, 3 ) +#include +#else +#include +#endif +IECORE_POP_DEFAULT_VISIBILITY + +#define private public +#include +#include +#undef private + +#include "CsVisualiseVectorTool.h" + +#include +#include +#include +#include +#include +#include +#if GAFFER_COMPATIBILITY_VERSION >= MAKE_GAFFER_COMPATIBILITY_VERSION( 1, 5 ) +#include +#else +#include +#endif + +#include +#include + +#include +#include +//#include +//#include +//#include +#include +#if ( GAFFER_MILESTONE_VERSION == 0 && GAFFER_MAJOR_VERSION < 61 ) +#include +#endif + +#if GAFFER_COMPATIBILITY_VERSION < MAKE_GAFFER_COMPATIBILITY_VERSION( 1, 3 ) +#include +#else +#include +#endif + +#include +#include +#include + +#include +#include +#include +#include + +namespace +{ + // scale and colour constants + + float const g_scaleDefault = 1.f; + float const g_scaleMin = 10.f * std::numeric_limits< float >::min(); + float const g_scaleInc = 0.01f; + + Imath::Color3f g_colourDefault( 1.f, 1.f, 1.f ); + + // name of P primitive variable + + std::string const g_pName( "P" ); + + // uniform block structure (std140 layout) + + struct UniformBlock + { + alignas( 16 ) Imath::M44f o2v; + alignas( 16 ) Imath::M44f n2v; + alignas( 16 ) Imath::M44f v2c; + alignas( 16 ) Imath::M44f o2c; + alignas( 16 ) Imath::Color3f colour; + alignas( 4 ) float scale; + }; + + GLuint const g_uniformBlockBindingIndex = 0; + +# define UNIFORM_BLOCK_GLSL_SOURCE \ + "layout( std140, row_major ) uniform UniformBlock\n" \ + "{\n" \ + " mat4 o2v;\n" \ + " mat4 n2v;\n" \ + " mat4 v2c;\n" \ + " mat4 o2c;\n" \ + " vec3 colour;\n" \ + " float scale;\n" \ + "} uniforms;\n" + +# define ATTRIB_GLSL_LOCATION_PS 0 +# define ATTRIB_GLSL_LOCATION_VS 1 + +# define ATTRIB_GLSL_SOURCE \ + "layout( location = " BOOST_PP_STRINGIZE( ATTRIB_GLSL_LOCATION_PS ) " ) in vec3 ps;\n" \ + "layout( location = " BOOST_PP_STRINGIZE( ATTRIB_GLSL_LOCATION_VS ) " ) in vec3 vs;\n" + + // opengl vertex shader code (point format) + + std::string const g_vertSourcePoint + ( + "#version 330\n" + + UNIFORM_BLOCK_GLSL_SOURCE + + ATTRIB_GLSL_SOURCE + + "void main()\n" + "{\n" + " vec3 position = ps;\n" + + " if( gl_VertexID == 1 )\n" + " {\n" + " position = vs;\n" + " }\n" + + " gl_Position = vec4( position, 1.0 ) * uniforms.o2c;\n" + "}\n" + ); + + // opengl vertex shader code (vector format) + + std::string const g_vertSourceVector + ( + "#version 330\n" + + UNIFORM_BLOCK_GLSL_SOURCE + + ATTRIB_GLSL_SOURCE + + "void main()\n" + "{\n" + " vec3 position = ps;\n" + + " if( gl_VertexID == 1 )\n" + " {\n" + " position += vs * uniforms.scale;" + " }\n" + + " gl_Position = vec4( position, 1.0 ) * uniforms.o2c;\n" + "}\n" + ); + + // opengl vertex shader code (bivector format) + + std::string const g_vertSourceBivector + ( + "#version 330\n" + + UNIFORM_BLOCK_GLSL_SOURCE + + ATTRIB_GLSL_SOURCE + + "void main()\n" + "{\n" + " vec4 position = vec4( ps, 1.0 ) * uniforms.o2v;\n" + + " if( gl_VertexID == 1 )\n" + " {\n" + " position.xyz += normalize( vs * mat3( uniforms.n2v ) ) * ( uniforms.scale * length( vs ) );\n" + " }\n" + + " gl_Position = position * uniforms.v2c;\n" + "}\n" + ); + + // opengl fragment shader code + + std::string const g_fragSource + ( + "#version 330\n" + + UNIFORM_BLOCK_GLSL_SOURCE + + "layout( location = 0 ) out vec4 cs;\n" + + "void main()\n" + "{\n" + " cs = vec4( uniforms.colour, 1.0 );\n" + "}\n" + ); + + // the gadget that does the actual opengl drawing of the vector lines + + struct Gadget + : public GafferUI::Gadget + { + explicit + Gadget + ( + CSGafferUI::CsVisualiseVectorTool const& tool, + std::string const& name = "CsVisualiseVectorGadget" + ) + : GafferUI::Gadget( name ) + , m_tool( & tool ) + , m_vectorShader() + , m_bivectorShader() + , m_uniformBuffer() + {} + + void resetTool() + { + m_tool = nullptr; + } + + protected: + + void +# if ( GAFFER_MILESTONE_VERSION > 0 || GAFFER_MAJOR_VERSION >= 61 ) + renderLayer +# else + doRenderLayer +# endif + ( + GafferUI::Gadget::Layer layer, + GafferUI::Style const* style +# if ( GAFFER_MILESTONE_VERSION > 0 || GAFFER_MAJOR_VERSION >= 61 ) + , GafferUI::Gadget::RenderReason reason +# endif + ) + const override + { + if( ( layer != GafferUI::Gadget::Layer::MidFront ) || +# if ( GAFFER_MILESTONE_VERSION > 0 || GAFFER_MAJOR_VERSION >= 61 ) + ( GafferUI::Gadget::isSelectionRender( reason ) ) ) +# else + ( IECoreGL::Selector::currentSelector() ) ) +# endif + { + return; + } + + // check tool reference valid + + if( m_tool == nullptr ) + { + return; + } + + // get parent viewport gadget + + GafferUI::ViewportGadget const* const viewportGadget = + ancestor< GafferUI::ViewportGadget >(); + if( viewportGadget == nullptr ) + { + return; + } + + // bootleg shaders + + buildShader( m_pointShader, g_vertSourcePoint ); + buildShader( m_vectorShader, g_vertSourceVector ); + buildShader( m_bivectorShader, g_vertSourceBivector ); + + // get the cached converter from IECoreGL, this is used to convert primitive + // variable data to opengl buffers which will be shared with the IECoreGL renderer + + IECoreGL::CachedConverter* const converter = + IECoreGL::CachedConverter::defaultCachedConverter(); + + // bootleg uniform buffer + + GLint uniformBinding; + glGetIntegerv( GL_UNIFORM_BUFFER_BINDING, & uniformBinding ); + + if( ! m_uniformBuffer ) + { + GLuint buffer = 0u; + glGenBuffers( 1, & buffer ); + glBindBuffer( GL_UNIFORM_BUFFER, buffer ); + glBufferData( GL_UNIFORM_BUFFER, sizeof( UniformBlock ), 0, GL_DYNAMIC_DRAW ); + m_uniformBuffer.reset( new IECoreGL::Buffer( buffer ) ); + } + + glBindBufferBase( GL_UNIFORM_BUFFER, g_uniformBlockBindingIndex, m_uniformBuffer->m_buffer ); + + // get the name of the primitive variable to visualise and format + + std::string const& name = m_tool->namePlug()->getValue(); + CSGafferUI::CsVisualiseVectorTool::Format const format = + static_cast< CSGafferUI::CsVisualiseVectorTool::Format >( + m_tool->formatPlug()->getValue() ); + + // get scale factor and colour + + UniformBlock uniforms; + uniforms.colour = m_tool->colourPlug()->getValue(); + uniforms.scale = m_tool->scalePlug()->getValue(); + + // get the world to view and view to clip space matrices + + Imath::M44f const w2v = viewportGadget->getCameraTransform().gjInverse(); + glGetFloatv( GL_PROJECTION_MATRIX, uniforms.v2c.getValue() ); + + // set opengl state + + GLfloat lineWidth; + glGetFloatv( GL_LINE_WIDTH, & lineWidth ); + glLineWidth( 1.f ); + + GLboolean const depthEnabled = glIsEnabled( GL_DEPTH_TEST ); + if( ! depthEnabled ) glEnable( GL_DEPTH_TEST ); + + GLboolean depthWriteEnabled; + glGetBooleanv( GL_DEPTH_WRITEMASK, & depthWriteEnabled ); + if( depthWriteEnabled ) glDepthMask( GL_FALSE ); + + GLboolean lineSmooth; + glGetBooleanv( GL_LINE_SMOOTH, & lineSmooth ); + if( lineSmooth ) glDisable( GL_LINE_SMOOTH ); + + GLboolean const blendEnabled = glIsEnabled( GL_BLEND ); + if( blendEnabled ) glDisable( GL_BLEND ); + + // choose shader program + + GLint shaderProgram; + glGetIntegerv( GL_CURRENT_PROGRAM, & shaderProgram ); + glUseProgram( chooseProgram( format ) ); + + // set opengl vertex attribute array state + + GLint arrayBinding; + glGetIntegerv( GL_ARRAY_BUFFER_BINDING, & arrayBinding ); + + glPushClientAttrib( GL_CLIENT_VERTEX_ARRAY_BIT ); + + glVertexAttribDivisor( ATTRIB_GLSL_LOCATION_PS, 1 ); + glEnableVertexAttribArray( ATTRIB_GLSL_LOCATION_PS ); + glVertexAttribDivisor( ATTRIB_GLSL_LOCATION_VS, 1 ); + glEnableVertexAttribArray( ATTRIB_GLSL_LOCATION_VS ); + + // loop through current selection + + for( std::vector< CSGafferUI::CsVisualiseVectorTool::Selection >::const_iterator + it = m_tool->selection().begin(), + itEnd = m_tool->selection().end(); it != itEnd; ++it ) + { + GafferScene::ScenePlug::PathScope scope( &( ( *it ).context() ), &( ( *it ).path() ) ); + + // check path exists + + if( !( ( *it ).scene().existsPlug()->getValue() ) ) + { + continue; + } + + // extract primitive + + IECoreScene::ConstPrimitivePtr const primitive = + IECore::runTimeCast< IECoreScene::Primitive const >( + ( *it ).scene().objectPlug()->getValue() ); + + if( ! primitive ) + { + continue; + } + + // retrieve cached IECoreGL primitive + + IECoreGL::ConstPrimitivePtr const primitiveGL = + IECore::runTimeCast< IECoreGL::Primitive const >( + converter->convert( primitive.get() ) ); + + if( ! primitiveGL ) + { + continue; + } + + // find "P" vertex attribute + + IECoreGL::Primitive::AttributeMap::const_iterator const pit = + primitiveGL->m_vertexAttributes.find( g_pName ); + if( pit == primitiveGL->m_vertexAttributes.end() ) + { + continue; + } + + IECore::ConstV3fVectorDataPtr const pData = + IECore::runTimeCast< IECore::V3fVectorData const >( ( *pit ).second ); + if( ! pData ) + { + continue; + } + + // find named vertex attribute + // + // NOTE : conversion to IECoreGL mesh may generate vertex attributes (eg. "N") + // so check named primitive variable exists on IECore mesh primitive as well. + + IECoreGL::Primitive::AttributeMap::const_iterator const vit = + primitiveGL->m_vertexAttributes.find( name ); + if( ( vit == primitiveGL->m_vertexAttributes.end() ) || + ( primitive->variables.find( name ) == primitive->variables.end() ) ) + { + continue; + } + + IECore::ConstV3fVectorDataPtr const vData = + IECore::runTimeCast< IECore::V3fVectorData const >( ( *vit ).second ); + if( ! vData ) + { + continue; + } + + // retrieve cached opengl buffer data + + IECoreGL::ConstBufferPtr const pBuffer = + IECore::runTimeCast< IECoreGL::Buffer const >( converter->convert( pData.get() ) ); + IECoreGL::ConstBufferPtr const vBuffer = + IECore::runTimeCast< IECoreGL::Buffer const >( converter->convert( vData.get() ) ); + + // get the object to world transform + + Imath::M44f o2w; + GafferScene::ScenePlug::ScenePath path( ( *it ).path() ); + while( ! path.empty() ) + { + scope.setPath( & path ); + o2w = o2w * ( *it ).scene().transformPlug()->getValue(); + path.pop_back(); + } + + // compute object/normal to view and object to clip matrices + + uniforms.o2v = o2w * w2v; + uniforms.n2v = ( uniforms.o2v.gjInverse() ).transpose(); + uniforms.o2c = uniforms.o2v * uniforms.v2c; + + // upload opengl uniform block data + + glBufferData( GL_UNIFORM_BUFFER, sizeof( UniformBlock ), & uniforms, GL_DYNAMIC_DRAW ); + + // instance a line segment for each element of vector data + + glBindBuffer( GL_ARRAY_BUFFER, pBuffer->m_buffer ); + glVertexAttribPointer( ATTRIB_GLSL_LOCATION_PS, 3, GL_FLOAT, GL_FALSE, 0, + static_cast< void const* >( 0 ) ); + glBindBuffer( GL_ARRAY_BUFFER, vBuffer->m_buffer ); + glVertexAttribPointer( ATTRIB_GLSL_LOCATION_VS, 3, GL_FLOAT, GL_FALSE, 0, + static_cast< void const* >( 0 ) ); + glDrawArraysInstanced( GL_LINES, 0, 2, static_cast< GLsizei >( pData->readable().size() ) ); + } + + // restore opengl state + + glPopClientAttrib(); + glBindBuffer( GL_ARRAY_BUFFER, arrayBinding ); + glBindBuffer( GL_UNIFORM_BUFFER, uniformBinding ); + + glLineWidth( lineWidth ); + + if( lineSmooth ) glEnable( GL_LINE_SMOOTH ); + if( blendEnabled ) glEnable( GL_BLEND ); + if( ! depthEnabled ) glDisable( GL_DEPTH_TEST ); + if( depthWriteEnabled ) glDepthMask( GL_TRUE ); + glUseProgram( shaderProgram ); + } + +# if ( GAFFER_MILESTONE_VERSION > 0 || GAFFER_MAJOR_VERSION >= 61 ) + Imath::Box3f renderBound() const override + { + // NOTE : for now just return an infinite box + + Imath::Box3f b; + b.makeInfinite(); + return b; + } +# endif + +# if ( GAFFER_MILESTONE_VERSION > 0 || GAFFER_MAJOR_VERSION >= 61 ) + unsigned layerMask() const override + { + return ( m_tool ) + ? static_cast< unsigned >( GafferUI::Gadget::Layer::MidFront ) + : static_cast< unsigned >( 0 ); + } +# else + bool hasLayer( GafferUI::Gadget::Layer layer ) + { + return ( m_tool && + ( layer == GafferUI::Gadget::Layer::MidFront ) ); + } +# endif + + private: + + GLuint + chooseProgram + ( + CSGafferUI::CsVisualiseVectorTool::Format const format + ) + const + { + IECoreGL::Shader const* shader = nullptr; + switch( format ) + { + case CSGafferUI::CsVisualiseVectorTool::Format::Point: + shader = m_pointShader.get(); + break; + case CSGafferUI::CsVisualiseVectorTool::Format::Vector: + shader = m_vectorShader.get(); + break; + case CSGafferUI::CsVisualiseVectorTool::Format::Bivector: + shader = m_bivectorShader.get(); + break; + default: + assert( 0 ); + break; + } + + return ( shader != nullptr ) ? shader->program() : static_cast< GLuint >( 0 ); + } + + void + buildShader + ( + IECoreGL::ConstShaderPtr& shader, + std::string const& vertSource + ) + const + { + if( ! shader ) + { + shader = IECoreGL::ShaderLoader::defaultShaderLoader()->create( + vertSource, std::string(), g_fragSource ); + if( shader ) + { + GLuint const program = shader->program(); + GLuint const blockIndex = glGetUniformBlockIndex( program, "UniformBlock" ); + if( blockIndex != GL_INVALID_INDEX ) + { + glUniformBlockBinding( program, blockIndex, g_uniformBlockBindingIndex ); + } + } + } + } + + CSGafferUI::CsVisualiseVectorTool const* m_tool; + mutable IECoreGL::ConstShaderPtr m_pointShader; + mutable IECoreGL::ConstShaderPtr m_vectorShader; + mutable IECoreGL::ConstShaderPtr m_bivectorShader; + mutable IECoreGL::ConstBufferPtr m_uniformBuffer; + }; + +} // namespace + +namespace CSGafferUI +{ + GAFFER_NODE_DEFINE_TYPE( CsVisualiseVectorTool ) + + GafferUI::Tool::ToolDescription< CsVisualiseVectorTool, GafferSceneUI::SceneView > CsVisualiseVectorTool::m_toolDescription; + + size_t CsVisualiseVectorTool::m_firstPlugIndex = 0; + + CsVisualiseVectorTool::CsVisualiseVectorTool + ( + GafferSceneUI::SceneView* const view, + std::string const& name + ) + : GafferSceneUI::SelectionTool( view, name ) +# if GAFFER_COMPATIBILITY_VERSION < MAKE_GAFFER_COMPATIBILITY_VERSION( 1, 5 ) + , m_contextChangedConnection() +# endif + , m_preRenderConnection() + , m_gadget( new Gadget( *this ) ) + , m_selection() + , m_gadgetDirty( true ) + , m_selectionDirty( true ) + , m_priorityPathsDirty( true ) + { + // add gadget to view and hide + + view->viewportGadget()->addChild( m_gadget ); + m_gadget->setVisible( false ); + + // store offset of first plug + + storeIndexOfNextChild( m_firstPlugIndex ); + + // add child plugs + + addChild( new Gaffer::StringPlug( "name", Gaffer::Plug::In, "N" ) ); + addChild( new Gaffer::IntPlug( "format", Gaffer::Plug::In, + static_cast< int >( Format::Bivector ), + static_cast< int >( Format::Point ), + static_cast< int >( Format::Bivector ) ) ); + addChild( new Gaffer::FloatPlug( "scale", Gaffer::Plug::In, g_scaleDefault, g_scaleMin ) ); + addChild( new Gaffer::Color3fPlug( "colour", Gaffer::Plug::In, g_colourDefault ) ); + addChild( new GafferScene::ScenePlug( "__scene", Gaffer::Plug::In ) ); + + // connect out internal scene plug to the parent view's scene plug + + internalScenePlug()->setInput( view->inPlug< GafferScene::ScenePlug >() ); + + // connect signal handlers + + view->viewportGadget()->keyPressSignal().connect( + boost::bind( & CsVisualiseVectorTool::keyPress, this, boost::placeholders::_2 ) ); + + plugDirtiedSignal().connect( + boost::bind( & CsVisualiseVectorTool::plugDirtied, this, boost::placeholders::_1 ) ); + +# if GAFFER_COMPATIBILITY_VERSION >= MAKE_GAFFER_COMPATIBILITY_VERSION( 1, 5 ) + view->contextChangedSignal().connect( + boost::bind( & CsVisualiseVectorTool::contextChanged, this ) ); + GafferSceneUI::ScriptNodeAlgo::selectedPathsChangedSignal( view->scriptNode() ).connect( + boost::bind( &CsVisualiseVectorTool::selectedPathsChanged, this ) ); +# else + connectToViewContext(); + view->contextChangedSignal().connect( + boost::bind( & CsVisualiseVectorTool::connectToViewContext, this ) ); +# endif + + Gaffer::Metadata::plugValueChangedSignal().connect( + boost::bind( & CsVisualiseVectorTool::metadataChanged, this, boost::placeholders::_3 ) ); + Gaffer::Metadata::nodeValueChangedSignal().connect( + boost::bind( & CsVisualiseVectorTool::metadataChanged, this, boost::placeholders::_2 ) ); + } + + CsVisualiseVectorTool::~CsVisualiseVectorTool() + { + // NOTE : ensure that the gadget's reference to the tool is reset + + static_cast< Gadget* >( m_gadget.get() )->resetTool(); + } + + Gaffer::StringPlug* CsVisualiseVectorTool::namePlug() + { + return const_cast< Gaffer::StringPlug* >( + static_cast< CsVisualiseVectorTool const* >( this )->namePlug() ); + } + + Gaffer::StringPlug const* CsVisualiseVectorTool::namePlug() const + { + return getChild< Gaffer::StringPlug >( m_firstPlugIndex + 0 ); + } + + Gaffer::IntPlug* CsVisualiseVectorTool::formatPlug() + { + return const_cast< Gaffer::IntPlug* >( + static_cast< CsVisualiseVectorTool const* >( this )->formatPlug() ); + } + + Gaffer::IntPlug const* CsVisualiseVectorTool::formatPlug() const + { + return getChild< Gaffer::IntPlug >( m_firstPlugIndex + 1 ); + } + + Gaffer::FloatPlug* CsVisualiseVectorTool::scalePlug() + { + return const_cast< Gaffer::FloatPlug* >( + static_cast< CsVisualiseVectorTool const* >( this )->scalePlug() ); + } + + Gaffer::FloatPlug const* CsVisualiseVectorTool::scalePlug() const + { + return getChild< Gaffer::FloatPlug >( m_firstPlugIndex + 2 ); + } + + Gaffer::Color3fPlug* CsVisualiseVectorTool::colourPlug() + { + return const_cast< Gaffer::Color3fPlug* >( + static_cast< CsVisualiseVectorTool const* >( this )->colourPlug() ); + } + + Gaffer::Color3fPlug const* CsVisualiseVectorTool::colourPlug() const + { + return getChild< Gaffer::Color3fPlug >( m_firstPlugIndex + 3 ); + } + + GafferScene::ScenePlug* CsVisualiseVectorTool::internalScenePlug() + { + return const_cast< GafferScene::ScenePlug* >( + static_cast< CsVisualiseVectorTool const* >( this )->internalScenePlug() ); + } + + GafferScene::ScenePlug const* CsVisualiseVectorTool::internalScenePlug() const + { + return getChild< GafferScene::ScenePlug >( m_firstPlugIndex + 4 ); + } + + std::vector< CsVisualiseVectorTool::Selection > const& CsVisualiseVectorTool::selection() const + { + return m_selection; + } +# if GAFFER_COMPATIBILITY_VERSION >= MAKE_GAFFER_COMPATIBILITY_VERSION( 1, 5 ) + void CsVisualiseVectorTool::contextChanged() + { + // Context changes can change the scene, which in turn + // dirties our selection. + selectedPathsChanged(); + } + + void CsVisualiseVectorTool::selectedPathsChanged() + { + m_selectionDirty = true; + m_gadgetDirty = true; + m_priorityPathsDirty = true; + } +# else + void CsVisualiseVectorTool::connectToViewContext() + { + m_contextChangedConnection = view()->getContext()->changedSignal().connect( + boost::bind( & CsVisualiseVectorTool::contextChanged, this, boost::placeholders::_2 ) ); + } + + void CsVisualiseVectorTool::contextChanged + ( + IECore::InternedString const& name + ) + { + if( GafferSceneUI::ContextAlgo::affectsSelectedPaths( name ) || + GafferSceneUI::ContextAlgo::affectsLastSelectedPath( name ) || + ! boost::starts_with( name.string(), "ui:" ) ) + { + m_selectionDirty = true; + m_gadgetDirty = true; + m_priorityPathsDirty = true; + } + } +# endif + + void CsVisualiseVectorTool::plugDirtied + ( + Gaffer::Plug const* const plug + ) + { + if( ( plug == activePlug() ) || + ( plug == internalScenePlug()->objectPlug() ) || + ( plug == internalScenePlug()->transformPlug() ) ) + { + m_selectionDirty = true; + m_gadgetDirty = true; + m_priorityPathsDirty = true; + } + else + if( ( plug == namePlug() ) || + ( plug == scalePlug() ) || + ( plug == colourPlug() ) || + ( plug == formatPlug() ) ) + { + m_gadgetDirty = true; + view()->viewportGadget()->renderRequestSignal()( view()->viewportGadget() ); + } + + if( plug == activePlug() ) + { + if( activePlug()->getValue() ) + { + m_preRenderConnection = view()->viewportGadget()->preRenderSignal().connect( + boost::bind( & CsVisualiseVectorTool::preRender, this ) ); + } + else + { + m_preRenderConnection.disconnect(); + m_gadget->setVisible( false ); + + static_cast< GafferSceneUI::SceneGadget* >( view()->viewportGadget()->getPrimaryChild() ) + ->setPriorityPaths( IECore::PathMatcher() ); + } + } + } + + void CsVisualiseVectorTool::metadataChanged + ( + IECore::InternedString const& key + ) + { + if( ! Gaffer::MetadataAlgo::readOnlyAffectedByChange( key ) ) + { + return; + } + + if( ! m_selectionDirty ) + { + m_selectionDirty = true; + } + + if( ! m_gadgetDirty ) + { + m_gadgetDirty = true; + view()->viewportGadget()->renderRequestSignal()( view()->viewportGadget() ); + } + } + + void CsVisualiseVectorTool::updateSelection() const + { + if( ! m_selectionDirty ) + { + return; + } + + m_selection.clear(); + m_selectionDirty = false; + + if( ! activePlug()->getValue() ) + { + return; + } + + GafferScene::ScenePlug const* scene = + internalScenePlug()->getInput< GafferScene::ScenePlug >(); + + if( !( scene ) || + !( scene = scene->getInput< GafferScene::ScenePlug >() ) ) + { + return; + } + +# if GAFFER_COMPATIBILITY_VERSION >= MAKE_GAFFER_COMPATIBILITY_VERSION( 1, 5 ) + IECore::PathMatcher const selectedPaths = + GafferSceneUI::ScriptNodeAlgo::getSelectedPaths( view()->scriptNode() ); +# else + IECore::PathMatcher const selectedPaths = + GafferSceneUI::ContextAlgo::getSelectedPaths( view()->getContext() ); +# endif + + if( selectedPaths.isEmpty() ) + { + return; + } + + for( IECore::PathMatcher::Iterator it = selectedPaths.begin(), + itEnd = selectedPaths.end(); it != itEnd; ++it ) + { +# if GAFFER_COMPATIBILITY_VERSION >= MAKE_GAFFER_COMPATIBILITY_VERSION( 1, 5 ) + m_selection.emplace_back( *( scene ), *it, *( view()->context() ) ); +# else + m_selection.emplace_back( *( scene ), *it, *( view()->getContext() ) ); +# endif + } + } + + void CsVisualiseVectorTool::preRender() + { + updateSelection(); + + if( m_priorityPathsDirty ) + { +# if GAFFER_COMPATIBILITY_VERSION >= MAKE_GAFFER_COMPATIBILITY_VERSION( 1, 5 ) + static_cast< GafferSceneUI::SceneGadget* >( view()->viewportGadget()->getPrimaryChild() ) + ->setPriorityPaths( ( m_selection.empty() ) + ? IECore::PathMatcher() + : GafferSceneUI::ScriptNodeAlgo::getSelectedPaths( view()->scriptNode() ) ); +# else + static_cast< GafferSceneUI::SceneGadget* >( view()->viewportGadget()->getPrimaryChild() ) + ->setPriorityPaths( ( m_selection.empty() ) + ? IECore::PathMatcher() + : GafferSceneUI::ContextAlgo::getSelectedPaths( view()->getContext() ) ); +# endif + m_priorityPathsDirty = false; + } + + if( m_selection.empty() ) + { + m_gadget->setVisible( false ); + return; + } + + m_gadget->setVisible( true ); + + if( m_gadgetDirty ) + { + m_gadgetDirty = false; + } + } + + bool CsVisualiseVectorTool::keyPress + ( + GafferUI::KeyEvent const& event + ) + { + if( ! activePlug()->getValue() ) + { + return false; + } + + // allow user to scale vectors with +/- keys + + if( event.key == "Plus" || event.key == "Equal" ) + { + scalePlug()->setValue( scalePlug()->getValue() + g_scaleInc ); + } + else + if( event.key == "Minus" || event.key == "Underscore" ) + { + scalePlug()->setValue( std::max( scalePlug()->getValue() - g_scaleInc, g_scaleMin ) ); + } + + return false; + } + + CsVisualiseVectorTool::Selection::Selection + ( + GafferScene::ScenePlug const& scene, + GafferScene::ScenePlug::ScenePath const& path, + Gaffer::Context const& context + ) + : m_scene( & scene ) + , m_path( path ) + , m_context( & context ) + {} + + GafferScene::ScenePlug const& CsVisualiseVectorTool::Selection::scene() const + { + return *( m_scene ); + } + + GafferScene::ScenePlug::ScenePath const& CsVisualiseVectorTool::Selection::path() const + { + return m_path; + } + + Gaffer::Context const& CsVisualiseVectorTool::Selection::context() const + { + return *( m_context ); + } + +} // CSGafferUI diff --git a/contrib/visualisers/CsVisualiseVectorTool.h b/contrib/visualisers/CsVisualiseVectorTool.h new file mode 100644 index 00000000000..07ad3a543dc --- /dev/null +++ b/contrib/visualisers/CsVisualiseVectorTool.h @@ -0,0 +1,195 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2024, Cinesite VFX Ltd. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above +// copyright notice, this list of conditions and the following +// disclaimer. +// +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided with +// the distribution. +// +// * Neither the name of John Haddon nor the names of +// any other contributors to this software may be used to endorse or +// promote products derived from this software without specific prior +// written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +////////////////////////////////////////////////////////////////////////// + +#ifndef CSGAFFERUI_TOOLS_CSVISUALISEVECTORTOOL_H +#define CSGAFFERUI_TOOLS_CSVISUALISEVECTORTOOL_H + +#include "../../GafferTypeIds.h" + +#include +#include +#include +#include +#include +#include + +namespace CSGafferUI +{ + /** + * @brief Tool that displays a named primitive variable of type Imath::V3f as line vectors + * + * The data is interpreted and visualised based on the format plug. + */ + struct CsVisualiseVectorTool + : public GafferSceneUI::SelectionTool + { + /** + * @brief Data formats + */ + enum class Format + { + Point = 0, /**< Interpret data as points */ + Vector, /**< Interpret data as vectors */ + Bivector /**< Interpret data as bivectors */ + }; + + /** + * @brief ctor + * @param view parent view + * @param name name for node + */ + explicit + CsVisualiseVectorTool + ( + GafferSceneUI::SceneView* view, + std::string const& name = Gaffer::GraphComponent::defaultName< CsVisualiseVectorTool >() + ); + + /** + * @brief dtor + */ + ~CsVisualiseVectorTool() override; + + /** + * @name GafferPlugAccessors + * @brief Gaffer plug accessor functions + * @{ + */ + + Gaffer::StringPlug* namePlug(); + Gaffer::StringPlug const* namePlug() const; + + Gaffer::IntPlug* formatPlug(); + Gaffer::IntPlug const* formatPlug() const; + + Gaffer::FloatPlug* scalePlug(); + Gaffer::FloatPlug const* scalePlug() const; + + Gaffer::Color3fPlug* colourPlug(); + Gaffer::Color3fPlug const* colourPlug() const; + + /** + * @} + */ + + GAFFER_NODE_DECLARE_TYPE( + CSGafferUI::CsVisualiseVectorTool, + CSInternalTypes::CsVisualiseVectorToolTypeId, + GafferSceneUI::SelectionTool ); + + /** + * @brief Class encapsulating a selected scene location + */ + struct Selection + { + /** + * @brief ctor + * @param scene scene + * @param path scene path + * @param context context + */ + Selection + ( + GafferScene::ScenePlug const& scene, + GafferScene::ScenePlug::ScenePath const& path, + Gaffer::Context const& context + ); + + /** + * @brief Get the scene + * @return scene + */ + GafferScene::ScenePlug const& scene() const; + + /** + * @brief Get the scene path + * @return scene path + */ + GafferScene::ScenePlug::ScenePath const& path() const; + + /** + * @brief Get the context + * @return context + */ + Gaffer::Context const& context() const; + + private: + + GafferScene::ConstScenePlugPtr m_scene; + GafferScene::ScenePlug::ScenePath m_path; + Gaffer::ConstContextPtr m_context; + }; + + /** + * @brief Get the current selection + * @return current selection + */ + std::vector< Selection > const& selection() const; + + private: + + GafferScene::ScenePlug* internalScenePlug(); + GafferScene::ScenePlug const* internalScenePlug() const; + + void plugDirtied( Gaffer::Plug const* plug ); + void metadataChanged( IECore::InternedString const& key ); + void updateSelection() const; + void preRender(); + bool keyPress( GafferUI::KeyEvent const& event ); + +# if GAFFER_COMPATIBILITY_VERSION >= MAKE_GAFFER_COMPATIBILITY_VERSION( 1, 5 ) + void contextChanged(); + void selectedPathsChanged(); +# else + void connectToViewContext(); + void contextChanged( IECore::InternedString const& name ); + Gaffer::Signals::ScopedConnection m_contextChangedConnection; +# endif + Gaffer::Signals::ScopedConnection m_preRenderConnection; + + GafferUI::GadgetPtr m_gadget; + mutable std::vector< Selection > m_selection; + bool m_gadgetDirty; + mutable bool m_selectionDirty; + bool m_priorityPathsDirty; + + static ToolDescription< CsVisualiseVectorTool, GafferSceneUI::SceneView > m_toolDescription; + static size_t m_firstPlugIndex; + }; + +} // CSGafferUI + +#endif // CSGAFFERUI_TOOLS_CSVISUALISEVECTORTOOL_H diff --git a/contrib/visualisers/CsVisualiseVertexIdTool.cpp b/contrib/visualisers/CsVisualiseVertexIdTool.cpp new file mode 100644 index 00000000000..b1cd53fddcb --- /dev/null +++ b/contrib/visualisers/CsVisualiseVertexIdTool.cpp @@ -0,0 +1,1373 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2024, Cinesite VFX Ltd. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above +// copyright notice, this list of conditions and the following +// disclaimer. +// +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided with +// the distribution. +// +// * Neither the name of John Haddon nor the names of +// any other contributors to this software may be used to endorse or +// promote products derived from this software without specific prior +// written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +IECORE_PUSH_DEFAULT_VISIBILITY +#if GAFFER_COMPATIBILITY_VERSION < MAKE_GAFFER_COMPATIBILITY_VERSION( 1, 3 ) +#include +#else +#include +#endif +IECORE_POP_DEFAULT_VISIBILITY + +#define private public +#include +#include +#undef private + +#include "CsVisualiseVertexIdTool.h" + +#include +#include +#include +#include +#include +#include +#include +#if GAFFER_COMPATIBILITY_VERSION >= MAKE_GAFFER_COMPATIBILITY_VERSION( 1, 5 ) +#include +#else +#include +#endif + +#include +#include + +#include +#include +#include +//#include +//#include +//#include +#include +#if ( GAFFER_MILESTONE_VERSION == 0 && GAFFER_MAJOR_VERSION < 61 ) +#include +#endif + +//#include + +#include +#include + +#include +#include +#include + +namespace +{ + // text sizes + + float const g_textSizeDefault = 9.0f; + float const g_textSizeMin = 6.0f; + float const g_textSizeInc = 0.5f; + float const g_cursorRadiusDefault = 25.f; + + Imath::Color3f const g_colourFGDefault( 0.9f ); + Imath::Color3f const g_colourHLDefault( 0.466f, 0.612f, 0.741f ); + + // convert three component colour to four component colour with full opacity + + Imath::Color4f convertToColor4f + ( + Imath::Color3f const& c + ) + { + return Imath::Color4f( c[ 0 ], c[ 1 ], c[ 2 ], 1.f ); + } + + // name of P primitive variable + + std::string const g_pName( "P" ); + + // uniform block structure (std140 layout) + + struct UniformBlock + { + alignas( 16 ) Imath::M44f o2c; + }; + + // block binding indexes for the uniform and shader storage buffers + + GLuint const g_uniformBlockBindingIndex = 0; + GLuint const g_storageBlockBindingIndex = 0; + + // uniform block definition (std140 layout) + +# define UNIFORM_BLOCK_NAME "UniformBlock" +# define UNIFORM_BLOCK_GLSL_SOURCE \ + "layout( std140, row_major ) uniform " UNIFORM_BLOCK_NAME "\n" \ + "{\n" \ + " mat4 o2c;\n" \ + "} uniforms;\n" + + // shader storage block definition (std430 layout) + // + // NOTE : std430 layout ensures that the elements of a uint array are tightly packed + // std140 would require 16 byte alignment of each element ... + +# define STORAGE_BLOCK_NAME "StorageBlock" +# define STORAGE_BLOCK_GLSL_SOURCE \ + "layout( std430 ) buffer " STORAGE_BLOCK_NAME "\n" \ + "{\n" \ + " coherent restrict uint visibility[];\n" \ + "} buffers;\n" + + // vertex attribute definitions + +# define ATTRIB_GLSL_LOCATION_PS 0 +# define ATTRIB_GLSL_SOURCE \ + "layout( location = " BOOST_PP_STRINGIZE( ATTRIB_GLSL_LOCATION_PS ) " ) in vec3 ps;\n" + + // interface block definition + +# define INTERFACE_BLOCK_GLSL_SOURCE( STORAGE, NAME ) \ + BOOST_PP_STRINGIZE( STORAGE ) " InterfaceBlock\n" \ + "{\n" \ + " flat uint vertexId;\n" \ + "} " BOOST_PP_STRINGIZE( NAME ) ";\n" + + // opengl vertex shader code + + std::string const g_vertSource + ( + "#version 430\n" + + UNIFORM_BLOCK_GLSL_SOURCE + + ATTRIB_GLSL_SOURCE + + INTERFACE_BLOCK_GLSL_SOURCE( out, outputs ) + + "void main()\n" + "{\n" + " gl_Position = vec4( ps, 1.0 ) * uniforms.o2c;\n" + " outputs.vertexId = uint( gl_VertexID );\n" + "}\n" + ); + + // opengl fragment shader code + + std::string const g_fragSource + ( + "#version 430\n" + + // NOTE : ensure that shader is only run for fragments that pass depth test. + + "layout( early_fragment_tests ) in;\n" + + STORAGE_BLOCK_GLSL_SOURCE + + UNIFORM_BLOCK_GLSL_SOURCE + + INTERFACE_BLOCK_GLSL_SOURCE( in, inputs ) + + "void main()\n" + "{\n" + " uint index = inputs.vertexId / 32u;\n" + " uint value = inputs.vertexId % 32u;\n" + " atomicOr( buffers.visibility[ index ], 1u << value );\n" + "}\n" + ); + + // the gadget that does the actual opengl drawing of the vertex id text + + struct Gadget + : public GafferUI::Gadget + { + explicit + Gadget + ( + CSGafferUI::CsVisualiseVertexIdTool& tool, + std::string const& name = "CsVisualiseVertexIdGadget" + ) + : GafferUI::Gadget( name ) + , m_tool( & tool ) + , m_shader() + , m_uniformBuffer() + , m_storageBuffer() + , m_storageCapacity( 0 ) + {} + + void resetTool() + { + m_tool = nullptr; + } + + protected: + + void +# if ( GAFFER_MILESTONE_VERSION > 0 || GAFFER_MAJOR_VERSION >= 61 ) + renderLayer +# else + doRenderLayer +# endif + ( + GafferUI::Gadget::Layer layer, + GafferUI::Style const* style +# if ( GAFFER_MILESTONE_VERSION > 0 || GAFFER_MAJOR_VERSION >= 61 ) + , GafferUI::Gadget::RenderReason reason +# endif + ) + const override + { + if( ( layer != GafferUI::Gadget::Layer::MidFront ) || +# if ( GAFFER_MILESTONE_VERSION > 0 || GAFFER_MAJOR_VERSION >= 61 ) + ( GafferUI::Gadget::isSelectionRender( reason ) ) ) +# else + ( IECoreGL::Selector::currentSelector() ) ) +# endif + { + return; + } + + // check tool reference valid + + if( m_tool == nullptr ) + { + return; + } + + // get parent viewport gadget + + GafferUI::ViewportGadget const* const viewportGadget = + ancestor< GafferUI::ViewportGadget >(); + if( viewportGadget == nullptr ) + { + return; + } + + // bootleg shader + + buildShader(); + + if( ! m_shader ) + { + return; + } + + // get the cached converter from IECoreGL, this is used to convert primitive + // variable data to opengl buffers which will be shared with the IECoreGL renderer + + IECoreGL::CachedConverter* const converter = + IECoreGL::CachedConverter::defaultCachedConverter(); + + // bootleg uniform buffer + + GLint uniformBinding; + glGetIntegerv( GL_UNIFORM_BUFFER_BINDING, & uniformBinding ); + + if( ! m_uniformBuffer ) + { + GLuint buffer = 0u; + glGenBuffers( 1, & buffer ); + glBindBuffer( GL_UNIFORM_BUFFER, buffer ); + glBufferData( GL_UNIFORM_BUFFER, sizeof( UniformBlock ), 0, GL_DYNAMIC_DRAW ); + glBindBuffer( GL_UNIFORM_BUFFER, uniformBinding ); + m_uniformBuffer.reset( new IECoreGL::Buffer( buffer ) ); + } + + UniformBlock uniforms; + + // bootleg storage buffer + + GLint storageBinding; + glGetIntegerv( GL_SHADER_STORAGE_BUFFER_BINDING, & storageBinding ); + + if( ! m_storageBuffer ) + { + GLuint buffer = 0u; + glGenBuffers( 1, & buffer ); + m_storageBuffer.reset( new IECoreGL::Buffer( buffer ) ); + } + + // save opengl state + + GLfloat pointSize; + glGetFloatv( GL_POINT_SIZE, & pointSize ); + + GLint depthFunc; + glGetIntegerv( GL_DEPTH_FUNC, & depthFunc ); + + GLboolean depthWriteEnabled; + glGetBooleanv( GL_DEPTH_WRITEMASK, & depthWriteEnabled ); + + GLboolean const depthEnabled = glIsEnabled( GL_DEPTH_TEST ); + GLboolean const multisampleEnabled = glIsEnabled( GL_MULTISAMPLE ); + + GLint shaderProgram; + glGetIntegerv( GL_CURRENT_PROGRAM, & shaderProgram ); + + GLint arrayBinding; + glGetIntegerv( GL_ARRAY_BUFFER_BINDING, & arrayBinding ); + + // get the world to clip space matrix + + Imath::M44f v2c; + glGetFloatv( GL_PROJECTION_MATRIX, v2c.getValue() ); + Imath::M44f const w2c = viewportGadget->getCameraTransform().gjInverse() * v2c; + + // get raster space bounding box + + Imath::Box2f const rasterBounds = Imath::Box2f( Imath::V2f( 0.f ), + Imath::V2f( static_cast< float >( viewportGadget->getViewport().x ), + static_cast< float >( viewportGadget->getViewport().y ) ) ); + + // get text raster space scale and colour + // + // NOTE : It seems that Gaffer defines the origin of raster space as the top left corner + // of the viewport, however the style text drawing functions assume that y increases + // "up" the screen rather than "down", so invert y to ensure text is not upside down. + + float const size = m_tool->sizePlug()->getValue(); + Imath::V3f const scale( size, -size, 1.f ); + Imath::Color4f const colourFG = convertToColor4f( m_tool->colourPlug()->getValue() ); + Imath::Color4f const colourHL = convertToColor4f( m_tool->cursorColourPlug()->getValue() ); + + // get cursor raster position + + int cursorVertexId = -1; + Imath::V2f const cursorRasterPos = m_tool->cursorPos(); + Imath::V2f cursorVertexRasterPos = Imath::V2f( -1.f ); + float minDistance2 = std::numeric_limits< float >::max(); + + // get cursor search radius + // + // NOTE : when the cursor position is invalid set the radius to zero to disable search. + + Imath::Box2i const viewport( Imath::V2i( 0 ), viewportGadget->getViewport() ); + float const cursorRadius = ( m_tool->cursorPosValid() && viewport.intersects( cursorRasterPos ) ) + ? m_tool->cursorRadiusPlug()->getValue() : 0.f; + float const cursorRadius2 = cursorRadius * cursorRadius; + + // loop through current selection + + std::stringstream oss; + for( std::vector< CSGafferUI::CsVisualiseVertexIdTool::Selection >::const_iterator + it = m_tool->selection().begin(), + itEnd = m_tool->selection().end(); it != itEnd; ++it ) + { + GafferScene::ScenePlug::PathScope scope( &( ( *it ).context() ), &( ( *it ).path() ) ); + + // check path exists + + if( !( ( *it ).scene().existsPlug()->getValue() ) ) + { + continue; + } + + // extract primitive + + IECoreScene::ConstPrimitivePtr const primitive = + IECore::runTimeCast< IECoreScene::Primitive const >( + ( *it ).scene().objectPlug()->getValue() ); + + if( ! primitive ) + { + continue; + } + + // find "P" vertex attribute + // + // TODO : We need to use the same polygon offset as the Viewer uses when it draws the + // primitive in polygon points mode. For mesh primitives topology may be different, + // primitive variables were converted to face varying and the mesh triangulated + // with vertex positions duplicated. This means that gl_VertexID in the shader + // no longer corresponds to the vertex id we want to display. It also means there + // may be multiple vertices in the IECoreGL mesh for each vertex in the IECore mesh. + // To get the correct polygon offset we need to draw the mesh using the same + // OpenGL draw call as the Viewer used so we must draw the IECoreGL mesh. So + // we need to search for the (posibly multiple) vertices that correspond to each + // original vertex. If any of these IECoreGL mesh vertices are visible we display + // the IECore mesh vertex id. To accelerate the search we build a multi map keyed + // on vertex position. This assumes that the triangulation and/or conversion to + // face varying attributes processing in IECore does not alter the position of the + // vertices. The building of this map is done after we issue the draw call for the + // mesh primitive, this gives OpenGL an opportunity to concurrently execute the + // visibility pass while we are building the map, ready for the map buffer operation. + // For points and curves primitives there is no polygon offset. For all primitives + // there may be a slight slight precision difference in o2c transform so push vertices + // forward. + // NOTE : a cheap alternative approach that solves most of the above problems is to draw + // the visibility pass using "fat" points which cover multiple pixels. This still + // has problems for vertices with negative surrounding curvature ... + // + // NOTE : We use the primitive variable from the IECore primitive as that has + // vertex interpolation. + + IECore::ConstV3fVectorDataPtr const pData = + primitive->expandedVariableData< IECore::V3fVectorData >( + g_pName, IECoreScene::PrimitiveVariable::Vertex, false /* throwIfInvalid */ ); + + if( ! pData ) + { + continue; + } + + // retrieve cached opengl buffer data + + IECoreGL::ConstBufferPtr const pBuffer = + IECore::runTimeCast< IECoreGL::Buffer const >( converter->convert( pData.get() ) ); + + // get the object to world transform + + Imath::M44f o2w; + GafferScene::ScenePlug::ScenePath path( ( *it ).path() ); + while( ! path.empty() ) + { + scope.setPath( & path ); + o2w = o2w * ( *it ).scene().transformPlug()->getValue(); + path.pop_back(); + } + + // compute object to clip matrix + + uniforms.o2c = o2w * w2c; + + // upload opengl uniform block data + + glBindBufferBase( GL_UNIFORM_BUFFER, g_uniformBlockBindingIndex, m_uniformBuffer->m_buffer ); + glBufferData( GL_UNIFORM_BUFFER, sizeof( UniformBlock ), & uniforms, GL_DYNAMIC_DRAW ); + + // ensure storage buffer capacity + + glBindBufferBase( GL_SHADER_STORAGE_BUFFER, g_storageBlockBindingIndex, m_storageBuffer->m_buffer ); + + std::size_t const storageCapacity = + ( pData->readable().size() / static_cast< std::size_t >( 32 ) ) + + static_cast< std::size_t >( 1 ); + std::size_t const storageSize = sizeof( std::uint32_t ) * storageCapacity; + + if( m_storageCapacity < storageCapacity ) + { + glBufferData( GL_SHADER_STORAGE_BUFFER, storageSize, 0, GL_DYNAMIC_DRAW ); + m_storageCapacity = storageCapacity; + } + + // clear storage buffer + // + // NOTE : Shader writes to individual bits using atomicOr instruction so region of + // storage buffer being used for current object needs to be cleared to zero + + GLuint const zeroValue = 0u; + glClearBufferSubData( GL_SHADER_STORAGE_BUFFER, GL_R32UI, 0, storageSize, + GL_RED_INTEGER, GL_UNSIGNED_INT, & zeroValue ); + + // set opengl state + + glPointSize( 3.f ); + glDepthFunc( GL_LEQUAL ); + if( ! depthEnabled ) glEnable( GL_DEPTH_TEST ); + if( depthEnabled ) glDisable( GL_DEPTH_TEST ); + if( depthWriteEnabled ) glDepthMask( GL_FALSE ); + if( multisampleEnabled ) glDisable( GL_MULTISAMPLE ); + + // set opengl vertex attribute array state + + glPushClientAttrib( GL_CLIENT_VERTEX_ARRAY_BIT ); + + glVertexAttribDivisor( ATTRIB_GLSL_LOCATION_PS, 0 ); + glEnableVertexAttribArray( ATTRIB_GLSL_LOCATION_PS ); + + // set visibility pass shader + + glUseProgram( m_shader->program() ); + + // draw points and ouput visibility to storage buffer + + glBindBuffer( GL_ARRAY_BUFFER, pBuffer->m_buffer ); + glVertexAttribPointer( ATTRIB_GLSL_LOCATION_PS, 3, GL_FLOAT, GL_FALSE, 0, + static_cast< void const* >( 0 ) ); + glDrawArrays( GL_POINTS, 0, static_cast< GLsizei >( pData->readable().size() ) ); + + // restore opengl state + + glPopClientAttrib(); + glBindBuffer( GL_ARRAY_BUFFER, arrayBinding ); + glBindBuffer( GL_UNIFORM_BUFFER, uniformBinding ); + + glPointSize( pointSize ); + glDepthFunc( depthFunc ); + if( ! depthEnabled ) glDisable( GL_DEPTH_TEST ); + if( depthEnabled ) glEnable( GL_DEPTH_TEST ); + if( depthWriteEnabled ) glDepthMask( GL_TRUE ); + if( multisampleEnabled ) glEnable( GL_MULTISAMPLE ); + glUseProgram( shaderProgram ); + + // map storage buffer + + std::uint32_t const* const vBuffer = + static_cast< std::uint32_t* >( glMapBufferRange( + GL_SHADER_STORAGE_BUFFER, 0, storageSize, GL_MAP_READ_BIT ) ); + glBindBuffer( GL_SHADER_STORAGE_BUFFER, storageBinding ); + + // draw vertex ids offset to vertex position in raster space + + if( vBuffer ) + { + GafferUI::ViewportGadget::RasterScope raster( viewportGadget ); + + std::vector< Imath::V3f > const& points = pData->readable(); + for( int i = 0; i < points.size(); ++i ) + { + // check visibility of vertex + + std::uint32_t const index = static_cast< std::uint32_t >( i ) / static_cast< std::uint32_t >( 32u ); + std::uint32_t const value = static_cast< std::uint32_t >( i ) % static_cast< std::uint32_t >( 32u ); + + if( vBuffer[ index ] & ( static_cast< std::uint32_t >( 1u ) << value ) ) + { + // transform vertex position to raster space and do manual scissor test + // + // NOTE : visibility pass encorporates scissor test which culls most + // vertices however some will slip through as visibility pass + // draws "fat" points. bounds test is cheap. + + Imath::V3f worldPos; + o2w.multVecMatrix( points[ i ], worldPos ); + Imath::V2f rasterPos = viewportGadget->worldToRasterSpace( worldPos ); + if( rasterBounds.intersects( rasterPos ) ) + { + int vertexId = i; + + // update cursor vertex id + // + // NOTE : We defer drawing of the vertex id currently under the cursor, so + // draw the last vertex id label if we replace the cursor vertex id + + float const distance2 = ( cursorRasterPos - rasterPos ).length2(); + if( ( distance2 < cursorRadius2 ) && ( distance2 < minDistance2 ) ) + { + using std::swap; + swap( cursorVertexId, vertexId ); + swap( cursorVertexRasterPos, rasterPos ); + minDistance2 = distance2; + } + + // draw vertex id label + + if( vertexId != -1 ) + { + oss.str( "" ); + oss.clear(); + oss << vertexId; + std::string const text = oss.str(); + + glPushMatrix(); + glTranslatef( rasterPos.x - style->textBound( GafferUI::Style::LabelText, text ).size().x * 0.5f * scale.x, rasterPos.y, 0.f ); + glScalef( scale.x, scale.y, scale.z ); + style->renderText( GafferUI::Style::LabelText, text, GafferUI::Style::NormalState, & colourFG ); + glPopMatrix(); + } + } + } + } + + // unmap storage buffer + + glBindBuffer( GL_SHADER_STORAGE_BUFFER, m_storageBuffer->m_buffer ); + glUnmapBuffer( GL_SHADER_STORAGE_BUFFER ); + glBindBuffer( GL_SHADER_STORAGE_BUFFER, storageBinding ); + } + + glBindBuffer( GL_SHADER_STORAGE_BUFFER, storageBinding ); + } + + // draw cursor vertex + + if( cursorVertexId != -1 ) + { + GafferUI::ViewportGadget::RasterScope raster( viewportGadget ); + + oss.str( "" ); + oss.clear(); + oss << cursorVertexId; + std::string const text = oss.str(); + + glPushMatrix(); + glTranslatef( cursorVertexRasterPos.x - style->textBound( GafferUI::Style::LabelText, text ).size().x * scale.x, cursorVertexRasterPos.y, 0.f ); + glScalef( scale.x * 2.f, scale.y * 2.f, scale.z ); + style->renderText( GafferUI::Style::LabelText, text, GafferUI::Style::NormalState, & colourHL ); + glPopMatrix(); + } + + // set tool cursor vertex id + + m_tool->cursorVertexId( cursorVertexId ); + } + +# if ( GAFFER_MILESTONE_VERSION > 0 || GAFFER_MAJOR_VERSION >= 61 ) + Imath::Box3f renderBound() const override + { + // NOTE : for now just return an infinite box + + Imath::Box3f b; + b.makeInfinite(); + return b; + } +# endif + +# if ( GAFFER_MILESTONE_VERSION > 0 || GAFFER_MAJOR_VERSION >= 61 ) + unsigned layerMask() const override + { + return ( m_tool ) + ? static_cast< unsigned >( GafferUI::Gadget::Layer::MidFront ) + : static_cast< unsigned >( 0 ); + } +# else + bool hasLayer( GafferUI::Gadget::Layer layer ) const override + { + return ( m_tool && + ( layer == GafferUI::Gadget::Layer::MidFront ) ); + } +# endif + + private: + + void buildShader() const + { + if( ! m_shader ) + { + m_shader = IECoreGL::ShaderLoader::defaultShaderLoader()->create( + g_vertSource, std::string(), g_fragSource ); + if( m_shader ) + { + GLuint const program = m_shader->program(); + GLuint const uniformblockIndex = glGetProgramResourceIndex( program, GL_UNIFORM_BLOCK, UNIFORM_BLOCK_NAME ); + if( uniformblockIndex != GL_INVALID_INDEX ) + { + glUniformBlockBinding( program, uniformblockIndex, g_uniformBlockBindingIndex ); + } + GLuint const storageblockIndex = glGetProgramResourceIndex( program, GL_SHADER_STORAGE_BLOCK, STORAGE_BLOCK_NAME ); + if( storageblockIndex != GL_INVALID_INDEX ) + { + glShaderStorageBlockBinding( program, storageblockIndex, g_storageBlockBindingIndex ); + } + } + } + } + + CSGafferUI::CsVisualiseVertexIdTool* m_tool; + mutable IECoreGL::ConstShaderPtr m_shader; + mutable IECoreGL::ConstBufferPtr m_uniformBuffer; + mutable IECoreGL::ConstBufferPtr m_storageBuffer; + mutable std::size_t m_storageCapacity; + }; + +} // namespace + +namespace CSGafferUI +{ + GAFFER_NODE_DEFINE_TYPE( CsVisualiseVertexIdTool ); + + GafferUI::Tool::ToolDescription< CsVisualiseVertexIdTool, GafferSceneUI::SceneView > CsVisualiseVertexIdTool::m_toolDescription; + + size_t CsVisualiseVertexIdTool::m_firstPlugIndex = 0; + + CsVisualiseVertexIdTool::CsVisualiseVertexIdTool + ( + GafferSceneUI::SceneView* const view, + std::string const& name + ) + : GafferSceneUI::SelectionTool( view, name ) +# if GAFFER_COMPATIBILITY_VERSION < MAKE_GAFFER_COMPATIBILITY_VERSION( 1, 5 ) + , m_contextChangedConnection() +# endif + , m_preRenderConnection() + , m_buttonPressConnection() + , m_dragBeginConnection() + , m_gadget( new Gadget( *this ) ) + , m_selection() + , m_cursorPos( -1, -1 ) + , m_cursorPosValid( false ) + , m_cursorValue() + , m_cursorVertexId( -1 ) + , m_gadgetDirty( true ) + , m_selectionDirty( true ) + , m_priorityPathsDirty( true ) + , m_acceptedButtonPress( false ) + , m_initiatedDrag( false ) + { + // add gadget to view and hide + + view->viewportGadget()->addChild( m_gadget ); + m_gadget->setVisible( false ); + + // store offset of first plug + + storeIndexOfNextChild( m_firstPlugIndex ); + + // add child plugs + + addChild( new Gaffer::FloatPlug( "size", Gaffer::Plug::In, g_textSizeDefault, g_textSizeMin ) ); + addChild( new Gaffer::Color3fPlug( "colour", Gaffer::Plug::In, g_colourFGDefault ) ); + addChild( new Gaffer::Color3fPlug( "cursorColour", Gaffer::Plug::In, g_colourHLDefault ) ); + addChild( new Gaffer::FloatPlug( "cursorRadius", Gaffer::Plug::In, g_cursorRadiusDefault, 0.f ) ); + addChild( new GafferScene::ScenePlug( "__scene", Gaffer::Plug::In ) ); + + // connect our internal scene plug to the parent view's scene plug + + internalScenePlug()->setInput( view->inPlug< GafferScene::ScenePlug >() ); + + // connect signal handlers + // + // NOTE : connecting to the viewport gadget means we will get called for all events + // which makes sense for key events, however we do not want to display vertex id + // text when the mouse is over another gadget, (eg. Transform Tool handle) + // so instead connect to scene gadget signal. + // NOTE : There are other handlers that will attempt to consume button and drag + // events so connect handlers at the front of button/drag signal handler queues. + + view->viewportGadget()->keyPressSignal().connect( + boost::bind( & CsVisualiseVertexIdTool::keyPress, this, boost::placeholders::_2 ) ); + + // NOTE : drag end and button release handlers remain whilst tool inactive in case tool + // is made inactive after button pressed or drag initiated in which case these + // handlers still need to tidy up state. + + sceneGadget()->buttonReleaseSignal(). +# if ( GAFFER_MILESTONE_VERSION > 0 || GAFFER_MAJOR_VERSION >= 62 ) + connectFront +# else + connect +# endif + ( boost::bind( & CsVisualiseVertexIdTool::buttonRelease, this, boost::placeholders::_2 ) +# if ( GAFFER_MILESTONE_VERSION == 0 && GAFFER_MAJOR_VERSION < 62 ) + , boost::signals::at_front +# endif + ); + + sceneGadget()->dragEndSignal(). +# if ( GAFFER_MILESTONE_VERSION > 0 || GAFFER_MAJOR_VERSION >= 62 ) + connectFront +# else + connect +# endif + ( boost::bind( & CsVisualiseVertexIdTool::dragEnd, this, boost::placeholders::_2 ) +# if ( GAFFER_MILESTONE_VERSION == 0 && GAFFER_MAJOR_VERSION < 62 ) + , boost::signals::at_front +# endif + ); + + // NOTE : mouse tracking handlers remain connected whilst tool inactive as they track the cursor + // line and whether its valid or not. This prevents the vertex id display from "sticking" to + // edge of viewport when cursor leaves viewport's screen space. It also means that we do + // not have to work out the cursor line and whether its valid when tool is made active. + + sceneGadget()->enterSignal().connect( + boost::bind( & CsVisualiseVertexIdTool::enter, this, boost::placeholders::_2 ) ); + sceneGadget()->leaveSignal().connect( + boost::bind( & CsVisualiseVertexIdTool::leave, this, boost::placeholders::_2 ) ); + sceneGadget()->mouseMoveSignal().connect( + boost::bind( & CsVisualiseVertexIdTool::mouseMove, this, boost::placeholders::_2 ) ); + + plugDirtiedSignal().connect( + boost::bind( & CsVisualiseVertexIdTool::plugDirtied, this, boost::placeholders::_1 ) ); + +# if GAFFER_COMPATIBILITY_VERSION >= MAKE_GAFFER_COMPATIBILITY_VERSION( 1, 5 ) + view->contextChangedSignal().connect( + boost::bind( & CsVisualiseVertexIdTool::contextChanged, this ) ); + GafferSceneUI::ScriptNodeAlgo::selectedPathsChangedSignal( view->scriptNode() ).connect( + boost::bind( &CsVisualiseVertexIdTool::selectedPathsChanged, this ) ); +# else + connectToViewContext(); + view->contextChangedSignal().connect( + boost::bind( & CsVisualiseVertexIdTool::connectToViewContext, this ) ); +# endif + + Gaffer::Metadata::plugValueChangedSignal().connect( + boost::bind( & CsVisualiseVertexIdTool::metadataChanged, this, boost::placeholders::_3 ) ); + Gaffer::Metadata::nodeValueChangedSignal().connect( + boost::bind( & CsVisualiseVertexIdTool::metadataChanged, this, boost::placeholders::_2 ) ); + } + + CsVisualiseVertexIdTool::~CsVisualiseVertexIdTool() + { + // NOTE : ensure that the gadget's reference to the tool is reset + + static_cast< Gadget* >( m_gadget.get() )->resetTool(); + } + + Gaffer::FloatPlug* CsVisualiseVertexIdTool::sizePlug() + { + return const_cast< Gaffer::FloatPlug* >( + static_cast< CsVisualiseVertexIdTool const* >( this )->sizePlug() ); + } + + Gaffer::FloatPlug const* CsVisualiseVertexIdTool::sizePlug() const + { + return getChild< Gaffer::FloatPlug >( m_firstPlugIndex + 0 ); + } + + Gaffer::Color3fPlug* CsVisualiseVertexIdTool::colourPlug() + { + return const_cast< Gaffer::Color3fPlug* >( + static_cast< CsVisualiseVertexIdTool const* >( this )->colourPlug() ); + } + + Gaffer::Color3fPlug const* CsVisualiseVertexIdTool::colourPlug() const + { + return getChild< Gaffer::Color3fPlug >( m_firstPlugIndex + 1 ); + } + + Gaffer::Color3fPlug* CsVisualiseVertexIdTool::cursorColourPlug() + { + return const_cast< Gaffer::Color3fPlug* >( + static_cast< CsVisualiseVertexIdTool const* >( this )->cursorColourPlug() ); + } + + Gaffer::Color3fPlug const* CsVisualiseVertexIdTool::cursorColourPlug() const + { + return getChild< Gaffer::Color3fPlug >( m_firstPlugIndex + 2 ); + } + + Gaffer::FloatPlug* CsVisualiseVertexIdTool::cursorRadiusPlug() + { + return const_cast< Gaffer::FloatPlug* >( + static_cast< CsVisualiseVertexIdTool const* >( this )->cursorRadiusPlug() ); + } + + Gaffer::FloatPlug const* CsVisualiseVertexIdTool::cursorRadiusPlug() const + { + return getChild< Gaffer::FloatPlug >( m_firstPlugIndex + 3 ); + } + + GafferScene::ScenePlug* CsVisualiseVertexIdTool::internalScenePlug() + { + return const_cast< GafferScene::ScenePlug* >( + static_cast< CsVisualiseVertexIdTool const* >( this )->internalScenePlug() ); + } + + GafferScene::ScenePlug const* CsVisualiseVertexIdTool::internalScenePlug() const + { + return getChild< GafferScene::ScenePlug >( m_firstPlugIndex + 4 ); + } + + std::vector< CsVisualiseVertexIdTool::Selection > const& CsVisualiseVertexIdTool::selection() const + { + return m_selection; + } + + Imath::V2f CsVisualiseVertexIdTool::cursorPos() const + { + return m_cursorPos; + } + + bool CsVisualiseVertexIdTool::cursorPosValid() const + { + return m_cursorPosValid; + } + + void CsVisualiseVertexIdTool::cursorVertexId( int const vertexId ) + { + m_cursorVertexId = vertexId; + } + + void CsVisualiseVertexIdTool::connectOnActive() + { + // NOTE : There are other handlers that will attempt to consume button and drag events + // so connect handlers at the front of button/drag signal handler queues. + + m_buttonPressConnection = sceneGadget()->buttonPressSignal(). +# if ( GAFFER_MILESTONE_VERSION > 0 || GAFFER_MAJOR_VERSION >= 62 ) + connectFront +# else + connect +# endif + ( boost::bind( & CsVisualiseVertexIdTool::buttonPress, this, boost::placeholders::_2 ) +# if ( GAFFER_MILESTONE_VERSION == 0 && GAFFER_MAJOR_VERSION < 62 ) + , boost::signals::at_front +# endif + ); + m_dragBeginConnection = sceneGadget()->dragBeginSignal(). +# if ( GAFFER_MILESTONE_VERSION > 0 || GAFFER_MAJOR_VERSION >= 62 ) + connectFront +# else + connect +# endif + ( boost::bind( & CsVisualiseVertexIdTool::dragBegin, this, boost::placeholders::_2 ) +# if ( GAFFER_MILESTONE_VERSION == 0 && GAFFER_MAJOR_VERSION < 62 ) + , boost::signals::at_front +# endif + ); + + m_preRenderConnection = view()->viewportGadget()->preRenderSignal().connect( + boost::bind( & CsVisualiseVertexIdTool::preRender, this ) ); + + // NOTE : redraw necessary to ensure value display updated. + + view()->viewportGadget()->renderRequestSignal()( view()->viewportGadget() ); + } + + void CsVisualiseVertexIdTool::disconnectOnInactive() + { + m_preRenderConnection.disconnect(); + m_buttonPressConnection.disconnect(); + m_dragBeginConnection.disconnect(); + } + +# if GAFFER_COMPATIBILITY_VERSION >= MAKE_GAFFER_COMPATIBILITY_VERSION( 1, 5 ) + void CsVisualiseVertexIdTool::contextChanged() + { + // Context changes can change the scene, which in turn + // dirties our selection. + selectedPathsChanged(); + } + + void CsVisualiseVertexIdTool::selectedPathsChanged() + { + m_selectionDirty = true; + m_gadgetDirty = true; + m_priorityPathsDirty = true; + } +# else + void CsVisualiseVertexIdTool::connectToViewContext() + { + m_contextChangedConnection = view()->getContext()->changedSignal().connect( + boost::bind( & CsVisualiseVertexIdTool::contextChanged, this, boost::placeholders::_2 ) ); + } + + void CsVisualiseVertexIdTool::contextChanged + ( + IECore::InternedString const& name + ) + { + if( GafferSceneUI::ContextAlgo::affectsSelectedPaths( name ) || + GafferSceneUI::ContextAlgo::affectsLastSelectedPath( name ) || + ! boost::starts_with( name.string(), "ui:" ) ) + { + m_selectionDirty = true; + m_gadgetDirty = true; + m_priorityPathsDirty = true; + } + } +# endif + + bool CsVisualiseVertexIdTool::mouseMove + ( + GafferUI::ButtonEvent const& event + ) + { + if( m_initiatedDrag ) + { + return false; + } + + updateCursorPos( event, true ); + + // NOTE : only schedule redraw if tool active + + if( activePlug()->getValue() ) + { + view()->viewportGadget()->renderRequestSignal()( view()->viewportGadget() ); + } + + return false; + } + + void CsVisualiseVertexIdTool::enter + ( + GafferUI::ButtonEvent const& event + ) + { + updateCursorPos( event, true ); + + // NOTE : only schedule redraw if tool active + + if( activePlug()->getValue() ) + { + view()->viewportGadget()->renderRequestSignal()( view()->viewportGadget() ); + } + } + + void CsVisualiseVertexIdTool::leave + ( + GafferUI::ButtonEvent const& event + ) + { + updateCursorPos( event, false ); + + // NOTE : only schedule redraw if tool active + + if( activePlug()->getValue() ) + { + view()->viewportGadget()->renderRequestSignal()( view()->viewportGadget() ); + } + } + + bool CsVisualiseVertexIdTool::keyPress + ( + GafferUI::KeyEvent const& event + ) + { + if( ! activePlug()->getValue() ) + { + return false; + } + + // allow user to scale text with +/- keys + + if( event.key == "Plus" || event.key == "Equal" ) + { + sizePlug()->setValue( sizePlug()->getValue() + g_textSizeInc ); + } + else + if( event.key == "Minus" || event.key == "Underscore" ) + { + sizePlug()->setValue( std::max( sizePlug()->getValue() - g_textSizeInc, g_textSizeMin ) ); + } + + return false; + } + + bool CsVisualiseVertexIdTool::buttonPress + ( + GafferUI::ButtonEvent const& event + ) + { + m_acceptedButtonPress = false; + m_initiatedDrag = false; + + if( ( event.button & GafferUI::ButtonEvent::Left ) ) + { + updateCursorValue(); + if( m_cursorValue ) + { + m_acceptedButtonPress = true; + return true; + } + } + + return false; + } + + bool CsVisualiseVertexIdTool::buttonRelease + ( + GafferUI::ButtonEvent const& event + ) + { + m_acceptedButtonPress = false; + m_initiatedDrag = false; + + return false; + } + + IECore::RunTimeTypedPtr + CsVisualiseVertexIdTool::dragBegin + ( + GafferUI::DragDropEvent const& event + ) + { + m_initiatedDrag = false; + + if( ! m_acceptedButtonPress ) + { + return IECore::RunTimeTypedPtr(); + } + + m_acceptedButtonPress = false; + + if( m_cursorValue ) + { + // NOTE : There is a possibility that the tool has become inactive since the button + // press event that triggered the drag was accepted, the cutoff point is the + // button press event, so any change to the active state after that does not + // affect an ongoing drag operation. We therefore always request a redraw + // here so that the displayed value is cleared. + + m_initiatedDrag = true; + m_cursorPosValid = false; + view()->viewportGadget()->renderRequestSignal()( view()->viewportGadget() ); + GafferUI::Pointer::setCurrent( "values" ); + } + + return m_cursorValue; + } + + bool CsVisualiseVertexIdTool::dragEnd + ( + GafferUI::DragDropEvent const& event + ) + { + if( ! m_initiatedDrag ) + { + return false; + } + + m_initiatedDrag = false; + updateCursorPos( event, true ); + GafferUI::Pointer::setCurrent( "" ); + return true; + } + + void CsVisualiseVertexIdTool::plugDirtied + ( + Gaffer::Plug const* const plug + ) + { + if( ( plug == activePlug() ) || + ( plug == internalScenePlug()->objectPlug() ) || + ( plug == internalScenePlug()->transformPlug() ) ) + { + m_selectionDirty = true; + m_gadgetDirty = true; + m_priorityPathsDirty = true; + } + else + if( ( plug == sizePlug() ) || + ( plug == colourPlug() ) || + ( plug == cursorColourPlug() ) || + ( plug == cursorRadiusPlug() ) ) + { + m_gadgetDirty = true; + view()->viewportGadget()->renderRequestSignal()( view()->viewportGadget() ); + } + + if( plug == activePlug() ) + { + if( activePlug()->getValue() ) + { + connectOnActive(); + } + else + { + disconnectOnInactive(); + m_gadget->setVisible( false ); + + sceneGadget()->setPriorityPaths( IECore::PathMatcher() ); + } + } + } + + void CsVisualiseVertexIdTool::metadataChanged + ( + IECore::InternedString const& key + ) + { + if( ! Gaffer::MetadataAlgo::readOnlyAffectedByChange( key ) ) + { + return; + } + + if( ! m_selectionDirty ) + { + m_selectionDirty = true; + } + + if( ! m_gadgetDirty ) + { + m_gadgetDirty = true; + view()->viewportGadget()->renderRequestSignal()( view()->viewportGadget() ); + } + } + + void CsVisualiseVertexIdTool::updateSelection() const + { + if( ! m_selectionDirty ) + { + return; + } + + m_selection.clear(); + m_selectionDirty = false; + + if( ! activePlug()->getValue() ) + { + return; + } + + GafferScene::ScenePlug const* scene = + internalScenePlug()->getInput< GafferScene::ScenePlug >(); + + if( !( scene ) || + !( scene = scene->getInput< GafferScene::ScenePlug >() ) ) + { + return; + } + +# if GAFFER_COMPATIBILITY_VERSION >= MAKE_GAFFER_COMPATIBILITY_VERSION( 1, 5 ) + IECore::PathMatcher const selectedPaths = + GafferSceneUI::ScriptNodeAlgo::getSelectedPaths( view()->scriptNode() ); +# else + IECore::PathMatcher const selectedPaths = + GafferSceneUI::ContextAlgo::getSelectedPaths( view()->getContext() ); +# endif + + if( selectedPaths.isEmpty() ) + { + return; + } + + for( IECore::PathMatcher::Iterator it = selectedPaths.begin(), + itEnd = selectedPaths.end(); it != itEnd; ++it ) + { +# if GAFFER_COMPATIBILITY_VERSION >= MAKE_GAFFER_COMPATIBILITY_VERSION( 1, 5 ) + m_selection.emplace_back( *( scene ), *it, *( view()->context() ) ); +# else + m_selection.emplace_back( *( scene ), *it, *( view()->getContext() ) ); +# endif + } + } + + void CsVisualiseVertexIdTool::preRender() + { + updateSelection(); + + if( m_priorityPathsDirty ) + { +# if GAFFER_COMPATIBILITY_VERSION >= MAKE_GAFFER_COMPATIBILITY_VERSION( 1, 5 ) + static_cast< GafferSceneUI::SceneGadget* >( view()->viewportGadget()->getPrimaryChild() ) + ->setPriorityPaths( ( m_selection.empty() ) + ? IECore::PathMatcher() + : GafferSceneUI::ScriptNodeAlgo::getSelectedPaths( view()->scriptNode() ) ); +# else + static_cast< GafferSceneUI::SceneGadget* >( view()->viewportGadget()->getPrimaryChild() ) + ->setPriorityPaths( ( m_selection.empty() ) + ? IECore::PathMatcher() + : GafferSceneUI::ContextAlgo::getSelectedPaths( view()->getContext() ) ); +# endif + m_priorityPathsDirty = false; + } + + if( m_selection.empty() ) + { + m_gadget->setVisible( false ); + return; + } + + m_gadget->setVisible( true ); + + if( m_gadgetDirty ) + { + m_gadgetDirty = false; + } + } + + void CsVisualiseVertexIdTool::updateCursorPos + ( + GafferUI::ButtonEvent const& event, + bool const valid + ) + { + // update cursor raster position + // + // NOTE : the cursor position is stored in raster space so it is free of camera + // transformations so we do not need to track camera changes. + + if( valid ) + { + assert( view() ); + assert( view()->viewportGadget() ); + + m_cursorPos = view()->viewportGadget()->gadgetToRasterSpace( event.line.p1, sceneGadget() ); + } + + m_cursorPosValid = valid; + } + + void CsVisualiseVertexIdTool::updateCursorValue() + { + IECore::DataPtr cursorValue = m_cursorValue; + m_cursorValue.reset(); + + // NOTE : cursor value invalid when cursor position is invalid (during drag or no cursor focus) + + if( ! m_cursorPosValid || m_cursorVertexId == -1 ) + { + return; + } + + // store cursor value + + IECore::IntDataPtr data = + IECore::runTimeCast< IECore::IntData >( cursorValue ); + if( ! data ) data.reset( new IECore::IntData() ); + data->writable() = m_cursorVertexId; + cursorValue = data; + + m_cursorValue = cursorValue; + } + + GafferSceneUI::SceneGadget* CsVisualiseVertexIdTool::sceneGadget() + { + return const_cast< GafferSceneUI::SceneGadget* >( + static_cast< CsVisualiseVertexIdTool const* >( this )->sceneGadget() ); + } + + GafferSceneUI::SceneGadget const* CsVisualiseVertexIdTool::sceneGadget() const + { + return IECore::runTimeCast< GafferSceneUI::SceneGadget const >( + view()->viewportGadget()->getPrimaryChild() ); + } + + CsVisualiseVertexIdTool::Selection::Selection + ( + GafferScene::ScenePlug const& scene, + GafferScene::ScenePlug::ScenePath const& path, + Gaffer::Context const& context + ) + : m_scene( & scene ) + , m_path( path ) + , m_context( & context ) + {} + + GafferScene::ScenePlug const& CsVisualiseVertexIdTool::Selection::scene() const + { + return *( m_scene ); + } + + GafferScene::ScenePlug::ScenePath const& CsVisualiseVertexIdTool::Selection::path() const + { + return m_path; + } + + Gaffer::Context const& CsVisualiseVertexIdTool::Selection::context() const + { + return *( m_context ); + } + +} // CSGafferUI diff --git a/contrib/visualisers/CsVisualiseVertexIdTool.h b/contrib/visualisers/CsVisualiseVertexIdTool.h new file mode 100644 index 00000000000..58a6e3009cf --- /dev/null +++ b/contrib/visualisers/CsVisualiseVertexIdTool.h @@ -0,0 +1,223 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2024, Cinesite VFX Ltd. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above +// copyright notice, this list of conditions and the following +// disclaimer. +// +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided with +// the distribution. +// +// * Neither the name of John Haddon nor the names of +// any other contributors to this software may be used to endorse or +// promote products derived from this software without specific prior +// written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +////////////////////////////////////////////////////////////////////////// + +#ifndef CSGAFFERUI_TOOLS_CSVISUALISEVERTEXIDTOOL_H +#define CSGAFFERUI_TOOLS_CSVISUALISEVERTEXIDTOOL_H + +#include "../../GafferTypeIds.h" + +#include +#include +#include +#include +#include + +#include + +namespace CSGafferUI +{ + /** + * @brief Tool that displays vertex ids of primitives in viewport as text. + */ + struct CsVisualiseVertexIdTool + : public GafferSceneUI::SelectionTool + { + /** + * @brief ctor + * @param view parent view + * @param name name for node + */ + explicit + CsVisualiseVertexIdTool + ( + GafferSceneUI::SceneView* view, + std::string const& name = Gaffer::GraphComponent::defaultName< CsVisualiseVertexIdTool >() + ); + + /** + * @brief dtor + */ + ~CsVisualiseVertexIdTool() override; + + /** + * @name GafferPlugAccessors + * @brief Gaffer plug accessor functions + * @{ + */ + + Gaffer::FloatPlug* sizePlug(); + Gaffer::FloatPlug const* sizePlug() const; + + Gaffer::Color3fPlug* colourPlug(); + Gaffer::Color3fPlug const* colourPlug() const; + + Gaffer::Color3fPlug* cursorColourPlug(); + Gaffer::Color3fPlug const* cursorColourPlug() const; + + Gaffer::FloatPlug* cursorRadiusPlug(); + Gaffer::FloatPlug const* cursorRadiusPlug() const; + + /** + * @} + */ + + GAFFER_NODE_DECLARE_TYPE( + CSGafferUI::CsVisualiseVertexIdTool, + CSInternalTypes::CsVisualiseVertexIdToolTypeId, + GafferSceneUI::SelectionTool ); + + /** + * @brief Class encapsulating a selected scene location + */ + struct Selection + { + /** + * @brief ctor + * @param scene scene + * @param path scene path + * @param context context + */ + Selection + ( + GafferScene::ScenePlug const& scene, + GafferScene::ScenePlug::ScenePath const& path, + Gaffer::Context const& context + ); + + /** + * @brief Get the scene + * @return scene + */ + GafferScene::ScenePlug const& scene() const; + + /** + * @brief Get the scene path + * @return scene path + */ + GafferScene::ScenePlug::ScenePath const& path() const; + + /** + * @brief Get the context + * @return context + */ + Gaffer::Context const& context() const; + + private: + + GafferScene::ConstScenePlugPtr m_scene; + GafferScene::ScenePlug::ScenePath m_path; + Gaffer::ConstContextPtr m_context; + }; + + /** + * @brief Get the current selection + * @return current selection + */ + std::vector< Selection > const& selection() const; + + /** + * @brief Get the cursor position in raster space + * @return cursor position in raster space + */ + Imath::V2f cursorPos() const; + + /** + * @brief Is the cursor position valid? + * @return true if cursor position is valid, otherwise false + */ + bool cursorPosValid() const; + + /** + * @brief Set the cursor vertex id + * @param cursor vertex id + */ + void cursorVertexId( int vertexId ); + + private: + + GafferScene::ScenePlug* internalScenePlug(); + GafferScene::ScenePlug const* internalScenePlug() const; + + void connectOnActive(); + void disconnectOnInactive(); + bool mouseMove( GafferUI::ButtonEvent const& event ); + void enter( GafferUI::ButtonEvent const& event ); + void leave( GafferUI::ButtonEvent const& event ); + bool keyPress( GafferUI::KeyEvent const& event ); + bool buttonPress( GafferUI::ButtonEvent const& event ); + bool buttonRelease( GafferUI::ButtonEvent const& event ); + IECore::RunTimeTypedPtr dragBegin( GafferUI::DragDropEvent const& event ); + bool dragEnd( GafferUI::DragDropEvent const& event ); + void plugDirtied( Gaffer::Plug const* plug ); + void metadataChanged( IECore::InternedString const& key ); + void updateSelection() const; + void preRender(); + void updateCursorPos( GafferUI::ButtonEvent const& event, bool valid ); + void updateCursorValue(); + GafferSceneUI::SceneGadget* sceneGadget(); + GafferSceneUI::SceneGadget const* sceneGadget() const; + +# if GAFFER_COMPATIBILITY_VERSION >= MAKE_GAFFER_COMPATIBILITY_VERSION( 1, 5 ) + void contextChanged(); + void selectedPathsChanged(); +# else + void connectToViewContext(); + void contextChanged( IECore::InternedString const& name ); + Gaffer::Signals::ScopedConnection m_contextChangedConnection; +# endif + Gaffer::Signals::ScopedConnection m_preRenderConnection; + Gaffer::Signals::ScopedConnection m_buttonPressConnection; + Gaffer::Signals::ScopedConnection m_dragBeginConnection; + + GafferUI::GadgetPtr m_gadget; + mutable std::vector< Selection > m_selection; + Imath::V2i m_cursorPos; + bool m_cursorPosValid; + IECore::DataPtr m_cursorValue; + int m_cursorVertexId; + bool m_gadgetDirty; + mutable bool m_selectionDirty; + bool m_priorityPathsDirty; + bool m_acceptedButtonPress; + bool m_initiatedDrag; + + static ToolDescription< CsVisualiseVertexIdTool, GafferSceneUI::SceneView > m_toolDescription; + static size_t m_firstPlugIndex; + }; + +} // CSGafferUI + +#endif // CSGAFFERUI_TOOLS_CSVISUALISEVERTEXIDTOOL_H From 468dd1b20d2cf00babf3c3904344d4dccda8ea5a Mon Sep 17 00:00:00 2001 From: Paul-George Roberts Date: Tue, 12 Nov 2024 10:27:28 -0500 Subject: [PATCH 2/2] Visualisers : Contribute UI --- .../visualisers/CsVisualiseOrientToolUI.py | 191 ++++++++++++++++ contrib/visualisers/CsVisualiseValueToolUI.py | 195 +++++++++++++++++ .../visualisers/CsVisualiseVectorToolUI.py | 203 ++++++++++++++++++ .../visualisers/CsVisualiseVertexIdToolUI.py | 179 +++++++++++++++ 4 files changed, 768 insertions(+) create mode 100644 contrib/visualisers/CsVisualiseOrientToolUI.py create mode 100644 contrib/visualisers/CsVisualiseValueToolUI.py create mode 100644 contrib/visualisers/CsVisualiseVectorToolUI.py create mode 100644 contrib/visualisers/CsVisualiseVertexIdToolUI.py diff --git a/contrib/visualisers/CsVisualiseOrientToolUI.py b/contrib/visualisers/CsVisualiseOrientToolUI.py new file mode 100644 index 00000000000..61c8a0ff2a4 --- /dev/null +++ b/contrib/visualisers/CsVisualiseOrientToolUI.py @@ -0,0 +1,191 @@ +########################################################################## +# +# Copyright (c) 2024, Cinesite VFX Ltd. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above +# copyright notice, this list of conditions and the following +# disclaimer. +# +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided with +# the distribution. +# +# * Neither the name of John Haddon nor the names of +# any other contributors to this software may be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +########################################################################## + +import Gaffer +import GafferUI + +from csgaffer.nodes import CsVisualiseOrientTool + +if CsVisualiseOrientTool is not None: + Gaffer.Metadata.registerNode( + CsVisualiseOrientTool, + "description", + """ + Tool for displaying named primitive variables of type Quatf as coordinate frame. + + Use keys (+/-) to change the scale of the displayed coordinate frame. + """, + "viewer:shortCut", + "O", + "viewer:shouldAutoActivate", + False, + "order", + 1003, + "tool:exclusive", + False, + "layout:activator:activatorFalse", + lambda node: False, + plugs={ + "active": ( + "boolPlugValueWidget:image", + "node_icons/tools/visualise_orient_data.png", + "layout:visibilityActivator", + "activatorFalse", + ), + "name": ( + "description", + """ + Specifies the name of the primitive variable to visualise. The data should + be of type Imath::Quatf. + """, + "layout:index", + 0, + "layout:section", + "Settings", + "label", + "Name", + ), + "scale": ( + "description", + """ + Scale factor applied to the orientation data visualisation. + """, + "layout:index", + 1, + "layout:section", + "Settings", + "label", + "Scale", + ), + "colourX": ( + "description", + """ + Colour applied to the orientation X axis visualisation. + """, + "layout:index", + 2, + "layout:section", + "Settings", + "label", + "Colour X", + ), + "colourY": ( + "description", + """ + Colour applied to the orientation Y axis visualisation. + """, + "layout:index", + 3, + "layout:section", + "Settings", + "label", + "Colour Y", + ), + "colourZ": ( + "description", + """ + Colour applied to the orientation Z axis visualisation. + """, + "layout:index", + 4, + "layout:section", + "Settings", + "label", + "Colour Z", + ), + }, + ) + + class _SettingsNodeUI(GafferUI.NodeUI): + def __init__(self, node, **kw): + self.__mainColumn = GafferUI.ListContainer( + GafferUI.ListContainer.Orientation.Vertical, spacing=4, borderWidth=4 + ) + + GafferUI.NodeUI.__init__(self, node, self.__mainColumn, **kw) + + with self.__mainColumn: + self.__plugLayout = GafferUI.PlugLayout(node, rootSection="Settings") + + def plugValueWidget(self, plug): + hierarchy = [] + while not plug.isSame(self.node()): + hierarchy.insert(0, plug) + plug = plug.parent() + + widget = self.__plugLayout.plugValueWidget(hierarchy[0]) + if widget is None: + return None + + for i in range(1, len(hierarchy)): + widget = widget.childPlugValueWidget(hierarchy[i]) + if widget is None: + return None + + return widget + + def setReadOnly(self, readOnly): + if readOnly == Gaffer.MetadataAlgo.getReadOnly(self.node()): + return + + Gaffer.NodeUI.setReadOnly(self, readOnly) + + self.__plugLayout.setReadOnly(readOnly) + + def __launchToolSettings(node, plugValueWidget): + w = GafferUI.Window(sizeMode=GafferUI.Window.SizeMode.Automatic) + w.setTitle("Tool Settings (%s)" % (CsVisualiseOrientTool.staticTypeName())) + w.setChild(GafferUI.NodeUI.create(node)) + plugValueWidget.ancestor(GafferUI.Window).addChildWindow(w, removeOnClose=True) + w.setVisible(True) + + def __plugPopupMenu(menuDefinition, plugValueWidget): + try: + plug = plugValueWidget.getPlug() + except: + pass + else: + node = plug.node() + if plug.getName() == "active" and isinstance(node, CsVisualiseOrientTool): + import functools + + menuDefinition.append("/Tool Settings Divider", {"divider": True}) + menuDefinition.append( + "/Tool Settings", {"command": functools.partial(__launchToolSettings, node, plugValueWidget)} + ) + + GafferUI.NodeUI.registerNodeUI(CsVisualiseOrientTool, _SettingsNodeUI) + GafferUI.PlugValueWidget.popupMenuSignal().connect(__plugPopupMenu, scoped=False) diff --git a/contrib/visualisers/CsVisualiseValueToolUI.py b/contrib/visualisers/CsVisualiseValueToolUI.py new file mode 100644 index 00000000000..e289dc37d30 --- /dev/null +++ b/contrib/visualisers/CsVisualiseValueToolUI.py @@ -0,0 +1,195 @@ +########################################################################## +# +# Copyright (c) 2024, Cinesite VFX Ltd. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above +# copyright notice, this list of conditions and the following +# disclaimer. +# +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided with +# the distribution. +# +# * Neither the name of John Haddon nor the names of +# any other contributors to this software may be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +########################################################################## + +import Gaffer +import GafferUI + +from csgaffer.nodes import CsVisualiseValueTool + +if CsVisualiseValueTool is not None: + Gaffer.Metadata.registerNode( + CsVisualiseValueTool, + "description", + """ + Tool for displaying named primitive variables of type float, V2f or V3f as a coloured overlay. + """, + "viewer:shortCut", + "S", + "viewer:shouldAutoActivate", + False, + "order", + 1001, + "tool:exclusive", + False, + "layout:activator:activatorFalse", + lambda node: False, + plugs={ + "active": ( + "boolPlugValueWidget:image", + "node_icons/tools/visualise_value_data.png", + "layout:visibilityActivator", + "activatorFalse", + ), + "name": ( + "description", + """ + Specifies the name of the primitive variable to visualise. The data should + be of type float, V2f or V3f. + """, + "layout:index", + 0, + "layout:section", + "Settings", + "label", + "Name", + ), + "valueMin": ( + "description", + """ + The minimum data channel value that will be mapped to 0. + + For float data only the first channel is used. For V2f data only the first + and second channels are used. For V3f data all three channels are used. + """, + "layout:index", + 1, + "layout:section", + "Settings", + "label", + "Min Value", + ), + "valueMax": ( + "description", + """ + The maximum data channel value that will be mapped to 1. + + For float data only the first channel is used. For V2f data only the first + and second channels are used. For V3f data all three channels are used. + """, + "layout:index", + 2, + "layout:section", + "Settings", + "label", + "Max Value", + ), + "size": ( + "description", + """ + Specifies the size of the displayed text. + """, + "layout:index", + 3, + "layout:section", + "Settings", + "label", + "Size", + ), + "colour": ( + "description", + """ + Specifies the colour of the displayed text. + """, + "layout:index", + 4, + "layout:section", + "Settings", + "label", + "Colour", + ), + }, + ) + + class _SettingsNodeUI(GafferUI.NodeUI): + def __init__(self, node, **kw): + self.__mainColumn = GafferUI.ListContainer( + GafferUI.ListContainer.Orientation.Vertical, spacing=4, borderWidth=4 + ) + + GafferUI.NodeUI.__init__(self, node, self.__mainColumn, **kw) + + with self.__mainColumn: + self.__plugLayout = GafferUI.PlugLayout(node, rootSection="Settings") + + def plugValueWidget(self, plug): + hierarchy = [] + while not plug.isSame(self.node()): + hierarchy.insert(0, plug) + plug = plug.parent() + + widget = self.__plugLayout.plugValueWidget(hierarchy[0]) + if widget is None: + return None + + for i in range(1, len(hierarchy)): + widget = widget.childPlugValueWidget(hierarchy[i]) + if widget is None: + return None + + return widget + + def setReadOnly(self, readOnly): + if readOnly == Gaffer.MetadataAlgo.getReadOnly(self.node()): + return + + Gaffer.NodeUI.setReadOnly(self, readOnly) + + self.__plugLayout.setReadOnly(readOnly) + + def __launchToolSettings(node, plugValueWidget): + w = GafferUI.Window(sizeMode=GafferUI.Window.SizeMode.Automatic) + w.setTitle("Tool Settings (%s)" % (CsVisualiseValueTool.staticTypeName())) + w.setChild(GafferUI.NodeUI.create(node)) + plugValueWidget.ancestor(GafferUI.Window).addChildWindow(w, removeOnClose=True) + w.setVisible(True) + + def __plugPopupMenu(menuDefinition, plugValueWidget): + try: + plug = plugValueWidget.getPlug() + except: + pass + else: + node = plug.node() + if plug.getName() == "active" and isinstance(node, CsVisualiseValueTool): + import functools + + menuDefinition.append("/Tool Settings Divider", {"divider": True}) + menuDefinition.append( + "/Tool Settings", {"command": functools.partial(__launchToolSettings, node, plugValueWidget)} + ) + + GafferUI.NodeUI.registerNodeUI(CsVisualiseValueTool, _SettingsNodeUI) + GafferUI.PlugValueWidget.popupMenuSignal().connect(__plugPopupMenu, scoped=False) diff --git a/contrib/visualisers/CsVisualiseVectorToolUI.py b/contrib/visualisers/CsVisualiseVectorToolUI.py new file mode 100644 index 00000000000..027c4488c7d --- /dev/null +++ b/contrib/visualisers/CsVisualiseVectorToolUI.py @@ -0,0 +1,203 @@ +########################################################################## +# +# Copyright (c) 2024, Cinesite VFX Ltd. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above +# copyright notice, this list of conditions and the following +# disclaimer. +# +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided with +# the distribution. +# +# * Neither the name of John Haddon nor the names of +# any other contributors to this software may be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +########################################################################## + +import Gaffer +import GafferUI + +from csgaffer.nodes import CsVisualiseVectorTool + +if CsVisualiseVectorTool is not None: + Gaffer.Metadata.registerNode( + CsVisualiseVectorTool, + "description", + """ + Tool for displaying named primitive variables of type V3f as line vectors. + + Use keys (+/-) to change the scale of the displayed line vectors. + """, + "viewer:shortCut", + "V", + "viewer:shouldAutoActivate", + False, + "order", + 1002, + "tool:exclusive", + False, + "layout:activator:activatorFalse", + lambda node: False, + "layout:activator:activatorScale", + lambda node: node["format"].getValue() != CsVisualiseVectorTool.Format.Point, + plugs={ + "active": ( + "boolPlugValueWidget:image", + "node_icons/tools/visualise_vector_data.png", + "layout:visibilityActivator", + "activatorFalse", + ), + "name": ( + "description", + """ + Specifies the name of the primitive variable to visualise. The data should + be of type Imath::V3f. + """, + "layout:index", + 0, + "layout:section", + "Settings", + "label", + "Name", + ), + "format": ( + "description", + """ + Format of data : + + Point - Interpret data as points. + + Vector - Interpret data as vectors. + + Bivector - Interpret data as bivectors (e.g. surface normals) so they remain + orthogonal to the plane containing the vectors whose cross product + they are the result of, under all affine transformations. + """, + "preset:Point", + CsVisualiseVectorTool.Format.Point, + "preset:Vector", + CsVisualiseVectorTool.Format.Vector, + "preset:Bivector", + CsVisualiseVectorTool.Format.Bivector, + "plugValueWidget:type", + "GafferUI.PresetsPlugValueWidget", + "layout:index", + 0, + "layout:accessory", + True, + "layout:width", + 100, + "layout:section", + "Settings", + "label", + "Format", + ), + "scale": ( + "description", + """ + Scale factor applied to the vector data visualisation. + """, + "layout:index", + 1, + "layout:section", + "Settings", + "layout:activator", + "activatorScale", + "label", + "Scale", + ), + "colour": ( + "description", + """ + Colour applied to the vector data visualisation. + """, + "layout:index", + 2, + "layout:section", + "Settings", + "label", + "Colour", + ), + }, + ) + + class _SettingsNodeUI(GafferUI.NodeUI): + def __init__(self, node, **kw): + self.__mainColumn = GafferUI.ListContainer( + GafferUI.ListContainer.Orientation.Vertical, spacing=4, borderWidth=4 + ) + + GafferUI.NodeUI.__init__(self, node, self.__mainColumn, **kw) + + with self.__mainColumn: + self.__plugLayout = GafferUI.PlugLayout(node, rootSection="Settings") + + def plugValueWidget(self, plug): + hierarchy = [] + while not plug.isSame(self.node()): + hierarchy.insert(0, plug) + plug = plug.parent() + + widget = self.__plugLayout.plugValueWidget(hierarchy[0]) + if widget is None: + return None + + for i in range(1, len(hierarchy)): + widget = widget.childPlugValueWidget(hierarchy[i]) + if widget is None: + return None + + return widget + + def setReadOnly(self, readOnly): + if readOnly == Gaffer.MetadataAlgo.getReadOnly(self.node()): + return + + Gaffer.NodeUI.setReadOnly(self, readOnly) + + self.__plugLayout.setReadOnly(readOnly) + + def __launchToolSettings(node, plugValueWidget): + w = GafferUI.Window(sizeMode=GafferUI.Window.SizeMode.Automatic) + w.setTitle("Tool Settings (%s)" % (CsVisualiseVectorTool.staticTypeName())) + w.setChild(GafferUI.NodeUI.create(node)) + plugValueWidget.ancestor(GafferUI.Window).addChildWindow(w, removeOnClose=True) + w.setVisible(True) + + def __plugPopupMenu(menuDefinition, plugValueWidget): + try: + plug = plugValueWidget.getPlug() + except: + pass + else: + node = plug.node() + if plug.getName() == "active" and isinstance(node, CsVisualiseVectorTool): + import functools + + menuDefinition.append("/Tool Settings Divider", {"divider": True}) + menuDefinition.append( + "/Tool Settings", {"command": functools.partial(__launchToolSettings, node, plugValueWidget)} + ) + + GafferUI.NodeUI.registerNodeUI(CsVisualiseVectorTool, _SettingsNodeUI) + GafferUI.PlugValueWidget.popupMenuSignal().connect(__plugPopupMenu, scoped=False) diff --git a/contrib/visualisers/CsVisualiseVertexIdToolUI.py b/contrib/visualisers/CsVisualiseVertexIdToolUI.py new file mode 100644 index 00000000000..7783882f99f --- /dev/null +++ b/contrib/visualisers/CsVisualiseVertexIdToolUI.py @@ -0,0 +1,179 @@ +########################################################################## +# +# Copyright (c) 2024, Cinesite VFX Ltd. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above +# copyright notice, this list of conditions and the following +# disclaimer. +# +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided with +# the distribution. +# +# * Neither the name of John Haddon nor the names of +# any other contributors to this software may be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +########################################################################## + +import Gaffer +import GafferUI + +from csgaffer.nodes import CsVisualiseVertexIdTool + +if CsVisualiseVertexIdTool is not None: + Gaffer.Metadata.registerNode( + CsVisualiseVertexIdTool, + "description", + """ + Tool for displaying the vertex ids of a primitive with a "P" primitive variable. + + Use keys (+/-) to change the size of the displayed text. + """, + "viewer:shortCut", + "U", + "viewer:shouldAutoActivate", + False, + "order", + 1004, + "tool:exclusive", + False, + "layout:activator:activatorFalse", + lambda node: False, + plugs={ + "active": ( + "boolPlugValueWidget:image", + "node_icons/tools/visualise_vertex_ids.png", + "layout:visibilityActivator", + "activatorFalse", + ), + "size": ( + "description", + """ + Specifies the size of the displayed text labels. + """, + "layout:index", + 0, + "layout:section", + "Settings", + "label", + "Text Size", + ), + "colour": ( + "description", + """ + Specifies the colour of the displayed text labels. + """, + "layout:index", + 1, + "layout:section", + "Settings", + "label", + "Text Colour", + ), + "cursorColour": ( + "description", + """ + Specifies the colour of the displayed cursor text label. + """, + "layout:index", + 2, + "layout:section", + "Settings", + "label", + "Cursor Text Colour", + ), + "cursorRadius": ( + "description", + """ + Specifies the search radius distance used to find the nearest vertex id to the cursor. + Set to zero to disable cursor vertex id search. + """, + "layout:index", + 3, + "layout:section", + "Settings", + "label", + "Cursor Search Radius", + ), + }, + ) + + class _SettingsNodeUI(GafferUI.NodeUI): + def __init__(self, node, **kw): + self.__mainColumn = GafferUI.ListContainer( + GafferUI.ListContainer.Orientation.Vertical, spacing=4, borderWidth=4 + ) + + GafferUI.NodeUI.__init__(self, node, self.__mainColumn, **kw) + + with self.__mainColumn: + self.__plugLayout = GafferUI.PlugLayout(node, rootSection="Settings") + + def plugValueWidget(self, plug): + hierarchy = [] + while not plug.isSame(self.node()): + hierarchy.insert(0, plug) + plug = plug.parent() + + widget = self.__plugLayout.plugValueWidget(hierarchy[0]) + if widget is None: + return None + + for i in range(1, len(hierarchy)): + widget = widget.childPlugValueWidget(hierarchy[i]) + if widget is None: + return None + + return widget + + def setReadOnly(self, readOnly): + if readOnly == Gaffer.MetadataAlgo.getReadOnly(self.node()): + return + + Gaffer.NodeUI.setReadOnly(readOnly) + + self.__plugLayout.setReadOnly(readOnly) + + def __launchToolSettings(node, plugValueWidget): + w = GafferUI.Window(sizeMode=GafferUI.Window.SizeMode.Automatic) + w.setTitle("Tool Settings (%s)" % (CsVisualiseVertexIdTool.staticTypeName())) + w.setChild(GafferUI.NodeUI.create(node)) + plugValueWidget.ancestor(GafferUI.Window).addChildWindow(w, removeOnClose=True) + w.setVisible(True) + + def __plugPopupMenu(menuDefinition, plugValueWidget): + try: + plug = plugValueWidget.getPlug() + except: + pass + else: + node = plug.node() + if plug.getName() == "active" and isinstance(node, CsVisualiseVertexIdTool): + import functools + + menuDefinition.append("/Tool Settings Divider", {"divider": True}) + menuDefinition.append( + "/Tool Settings", {"command": functools.partial(__launchToolSettings, node, plugValueWidget)} + ) + + GafferUI.NodeUI.registerNodeUI(CsVisualiseVertexIdTool, _SettingsNodeUI) + GafferUI.PlugValueWidget.popupMenuSignal().connect(__plugPopupMenu, scoped=False)