diff --git a/NifSkope.pro b/NifSkope.pro index 1c0bc5a69..a173dac0b 100644 --- a/NifSkope.pro +++ b/NifSkope.pro @@ -189,6 +189,7 @@ HEADERS += \ src/ui/widgets/refrbrowser.h \ src/ui/widgets/uvedit.h \ src/ui/widgets/valueedit.h \ + src/ui/widgets/vertexpaintwidget.h \ src/ui/widgets/xmlcheck.h \ src/ui/about_dialog.h \ src/ui/checkablemessagebox.h \ @@ -270,6 +271,7 @@ SOURCES += \ src/ui/widgets/refrbrowser.cpp \ src/ui/widgets/uvedit.cpp \ src/ui/widgets/valueedit.cpp \ + src/ui/widgets/vertexpaintwidget.cpp \ src/ui/widgets/xmlcheck.cpp \ src/ui/about_dialog.cpp \ src/ui/checkablemessagebox.cpp \ @@ -298,7 +300,8 @@ FORMS += \ src/ui/settingsgeneral.ui \ src/ui/settingsrender.ui \ src/ui/settingsresources.ui \ - src/ui/widgets/lightingwidget.ui + src/ui/widgets/lightingwidget.ui \ + src/ui/widgets/vertexpaintwidget.ui ############################### @@ -346,6 +349,9 @@ gli { } zlib { + macx { + DEFINES += Z_HAVE_UNISTD_H + } !*msvc*:QMAKE_CFLAGS += -isystem ../nifskope/lib/zlib !*msvc*:QMAKE_CXXFLAGS += -isystem ../nifskope/lib/zlib else:INCLUDEPATH += lib/zlib diff --git a/res/icon/paint-verts.png b/res/icon/paint-verts.png new file mode 100644 index 000000000..0274ebc68 Binary files /dev/null and b/res/icon/paint-verts.png differ diff --git a/res/nifskope.qrc b/res/nifskope.qrc index 5b60c8401..0347e3823 100644 --- a/res/nifskope.qrc +++ b/res/nifskope.qrc @@ -62,5 +62,6 @@ icon/skinned.png icon/collapse.png icon/expand.png + icon/paint-verts.png diff --git a/src/data/niftypes.h b/src/data/niftypes.h index eab32f167..d5cf0588d 100644 --- a/src/data/niftypes.h +++ b/src/data/niftypes.h @@ -432,11 +432,11 @@ class Vector3 QString toHtml() const { return tr( "X %1 Y %2 Z %3\nlength %4" ).arg( - NumOrMinMax( xyz[0] ), - NumOrMinMax( xyz[1] ), - NumOrMinMax( xyz[2] ), - QString::number( length() ) - ); + NumOrMinMax( xyz[0] ), + NumOrMinMax( xyz[1] ), + NumOrMinMax( xyz[2] ), + QString::number( length() ) + ); } protected: @@ -687,12 +687,12 @@ class Vector4 QString toHtml() const { return tr( "X %1 Y %2 Z %3 W %4\nlength %5" ).arg( - NumOrMinMax( xyzw[0] ), - NumOrMinMax( xyzw[1] ), - NumOrMinMax( xyzw[2] ), - NumOrMinMax( xyzw[3] ), - QString::number( length() ) - ); + NumOrMinMax( xyzw[0] ), + NumOrMinMax( xyzw[1] ), + NumOrMinMax( xyzw[2] ), + NumOrMinMax( xyzw[3] ), + QString::number( length() ) + ); } protected: @@ -748,10 +748,10 @@ class Quat void normalize () { float mag = ( - (wxyz[0] * wxyz[0]) - + (wxyz[1] * wxyz[1]) - + (wxyz[2] * wxyz[2]) - + (wxyz[3] * wxyz[3]) + (wxyz[0] * wxyz[0]) + + (wxyz[1] * wxyz[1]) + + (wxyz[2] * wxyz[2]) + + (wxyz[3] * wxyz[3]) ); wxyz[0] /= mag; wxyz[1] /= mag; @@ -833,11 +833,11 @@ class Quat QString toHtml() const { return tr( "W %1\nX %2\nY %3\nZ %4" ).arg( - NumOrMinMax( wxyz[0] ), - NumOrMinMax( wxyz[1] ), - NumOrMinMax( wxyz[2] ), - NumOrMinMax( wxyz[3] ) - ); + NumOrMinMax( wxyz[0] ), + NumOrMinMax( wxyz[1] ), + NumOrMinMax( wxyz[2] ), + NumOrMinMax( wxyz[3] ) + ); } protected: @@ -874,8 +874,8 @@ class Matrix for ( int r = 0; r < 3; r++ ) { for ( int c = 0; c < 3; c++ ) { m3.m[r][c] = m[r][0] * m2.m[0][c] - + m[r][1] * m2.m[1][c] - + m[r][2] * m2.m[2][c]; + + m[r][1] * m2.m[1][c] + + m[r][2] * m2.m[2][c]; } } @@ -968,9 +968,9 @@ class Matrix4 for ( int r = 0; r < 4; r++ ) { for ( int c = 0; c < 4; c++ ) { m3.m[r][c] = m[r][0] * m2.m[0][c] - + m[r][1] * m2.m[1][c] - + m[r][2] * m2.m[2][c] - + m[r][3] * m2.m[3][c]; + + m[r][1] * m2.m[1][c] + + m[r][2] * m2.m[2][c] + + m[r][3] * m2.m[3][c]; } } @@ -1352,6 +1352,16 @@ class Color4 return c; } + Color4 operator*( const Color4& o ) const + { + Color4 c( *this ); + c.rgba[0] *= o.rgba[0]; + c.rgba[1] *= o.rgba[1]; + c.rgba[2] *= o.rgba[2]; + c.rgba[3] *= o.rgba[3]; + return c; + } + //! Add-equals operator Color4 & operator+=( const Color4 & o ) { @@ -1452,6 +1462,7 @@ class Color4 friend class NifIStream; friend class NifOStream; + friend class ByteColor4; friend QDataStream & operator>>( QDataStream & ds, Color4 & c ); }; @@ -1461,6 +1472,15 @@ class ByteColor4 : public Color4 public: //! Default constructor ByteColor4() { rgba[0] = rgba[1] = rgba[2] = rgba[3] = 1.0; } + static ByteColor4 fromColor4(const Color4& c) + { + ByteColor4 o; + o.rgba[0] = c.rgba[0]; + o.rgba[1] = c.rgba[1]; + o.rgba[2] = c.rgba[2]; + o.rgba[3] = c.rgba[3]; + return o; + } }; @@ -1936,71 +1956,71 @@ struct DataStreamMetadata }; typedef enum { - F_UNKNOWN = 0x00000000, - F_INT8_1 = 0x00010101, - F_INT8_2 = 0x00020102, - F_INT8_3 = 0x00030103, - F_INT8_4 = 0x00040104, - F_UINT8_1 = 0x00010105, - F_UINT8_2 = 0x00020106, - F_UINT8_3 = 0x00030107, - F_UINT8_4 = 0x00040108, - F_NORMINT8_1 = 0x00010109, - F_NORMINT8_2 = 0x0002010A, - F_NORMINT8_3 = 0x0003010B, - F_NORMINT8_4 = 0x0004010C, - F_NORMUINT8_1 = 0x0001010D, - F_NORMUINT8_2 = 0x0002010E, - F_NORMUINT8_3 = 0x0003010F, - F_NORMUINT8_4 = 0x00040110, - F_INT16_1 = 0x00010211, - F_INT16_2 = 0x00020212, - F_INT16_3 = 0x00030213, - F_INT16_4 = 0x00040214, - F_UINT16_1 = 0x00010215, - F_UINT16_2 = 0x00020216, - F_UINT16_3 = 0x00030217, - F_UINT16_4 = 0x00040218, - F_NORMINT16_1 = 0x00010219, - F_NORMINT16_2 = 0x0002021A, - F_NORMINT16_3 = 0x0003021B, - F_NORMINT16_4 = 0x0004021C, - F_NORMUINT16_1 = 0x0001021D, - F_NORMUINT16_2 = 0x0002021E, - F_NORMUINT16_3 = 0x0003021F, - F_NORMUINT16_4 = 0x00040220, - F_INT32_1 = 0x00010421, - F_INT32_2 = 0x00020422, - F_INT32_3 = 0x00030423, - F_INT32_4 = 0x00040424, - F_UINT32_1 = 0x00010425, - F_UINT32_2 = 0x00020426, - F_UINT32_3 = 0x00030427, - F_UINT32_4 = 0x00040428, - F_NORMINT32_1 = 0x00010429, - F_NORMINT32_2 = 0x0002042A, - F_NORMINT32_3 = 0x0003042B, - F_NORMINT32_4 = 0x0004042C, - F_NORMUINT32_1 = 0x0001042D, - F_NORMUINT32_2 = 0x0002042E, - F_NORMUINT32_3 = 0x0003042F, - F_NORMUINT32_4 = 0x00040430, - F_FLOAT16_1 = 0x00010231, - F_FLOAT16_2 = 0x00020232, - F_FLOAT16_3 = 0x00030233, - F_FLOAT16_4 = 0x00040234, - F_FLOAT32_1 = 0x00010435, - F_FLOAT32_2 = 0x00020436, - F_FLOAT32_3 = 0x00030437, - F_FLOAT32_4 = 0x00040438, - F_UINT_10_10_10_L1 = 0x00010439, - F_NORMINT_10_10_10_L1 = 0x0001043A, - F_NORMINT_11_11_10 = 0x0001043B, - F_NORMUINT8_4_BGRA = 0x0004013C, - F_NORMINT_10_10_10_2 = 0x0001043D, - F_UINT_10_10_10_2 = 0x0001043E, - F_TYPE_COUNT = 63, - F_MAX_COMP_SIZE = 16 + F_UNKNOWN = 0x00000000, + F_INT8_1 = 0x00010101, + F_INT8_2 = 0x00020102, + F_INT8_3 = 0x00030103, + F_INT8_4 = 0x00040104, + F_UINT8_1 = 0x00010105, + F_UINT8_2 = 0x00020106, + F_UINT8_3 = 0x00030107, + F_UINT8_4 = 0x00040108, + F_NORMINT8_1 = 0x00010109, + F_NORMINT8_2 = 0x0002010A, + F_NORMINT8_3 = 0x0003010B, + F_NORMINT8_4 = 0x0004010C, + F_NORMUINT8_1 = 0x0001010D, + F_NORMUINT8_2 = 0x0002010E, + F_NORMUINT8_3 = 0x0003010F, + F_NORMUINT8_4 = 0x00040110, + F_INT16_1 = 0x00010211, + F_INT16_2 = 0x00020212, + F_INT16_3 = 0x00030213, + F_INT16_4 = 0x00040214, + F_UINT16_1 = 0x00010215, + F_UINT16_2 = 0x00020216, + F_UINT16_3 = 0x00030217, + F_UINT16_4 = 0x00040218, + F_NORMINT16_1 = 0x00010219, + F_NORMINT16_2 = 0x0002021A, + F_NORMINT16_3 = 0x0003021B, + F_NORMINT16_4 = 0x0004021C, + F_NORMUINT16_1 = 0x0001021D, + F_NORMUINT16_2 = 0x0002021E, + F_NORMUINT16_3 = 0x0003021F, + F_NORMUINT16_4 = 0x00040220, + F_INT32_1 = 0x00010421, + F_INT32_2 = 0x00020422, + F_INT32_3 = 0x00030423, + F_INT32_4 = 0x00040424, + F_UINT32_1 = 0x00010425, + F_UINT32_2 = 0x00020426, + F_UINT32_3 = 0x00030427, + F_UINT32_4 = 0x00040428, + F_NORMINT32_1 = 0x00010429, + F_NORMINT32_2 = 0x0002042A, + F_NORMINT32_3 = 0x0003042B, + F_NORMINT32_4 = 0x0004042C, + F_NORMUINT32_1 = 0x0001042D, + F_NORMUINT32_2 = 0x0002042E, + F_NORMUINT32_3 = 0x0003042F, + F_NORMUINT32_4 = 0x00040430, + F_FLOAT16_1 = 0x00010231, + F_FLOAT16_2 = 0x00020232, + F_FLOAT16_3 = 0x00030233, + F_FLOAT16_4 = 0x00040234, + F_FLOAT32_1 = 0x00010435, + F_FLOAT32_2 = 0x00020436, + F_FLOAT32_3 = 0x00030437, + F_FLOAT32_4 = 0x00040438, + F_UINT_10_10_10_L1 = 0x00010439, + F_NORMINT_10_10_10_L1 = 0x0001043A, + F_NORMINT_11_11_10 = 0x0001043B, + F_NORMUINT8_4_BGRA = 0x0004013C, + F_NORMINT_10_10_10_2 = 0x0001043D, + F_UINT_10_10_10_2 = 0x0001043E, + F_TYPE_COUNT = 63, + F_MAX_COMP_SIZE = 16 } DataStreamFormat; typedef enum diff --git a/src/gl/bsshape.cpp b/src/gl/bsshape.cpp index 7a1a43943..c31bc806c 100644 --- a/src/gl/bsshape.cpp +++ b/src/gl/bsshape.cpp @@ -9,7 +9,7 @@ BSShape::BSShape( Scene * s, const QModelIndex & b ) : Shape( s, b ) { - + } void BSShape::clear() @@ -222,8 +222,8 @@ void BSShape::transform() if ( !nif || !iBlock.isValid() ) { clear(); return; - } - + } + if ( updateData ) { updateData = false; } @@ -357,13 +357,13 @@ void BSShape::drawShapes( NodeList * secondPass, bool presort ) return; auto nif = static_cast(iBlock.model()); - + bool drawPolygons = true; if ( Node::SELECTING ) { - if ( scene->selMode & Scene::SelObject ) { + if ( scene->actionMode & Scene::Object ) { int s_nodeId = ID2COLORKEY( nodeId ); glColor4ubv( (GLubyte *)&s_nodeId ); } else { - glColor4f( 0, 0, 0, 1 ); + drawPolygons = false; } } @@ -384,85 +384,87 @@ void BSShape::drawShapes( NodeList * secondPass, bool presort ) glMultMatrix( viewTrans() ); } - // Render polygon fill slightly behind alpha transparency and wireframe - if ( !drawSecond ) { - glEnable( GL_POLYGON_OFFSET_FILL ); - glPolygonOffset( 1.0f, 2.0f ); - } - - glEnableClientState( GL_VERTEX_ARRAY ); - glVertexPointer( 3, GL_FLOAT, 0, transVerts.constData() ); + if (drawPolygons) { - if ( !Node::SELECTING ) { - glEnableClientState( GL_NORMAL_ARRAY ); - glNormalPointer( GL_FLOAT, 0, transNorms.constData() ); - - bool doVCs = (bssp && (bssp->getFlags2() & ShaderFlags::SLSF2_Vertex_Colors)); - // Always do vertex colors for FO4 if colors present - if ( nifVersion == 130 && hasVertexColors && colors.count() ) - doVCs = true; + // Render polygon fill slightly behind alpha transparency and wireframe + if ( !drawSecond ) { + glEnable( GL_POLYGON_OFFSET_FILL ); + glPolygonOffset( 1.0f, 2.0f ); + } - if ( transColors.count() && (scene->options & Scene::DoVertexColors) && doVCs ) { - glEnableClientState( GL_COLOR_ARRAY ); - glColorPointer( 4, GL_FLOAT, 0, transColors.constData() ); - } else if ( !hasVertexColors && (bslsp && bslsp->hasVertexColors) ) { - // Correctly blacken the mesh if SLSF2_Vertex_Colors is still on - // yet "Has Vertex Colors" is not. - glColor( Color3( 0.0f, 0.0f, 0.0f ) ); - } else { - glColor( Color3( 1.0f, 1.0f, 1.0f ) ); + glEnableClientState( GL_VERTEX_ARRAY ); + glVertexPointer( 3, GL_FLOAT, 0, transVerts.constData() ); + + if ( !Node::SELECTING ) { + glEnableClientState( GL_NORMAL_ARRAY ); + glNormalPointer( GL_FLOAT, 0, transNorms.constData() ); + + bool doVCs = (bssp && (bssp->getFlags2() & ShaderFlags::SLSF2_Vertex_Colors)); + // Always do vertex colors for FO4 if colors present + if ( nifVersion == 130 && hasVertexColors && colors.count() ) + doVCs = true; + + if ( transColors.count() && (scene->options & Scene::DoVertexColors) && doVCs ) { + glEnableClientState( GL_COLOR_ARRAY ); + glColorPointer( 4, GL_FLOAT, 0, transColors.constData() ); + } else if ( !hasVertexColors && (bslsp && bslsp->hasVertexColors) ) { + // Correctly blacken the mesh if SLSF2_Vertex_Colors is still on + // yet "Has Vertex Colors" is not. + glColor( Color3( 0.0f, 0.0f, 0.0f ) ); + } else { + glColor( Color3( 1.0f, 1.0f, 1.0f ) ); + } } - } - if ( !Node::SELECTING ) - shader = scene->renderer->setupProgram( this, shader ); - - if ( isDoubleSided ) { - glCullFace( GL_FRONT ); - glDrawElements( GL_TRIANGLES, triangles.count() * 3, GL_UNSIGNED_SHORT, triangles.constData() ); - glCullFace( GL_BACK ); - } - - if ( !isLOD ) { - glDrawElements( GL_TRIANGLES, triangles.count() * 3, GL_UNSIGNED_SHORT, triangles.constData() ); - } else if ( triangles.count() ) { - auto lod0 = nif->get( iBlock, "LOD0 Size" ); - auto lod1 = nif->get( iBlock, "LOD1 Size" ); - auto lod2 = nif->get( iBlock, "LOD2 Size" ); - - auto lod0tris = triangles.mid( 0, lod0 ); - auto lod1tris = triangles.mid( lod0, lod1 ); - auto lod2tris = triangles.mid( lod0 + lod1, lod2 ); - - // If Level2, render all - // If Level1, also render Level0 - switch ( scene->lodLevel ) { - case Scene::Level2: - if ( lod2tris.count() ) - glDrawElements( GL_TRIANGLES, lod2tris.count() * 3, GL_UNSIGNED_SHORT, lod2tris.constData() ); - case Scene::Level1: - if ( lod1tris.count() ) - glDrawElements( GL_TRIANGLES, lod1tris.count() * 3, GL_UNSIGNED_SHORT, lod1tris.constData() ); - case Scene::Level0: - default: - if ( lod0tris.count() ) - glDrawElements( GL_TRIANGLES, lod0tris.count() * 3, GL_UNSIGNED_SHORT, lod0tris.constData() ); - break; + if ( !Node::SELECTING ) + shader = scene->renderer->setupProgram( this, shader ); + + if ( isDoubleSided ) { + glCullFace( GL_FRONT ); + glDrawElements( GL_TRIANGLES, triangles.count() * 3, GL_UNSIGNED_SHORT, triangles.constData() ); + glCullFace( GL_BACK ); } - } - if ( !Node::SELECTING ) - scene->renderer->stopProgram(); + if ( !isLOD ) { + glDrawElements( GL_TRIANGLES, triangles.count() * 3, GL_UNSIGNED_SHORT, triangles.constData() ); + } else if ( triangles.count() ) { + auto lod0 = nif->get( iBlock, "LOD0 Size" ); + auto lod1 = nif->get( iBlock, "LOD1 Size" ); + auto lod2 = nif->get( iBlock, "LOD2 Size" ); + + auto lod0tris = triangles.mid( 0, lod0 ); + auto lod1tris = triangles.mid( lod0, lod1 ); + auto lod2tris = triangles.mid( lod0 + lod1, lod2 ); + + // If Level2, render all + // If Level1, also render Level0 + switch ( scene->lodLevel ) { + case Scene::Level2: + if ( lod2tris.count() ) + glDrawElements( GL_TRIANGLES, lod2tris.count() * 3, GL_UNSIGNED_SHORT, lod2tris.constData() ); + case Scene::Level1: + if ( lod1tris.count() ) + glDrawElements( GL_TRIANGLES, lod1tris.count() * 3, GL_UNSIGNED_SHORT, lod1tris.constData() ); + case Scene::Level0: + default: + if ( lod0tris.count() ) + glDrawElements( GL_TRIANGLES, lod0tris.count() * 3, GL_UNSIGNED_SHORT, lod0tris.constData() ); + break; + } + } - glDisableClientState( GL_VERTEX_ARRAY ); - glDisableClientState( GL_NORMAL_ARRAY ); - glDisableClientState( GL_COLOR_ARRAY ); + if ( !Node::SELECTING ) + scene->renderer->stopProgram(); - glDisable( GL_POLYGON_OFFSET_FILL ); + glDisableClientState( GL_VERTEX_ARRAY ); + glDisableClientState( GL_NORMAL_ARRAY ); + glDisableClientState( GL_COLOR_ARRAY ); + glDisable( GL_POLYGON_OFFSET_FILL ); + } - if ( scene->selMode & Scene::SelVertex ) { + if ( scene->actionMode & Scene::Vertex ) { drawVerts(); } @@ -515,7 +517,7 @@ void BSShape::drawSelection() const if ( scene->options & Scene::ShowNodes ) Node::drawSelection(); - if ( isHidden() || !(scene->selMode & Scene::SelObject) ) + if ( isHidden() || !(scene->actionMode & Scene::Object) ) return; auto idx = scene->currentIndex; @@ -545,7 +547,7 @@ void BSShape::drawSelection() const // Parent index auto pBlock = nif->getBlock( nif->getParent( blk ) ); - auto push = [this] ( const Transform & t ) { + auto push = [this] ( const Transform & t ) { if ( transformRigid ) { glPushMatrix(); glMultMatrix( t ); @@ -591,7 +593,7 @@ void BSShape::drawSelection() const if ( normalScale < 0.1f ) normalScale = 0.1f; - + // Draw All Verts lambda auto allv = [this]( float size ) { @@ -611,7 +613,7 @@ void BSShape::drawSelection() const drawSphereSimple( sph.center, sph.radius, 72 ); } } - + if ( blockName.startsWith( "BSPackedCombined" ) && pBlock == iBlock ) { QVector idxs; if ( n == "Bounding Sphere" ) { @@ -699,8 +701,8 @@ void BSShape::drawSelection() const glVertex( transVerts.value( s ) ); glEnd(); } - } - + } + glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); // Draw Lines lambda @@ -732,7 +734,7 @@ void BSShape::drawSelection() const glLineWidth( lineWidth ); } }; - + // Draw Normals if ( n.contains( "Normal" ) ) { lines( transNorms ); @@ -766,7 +768,7 @@ void BSShape::drawSelection() const int s; QVector cols = { { 255, 0, 0, 128 }, { 0, 255, 0, 128 }, { 0, 0, 255, 128 }, { 255, 255, 0, 128 }, - { 0, 255, 255, 128 }, { 255, 0, 255, 128 }, { 255, 255, 255, 128 } + { 0, 255, 255, 128 }, { 255, 0, 255, 128 }, { 255, 255, 255, 128 } }; glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); @@ -838,7 +840,7 @@ void BSShape::drawSelection() const for ( int i = off; i < cnt + off; i++ ) { if ( i >= maxTris ) continue; - + Triangle tri = triangles[i]; glBegin( GL_TRIANGLES ); glVertex( transVerts.value( tri.v1() ) ); diff --git a/src/gl/glcontroller.cpp b/src/gl/glcontroller.cpp index 2c84ef2ff..44dc07460 100644 --- a/src/gl/glcontroller.cpp +++ b/src/gl/glcontroller.cpp @@ -344,7 +344,7 @@ bool Controller::timeIndex( float time, const NifModel * nif, const QModelIndex // Previously, this branch was causing x to decrement from 1.0. // (This works fine for linear interpolation apparently) x = 1.0 - x; - + // Swap I and J // With x inverted, we must swap I and J or the animation will reverse. auto tmpI = i; @@ -379,20 +379,34 @@ template bool interpolate( T & value, const QModelIndex & array, fl T v2 = nif->get( frames.child( next, 0 ), "Value" ); switch ( nif->get( array, "Interpolation" ) ) { - + case 2: { // Quadratic /* - In general, for keyframe values v1 = 0, v2 = 1 it appears that - setting v1's corresponding "Backward" value to 1 and v2's - corresponding "Forward" to 1 results in a linear interpolation. + Interpolate X, Y, and Z values independently, along a segment between + two points, at an arbitrary time "x", using an Hermite Cubic spline. + * The "v1" keyframe is the one previous in time to "x", + * The "v2" keyframe is the next one, + * The segment's backward derivative, "tangent 1" is stored as part of "v1" + * The segment's forward derivative, "tangent 2" is stored as part of "v2" + N.B. v2's backward derivate does not "belong" to the segment with v1, + but rather to the segment with v3. Otherwise said, the key at t=0.0 + can have a forward derivative, but it will not be used; the final key's + backward derivative will also not be used. The segment between time 1 and 2 + uses time 1's backward derivative and 2's forward derivative, not the 1's + forward derivative and 2's backward derivative. + + The XYZ vectors used in the derivatives, if directed from V1 to V2, can appear + as linear interpolation, however if their magnitude is not correct, then + temporally there will be a speed up/down, even if the spacial trajectory is + a straight line. */ // Tangent 1 - float t1 = nif->get( frames.child( last, 0 ), "Backward" ); + T t1 = nif->get( frames.child( last, 0 ), "Backward" ); // Tangent 2 - float t2 = nif->get( frames.child( next, 0 ), "Forward" ); + T t2 = nif->get( frames.child( next, 0 ), "Forward" ); float x2 = x * x; float x3 = x2 * x; @@ -403,7 +417,7 @@ template bool interpolate( T & value, const QModelIndex & array, fl value = v1 * (2.0f * x3 - 3.0f * x2 + 1.0f) + v2 * (-2.0f * x3 + 3.0f * x2) + t1 * (x3 - 2.0f * x2 + x) + t2 * (x3 - x2); } return true; - + case 5: // Constant if ( x < 0.5 ) @@ -504,8 +518,8 @@ template <> bool Controller::interpolate( Matrix & value, const QModelIndex & ar float a = acos( Quat::dotproduct( v1, v2 ) ); if ( fabs( a ) >= 0.00005 ) { - float i = 1.0 / sin( a ); - v4 = v1 * sin( ( 1.0 - x ) * a ) * i + v2 * sin( x * a ) * i; + float i = 1.0 / sin( a ); + v4 = v1 * sin( ( 1.0 - x ) * a ) * i + v2 * sin( x * a ) * i; } */ value.fromQuat( v3 ); @@ -633,7 +647,7 @@ static float blend( int k, int t, int * u, float v ) value = (v - u[k]) / (u[k + t - 1] - u[k]) * blend( k, t - 1, u, v ); else value = ( v - u[k] ) / ( u[k + t - 1] - u[k] ) * blend( k, t - 1, u, v ) - + ( u[k + t] - v ) / ( u[k + t] - u[k + 1] ) * blend( k + 1, t - 1, u, v ); + + ( u[k + t] - v ) / ( u[k + t] - u[k + 1] ) * blend( k + 1, t - 1, u, v ); } return value; diff --git a/src/gl/glmesh.cpp b/src/gl/glmesh.cpp index 794cb7359..a99573c6f 100644 --- a/src/gl/glmesh.cpp +++ b/src/gl/glmesh.cpp @@ -113,7 +113,7 @@ void Shape::update( const NifModel * nif, const QModelIndex & index ) Node::update( nif, index ); // If shaders are reenabled, reset - if ( !(scene->options & Scene::DisableShaders) && shader.isNull() + if ( !(scene->options & Scene::DisableShaders) && shader.isNull() && nif->checkVersion( 0x14020007, 0x14020007 ) ) { updateShaderProperties( nif ); @@ -289,10 +289,10 @@ QModelIndex Mesh::vertexAt( int idx ) const bool Mesh::isHidden() const { return ( Node::isHidden() - || ( !(scene->options & Scene::ShowHidden) /*&& Options::onlyTextured()*/ - && !properties.get() - && !properties.get() - ) + || ( !(scene->options & Scene::ShowHidden) /*&& Options::onlyTextured()*/ + && !properties.get() + && !properties.get() + ) ); } @@ -327,7 +327,7 @@ void Mesh::transform() weights.clear(); triangles.clear(); - + // All the semantics used by this mesh NiMesh::SemanticFlags semFlags = NiMesh::HAS_NONE; @@ -391,7 +391,7 @@ void Mesh::transform() // The Nth component after ignoring DataStreamUsage > 1 int compIdx = 0; for ( int i = 0; i < nif->rowCount( iData ); i++ ) { - // TODO: For now, submeshes are not actually used and the regions are + // TODO: For now, submeshes are not actually used and the regions are // filled in order for each data stream. // Submeshes may be required if total index values exceed USHRT_MAX QMap submeshMap; @@ -992,13 +992,14 @@ void Mesh::drawShapes( NodeList * secondPass, bool presort ) return; auto nif = static_cast(iBlock.model()); - + bool drawPolygons = true; + if ( Node::SELECTING ) { - if ( scene->selMode & Scene::SelObject ) { + if ( scene->actionMode & Scene::Object ) { int s_nodeId = ID2COLORKEY( nodeId ); glColor4ubv( (GLubyte *)&s_nodeId ); } else { - glColor4f( 0, 0, 0, 1 ); + drawPolygons = false; } } @@ -1035,97 +1036,100 @@ void Mesh::drawShapes( NodeList * secondPass, bool presort ) // setup array pointers - // Render polygon fill slightly behind alpha transparency and wireframe - glEnable( GL_POLYGON_OFFSET_FILL ); - glPolygonOffset( 1.0f, 2.0f ); + if (drawPolygons) { - glEnableClientState( GL_VERTEX_ARRAY ); - glVertexPointer( 3, GL_FLOAT, 0, transVerts.constData() ); + // Render polygon fill slightly behind alpha transparency and wireframe + glEnable( GL_POLYGON_OFFSET_FILL ); + glPolygonOffset( 1.0f, 2.0f ); - if ( !Node::SELECTING ) { - if ( transNorms.count() ) { - glEnableClientState( GL_NORMAL_ARRAY ); - glNormalPointer( GL_FLOAT, 0, transNorms.constData() ); - } + glEnableClientState( GL_VERTEX_ARRAY ); + glVertexPointer( 3, GL_FLOAT, 0, transVerts.constData() ); - // Do VCs if legacy or if either bslsp or bsesp is set - bool doVCs = (!bssp) || (bssp && (bssp->getFlags2() & ShaderFlags::SLSF2_Vertex_Colors)); + if ( !Node::SELECTING ) { + if ( transNorms.count() ) { + glEnableClientState( GL_NORMAL_ARRAY ); + glNormalPointer( GL_FLOAT, 0, transNorms.constData() ); + } - if ( transColors.count() - && ( scene->options & Scene::DoVertexColors ) - && doVCs ) - { - glEnableClientState( GL_COLOR_ARRAY ); - glColorPointer( 4, GL_FLOAT, 0, transColors.constData() ); - } else { - if ( !hasVertexColors && (bslsp && bslsp->hasVertexColors) ) { - // Correctly blacken the mesh if SLSF2_Vertex_Colors is still on - // yet "Has Vertex Colors" is not. - glColor( Color3( 0.0f, 0.0f, 0.0f ) ); + // Do VCs if legacy or if either bslsp or bsesp is set + bool doVCs = (!bssp) || (bssp && (bssp->getFlags2() & ShaderFlags::SLSF2_Vertex_Colors)); + + if ( transColors.count() + && ( scene->options & Scene::DoVertexColors ) + && doVCs ) + { + glEnableClientState( GL_COLOR_ARRAY ); + glColorPointer( 4, GL_FLOAT, 0, transColors.constData() ); } else { - glColor( Color3( 1.0f, 1.0f, 1.0f ) ); + if ( !hasVertexColors && (bslsp && bslsp->hasVertexColors) ) { + // Correctly blacken the mesh if SLSF2_Vertex_Colors is still on + // yet "Has Vertex Colors" is not. + glColor( Color3( 0.0f, 0.0f, 0.0f ) ); + } else { + glColor( Color3( 1.0f, 1.0f, 1.0f ) ); + } } } - } - // TODO: Hotspot. See about optimizing this. - if ( !Node::SELECTING ) - shader = scene->renderer->setupProgram( this, shader ); + // TODO: Hotspot. See about optimizing this. + if ( !Node::SELECTING ) + shader = scene->renderer->setupProgram( this, shader ); - if ( isDoubleSided ) { - glDisable( GL_CULL_FACE ); - } + if ( isDoubleSided ) { + glDisable( GL_CULL_FACE ); + } - if ( !isLOD ) { - // render the triangles - if ( sortedTriangles.count() ) - glDrawElements( GL_TRIANGLES, sortedTriangles.count() * 3, GL_UNSIGNED_SHORT, sortedTriangles.constData() ); - - } else if ( sortedTriangles.count() ) { - auto lod0 = nif->get( iBlock, "LOD0 Size" ); - auto lod1 = nif->get( iBlock, "LOD1 Size" ); - auto lod2 = nif->get( iBlock, "LOD2 Size" ); - - auto lod0tris = sortedTriangles.mid( 0, lod0 ); - auto lod1tris = sortedTriangles.mid( lod0, lod1 ); - auto lod2tris = sortedTriangles.mid( lod0 + lod1, lod2 ); - - // If Level2, render all - // If Level1, also render Level0 - switch ( scene->lodLevel ) { - case Scene::Level2: - if ( lod2tris.count() ) - glDrawElements( GL_TRIANGLES, lod2tris.count() * 3, GL_UNSIGNED_SHORT, lod2tris.constData() ); - case Scene::Level1: - if ( lod1tris.count() ) - glDrawElements( GL_TRIANGLES, lod1tris.count() * 3, GL_UNSIGNED_SHORT, lod1tris.constData() ); - case Scene::Level0: - default: - if ( lod0tris.count() ) - glDrawElements( GL_TRIANGLES, lod0tris.count() * 3, GL_UNSIGNED_SHORT, lod0tris.constData() ); - break; + if ( !isLOD ) { + // render the triangles + if ( sortedTriangles.count() ) + glDrawElements( GL_TRIANGLES, sortedTriangles.count() * 3, GL_UNSIGNED_SHORT, sortedTriangles.constData() ); + + } else if ( sortedTriangles.count() ) { + auto lod0 = nif->get( iBlock, "LOD0 Size" ); + auto lod1 = nif->get( iBlock, "LOD1 Size" ); + auto lod2 = nif->get( iBlock, "LOD2 Size" ); + + auto lod0tris = sortedTriangles.mid( 0, lod0 ); + auto lod1tris = sortedTriangles.mid( lod0, lod1 ); + auto lod2tris = sortedTriangles.mid( lod0 + lod1, lod2 ); + + // If Level2, render all + // If Level1, also render Level0 + switch ( scene->lodLevel ) { + case Scene::Level2: + if ( lod2tris.count() ) + glDrawElements( GL_TRIANGLES, lod2tris.count() * 3, GL_UNSIGNED_SHORT, lod2tris.constData() ); + case Scene::Level1: + if ( lod1tris.count() ) + glDrawElements( GL_TRIANGLES, lod1tris.count() * 3, GL_UNSIGNED_SHORT, lod1tris.constData() ); + case Scene::Level0: + default: + if ( lod0tris.count() ) + glDrawElements( GL_TRIANGLES, lod0tris.count() * 3, GL_UNSIGNED_SHORT, lod0tris.constData() ); + break; + } } - } - // render the tristrips - for ( auto & s : tristrips ) - glDrawElements( GL_TRIANGLE_STRIP, s.count(), GL_UNSIGNED_SHORT, s.constData() ); + // render the tristrips + for ( auto & s : tristrips ) + glDrawElements( GL_TRIANGLE_STRIP, s.count(), GL_UNSIGNED_SHORT, s.constData() ); - if ( isDoubleSided ) { - glEnable( GL_CULL_FACE ); - } + if ( isDoubleSided ) { + glEnable( GL_CULL_FACE ); + } - if ( !Node::SELECTING ) - scene->renderer->stopProgram(); + if ( !Node::SELECTING ) + scene->renderer->stopProgram(); - glDisableClientState( GL_VERTEX_ARRAY ); - glDisableClientState( GL_NORMAL_ARRAY ); - glDisableClientState( GL_COLOR_ARRAY ); + glDisableClientState( GL_VERTEX_ARRAY ); + glDisableClientState( GL_NORMAL_ARRAY ); + glDisableClientState( GL_COLOR_ARRAY ); - glDisable( GL_POLYGON_OFFSET_FILL ); + glDisable( GL_POLYGON_OFFSET_FILL ); + } glPointSize( 8.5 ); - if ( scene->selMode & Scene::SelVertex ) { + if ( scene->actionMode & Scene::Vertex ) { drawVerts(); } @@ -1165,7 +1169,7 @@ void Mesh::drawSelection() const if ( scene->options & Scene::ShowNodes ) Node::drawSelection(); - if ( isHidden() || !(scene->selMode & Scene::SelObject) ) + if ( isHidden() || !(scene->actionMode & Scene::Object) ) return; auto idx = scene->currentIndex; @@ -1176,7 +1180,7 @@ void Mesh::drawSelection() const return; if ( blk != iBlock && blk != iData && blk != iSkinPart && blk != iSkinData - && ( !iTangentData.isValid() || blk != iTangentData ) ) + && ( !iTangentData.isValid() || blk != iTangentData ) ) { return; } @@ -1226,7 +1230,7 @@ void Mesh::drawSelection() const glPolygonMode( GL_FRONT_AND_BACK, GL_POINT ); if ( n == "Vertices" || n == "Normals" || n == "Vertex Colors" - || n == "UV Sets" || n == "Tangents" || n == "Bitangents" ) + || n == "UV Sets" || n == "Tangents" || n == "Bitangents" ) { glBegin( GL_POINTS ); diff --git a/src/gl/glnode.cpp b/src/gl/glnode.cpp index d9cbb9da3..5cb22ed03 100644 --- a/src/gl/glnode.cpp +++ b/src/gl/glnode.cpp @@ -530,7 +530,7 @@ void Node::draw() if ( isHidden() || iBlock == scene->currentBlock ) return; - if ( !(scene->selMode & Scene::SelObject) ) + if ( !(scene->actionMode & Scene::Object) ) return; if ( Node::SELECTING ) { @@ -587,7 +587,7 @@ void Node::drawSelection() const if ( !nif ) return; - if ( !(scene->selMode & Scene::SelObject) ) + if ( !(scene->actionMode & Scene::Object) ) return; bool extraData = false; @@ -776,7 +776,7 @@ void drawHvkShape( const NifModel * nif, const QModelIndex & iShape, QStackselMode & Scene::SelObject) ) + if ( !(scene->actionMode & Scene::Object) ) return; stack.push( iShape ); @@ -1079,7 +1079,7 @@ void drawHvkConstraint( const NifModel * nif, const QModelIndex & iConstraint, c if ( !( nif && iConstraint.isValid() && scene && (scene->options & Scene::ShowConstraints) ) ) return; - if ( !(scene->selMode & Scene::SelObject) ) + if ( !(scene->actionMode & Scene::Object) ) return; QList tBodies; @@ -1218,13 +1218,13 @@ void drawHvkConstraint( const NifModel * nif, const QModelIndex & iConstraint, c if ( nif->checkVersion( 0, 0x14000002 ) ) { - Vector3 axleB1temp( axleB[1], axleB[2], axleB[0] ); - Vector3 axleB2temp( Vector3::crossproduct( axleB, axleB1temp ) ); + Vector3 axleB1temp( axleB[1], axleB[2], axleB[0] ); + Vector3 axleB2temp( Vector3::crossproduct( axleB, axleB1temp ) ); } else if ( nif->checkVersion( 0x14020007, 0 ) ) { - Vector3 axleB1temp( nif->get( iHinge, "Perp2 Axle In B1" ) ); - Vector3 axleB2temp( nif->get( iHinge, "Perp2 Axle In B2" ) ); + Vector3 axleB1temp( nif->get( iHinge, "Perp2 Axle In B1" ) ); + Vector3 axleB2temp( nif->get( iHinge, "Perp2 Axle In B2" ) ); } const Vector3 axleB1( axleB1temp ); @@ -1396,7 +1396,7 @@ void drawHvkConstraint( const NifModel * nif, const QModelIndex & iConstraint, c void Node::drawHavok() { - if ( !(scene->selMode & Scene::SelObject) ) + if ( !(scene->actionMode & Scene::Object) ) return; // TODO: Why are all these here - "drawNodes", "drawFurn", "drawHavok"? @@ -1482,7 +1482,7 @@ void Node::drawHavok() t.translation = center; glMultMatrix( t ); } - + if ( Node::SELECTING ) { int s_nodeId = ID2COLORKEY( nif->getBlockNumber( iBSMultiBoundData ) ); glColor4ubv( (GLubyte *)&s_nodeId ); @@ -1578,8 +1578,8 @@ void Node::drawHavok() if ( scene->currentBlock == nif->getBlock( nif->getLink( iBody, "Shape" ) ) ) { // fix: add selected visual to havok meshes glHighlightColor(); // TODO: idea: I do not recommend mimicking the Open GL API - // It confuses the one who reads the code. And the Open GL API is - // in constant development. + // It confuses the one who reads the code. And the Open GL API is + // in constant development. glLineWidth( 2.5 ); //glPointSize( 8.5 ); } @@ -1810,7 +1810,7 @@ void Node::drawFurn() if ( !( iBlock.isValid() && nif ) ) return; - if ( !(scene->selMode & Scene::SelObject) ) + if ( !(scene->actionMode & Scene::Object) ) return; QModelIndex iExtraDataList = nif->getIndex( iBlock, "Extra Data List" ); @@ -1872,7 +1872,7 @@ void Node::drawShapes( NodeList * secondPass, bool presort ) return; const NifModel * nif = static_cast(iBlock.model()); - + // BSOrderedNode support // Only set if true (|=) so that it propagates to all children presort |= nif->getBlock( iBlock, "BSOrderedNode" ).isValid(); @@ -1893,11 +1893,11 @@ QString trans2string( Transform t ) float xr, yr, zr; t.rotation.toEuler( xr, yr, zr ); return QString( "translation X %1, Y %2, Z %3\n" ).Farg( t.translation[0] ).Farg( t.translation[1] ).Farg( t.translation[2] ) - + QString( "rotation Y %1, P %2, R %3 " ).Farg( xr * 180 / PI ).Farg( yr * 180 / PI ).Farg( zr * 180 / PI ) - + QString( "( (%1, %2, %3), " ).Farg( t.rotation( 0, 0 ) ).Farg( t.rotation( 0, 1 ) ).Farg( t.rotation( 0, 2 ) ) - + QString( "(%1, %2, %3), " ).Farg( t.rotation( 1, 0 ) ).Farg( t.rotation( 1, 1 ) ).Farg( t.rotation( 1, 2 ) ) - + QString( "(%1, %2, %3) )\n" ).Farg( t.rotation( 2, 0 ) ).Farg( t.rotation( 2, 1 ) ).Farg( t.rotation( 2, 2 ) ) - + QString( "scale %1\n" ).Farg( t.scale ); + + QString( "rotation Y %1, P %2, R %3 " ).Farg( xr * 180 / PI ).Farg( yr * 180 / PI ).Farg( zr * 180 / PI ) + + QString( "( (%1, %2, %3), " ).Farg( t.rotation( 0, 0 ) ).Farg( t.rotation( 0, 1 ) ).Farg( t.rotation( 0, 2 ) ) + + QString( "(%1, %2, %3), " ).Farg( t.rotation( 1, 0 ) ).Farg( t.rotation( 1, 1 ) ).Farg( t.rotation( 1, 2 ) ) + + QString( "(%1, %2, %3) )\n" ).Farg( t.rotation( 2, 0 ) ).Farg( t.rotation( 2, 1 ) ).Farg( t.rotation( 2, 2 ) ) + + QString( "scale %1\n" ).Farg( t.scale ); } QString Node::textStats() const @@ -1989,7 +1989,7 @@ void LODNode::update( const NifModel * nif, const QModelIndex & index ) if ( iLevels.isValid() ) { for ( int r = 0; r < nif->rowCount( iLevels ); r++ ) { ranges.append( { nif->get( iLevels.child( r, 0 ), "Near Extent" ), - nif->get( iLevels.child( r, 0 ), "Far Extent" ) } + nif->get( iLevels.child( r, 0 ), "Far Extent" ) } ); } } diff --git a/src/gl/glscene.cpp b/src/gl/glscene.cpp index 265d07a1e..82c8539b8 100644 --- a/src/gl/glscene.cpp +++ b/src/gl/glscene.cpp @@ -68,7 +68,7 @@ Scene::Scene( TexCache * texcache, QOpenGLContext * context, QOpenGLFunctions * visMode = VisNone; - selMode = SelObject; + actionMode = ( Select | Object ); // Startup Defaults @@ -197,7 +197,7 @@ void Scene::updateSelectMode( QAction * action ) if ( !action ) return; - selMode = SelMode( action->data().toInt() ); + actionMode = ActionMode( action->data().toInt() ); emit sceneUpdated(); } diff --git a/src/gl/glscene.h b/src/gl/glscene.h index 4adab9b3b..da27eb45b 100644 --- a/src/gl/glscene.h +++ b/src/gl/glscene.h @@ -129,16 +129,18 @@ class Scene final : public QObject VisMode visMode; - enum SelModes + enum ActionModes { - SelNone = 0, - SelObject = 1, - SelVertex = 2 + Deselect = 0x0, + Select = 0x1, + Paint = 0x2, + Object = 0x10000, + Vertex = 0x20000 }; - Q_DECLARE_FLAGS( SelMode, SelModes ); + Q_DECLARE_FLAGS( ActionMode, ActionModes ); - SelMode selMode; + ActionMode actionMode; enum LodLevel { @@ -149,7 +151,7 @@ class Scene final : public QObject LodLevel lodLevel; - + Renderer * renderer; NodeList nodes; @@ -203,4 +205,6 @@ Q_DECLARE_OPERATORS_FOR_FLAGS( Scene::SceneOptions ) Q_DECLARE_OPERATORS_FOR_FLAGS( Scene::VisMode ) +Q_DECLARE_OPERATORS_FOR_FLAGS( Scene::ActionMode ) + #endif diff --git a/src/glview.cpp b/src/glview.cpp index d44d8c0d1..539a16eaf 100644 --- a/src/glview.cpp +++ b/src/glview.cpp @@ -72,15 +72,15 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Appears to be used solely for gluErrorString // There may be some Qt alternative #ifdef __APPLE__ - #include + #include #else - #include + #include #endif // NOTE: The FPS define is a frame limiter, // NOT the guaranteed FPS in the viewport. -// Also the QTimer is integer milliseconds +// Also the QTimer is integer milliseconds // so 60 will give you 1000/60 = 16, not 16.666 // therefore it's really 62.5FPS #define FPS 144 @@ -98,11 +98,11 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. GLGraphicsView::GLGraphicsView( QWidget * parent ) : QGraphicsView() { - setContextMenuPolicy( Qt::CustomContextMenu ); - setFocusPolicy( Qt::ClickFocus ); - setAcceptDrops( true ); - - installEventFilter( parent ); + setContextMenuPolicy( Qt::CustomContextMenu ); + setFocusPolicy( Qt::ClickFocus ); + setAcceptDrops( true ); + + installEventFilter( parent ); } GLGraphicsView::~GLGraphicsView() {} @@ -110,175 +110,176 @@ GLGraphicsView::~GLGraphicsView() {} GLView * GLView::create( NifSkope * window ) { - QGLFormat fmt; - static QList > views; + QGLFormat fmt; + static QList > views; + + QGLWidget * share = nullptr; + for ( const QPointer& v : views ) { + if ( v ) + share = v; + } - QGLWidget * share = nullptr; - for ( const QPointer& v : views ) { - if ( v ) - share = v; - } + QSettings settings; + int aa = settings.value( "Settings/Render/General/Antialiasing", 4 ).toInt(); - QSettings settings; - int aa = settings.value( "Settings/Render/General/Antialiasing", 4 ).toInt(); + // All new windows after the first window will share a format + if ( share ) { + fmt = share->format(); + } else { + fmt.setSampleBuffers( aa > 0 ); + } - // All new windows after the first window will share a format - if ( share ) { - fmt = share->format(); - } else { - fmt.setSampleBuffers( aa > 0 ); - } - - // OpenGL version - fmt.setVersion( 2, 1 ); - // Ignored if version < 3.2 - //fmt.setProfile(QGLFormat::CoreProfile); + // OpenGL version + fmt.setVersion( 2, 1 ); + // Ignored if version < 3.2 + //fmt.setProfile(QGLFormat::CoreProfile); - // V-Sync - fmt.setSwapInterval( 1 ); - fmt.setDoubleBuffer( true ); + // V-Sync + fmt.setSwapInterval( 1 ); + fmt.setDoubleBuffer( true ); - fmt.setSamples( std::pow( aa, 2 ) ); + fmt.setSamples( std::pow( aa, 2 ) ); - fmt.setDirectRendering( true ); - fmt.setRgba( true ); + fmt.setDirectRendering( true ); + fmt.setRgba( true ); - views.append( QPointer( new GLView( fmt, window, share ) ) ); + views.append( QPointer( new GLView( fmt, window, share ) ) ); - return views.last(); + return views.last(); } GLView::GLView( const QGLFormat & format, QWidget * p, const QGLWidget * shareWidget ) - : QGLWidget( format, p, shareWidget ) + : QGLWidget( format, p, shareWidget ) { - setFocusPolicy( Qt::ClickFocus ); - //setAttribute( Qt::WA_PaintOnScreen ); - //setAttribute( Qt::WA_NoSystemBackground ); - setAutoFillBackground( false ); - setAcceptDrops( true ); - setContextMenuPolicy( Qt::CustomContextMenu ); + setFocusPolicy( Qt::ClickFocus ); + //setAttribute( Qt::WA_PaintOnScreen ); + //setAttribute( Qt::WA_NoSystemBackground ); + setAutoFillBackground( false ); + setAcceptDrops( true ); + setMouseTracking( true ); + setContextMenuPolicy( Qt::CustomContextMenu ); - // Manually handle the buffer swap - // Fixes bug with QGraphicsView and double buffering - // Input becomes sluggish and CPU usage doubles when putting GLView - // inside a QGraphicsView. - setAutoBufferSwap( false ); + // Manually handle the buffer swap + // Fixes bug with QGraphicsView and double buffering + // Input becomes sluggish and CPU usage doubles when putting GLView + // inside a QGraphicsView. + setAutoBufferSwap( false ); - // Make the context current on this window - makeCurrent(); + // Make the context current on this window + makeCurrent(); - // Create an OpenGL context - glContext = context()->contextHandle(); + // Create an OpenGL context + glContext = context()->contextHandle(); - // Obtain a functions object and resolve all entry points - glFuncs = glContext->functions(); + // Obtain a functions object and resolve all entry points + glFuncs = glContext->functions(); - if ( !glFuncs ) { - Message::critical( this, tr( "Could not obtain OpenGL functions" ) ); - exit( 1 ); - } + if ( !glFuncs ) { + Message::critical( this, tr( "Could not obtain OpenGL functions" ) ); + exit( 1 ); + } - glFuncs->initializeOpenGLFunctions(); + glFuncs->initializeOpenGLFunctions(); - view = ViewDefault; - animState = AnimEnabled; - debugMode = DbgNone; + view = ViewDefault; + animState = AnimEnabled; + debugMode = DbgNone; - Zoom = 1.0; + Zoom = 1.0; - doCenter = false; - doCompile = false; + doCenter = false; + doCompile = false; - model = nullptr; + model = nullptr; - time = 0.0; - lastTime = QTime::currentTime(); + time = 0.0; + lastTime = QTime::currentTime(); - textures = new TexCache( this ); + textures = new TexCache( this ); - updateSettings(); + updateSettings(); - scene = new Scene( textures, glContext, glFuncs ); - connect( textures, &TexCache::sigRefresh, this, static_cast(&GLView::update) ); - connect( scene, &Scene::sceneUpdated, this, static_cast(&GLView::update) ); + scene = new Scene( textures, glContext, glFuncs ); + connect( textures, &TexCache::sigRefresh, this, static_cast(&GLView::update) ); + connect( scene, &Scene::sceneUpdated, this, static_cast(&GLView::update) ); - timer = new QTimer( this ); - timer->setInterval( 1000 / FPS ); - timer->start(); - connect( timer, &QTimer::timeout, this, &GLView::advanceGears ); + timer = new QTimer( this ); + timer->setInterval( 1000 / FPS ); + timer->start(); + connect( timer, &QTimer::timeout, this, &GLView::advanceGears ); - lightVisTimeout = 1500; - lightVisTimer = new QTimer( this ); - lightVisTimer->setSingleShot( true ); - connect( lightVisTimer, &QTimer::timeout, [this]() { setVisMode( Scene::VisLightPos, false ); update(); } ); + lightVisTimeout = 1500; + lightVisTimer = new QTimer( this ); + lightVisTimer->setSingleShot( true ); + connect( lightVisTimer, &QTimer::timeout, [this]() { setVisMode( Scene::VisLightPos, false ); update(); } ); - connect( NifSkope::getOptions(), &SettingsDialog::flush3D, textures, &TexCache::flush ); - connect( NifSkope::getOptions(), &SettingsDialog::update3D, [this]() { - updateSettings(); - qglClearColor( cfg.background ); - update(); - } ); + connect( NifSkope::getOptions(), &SettingsDialog::flush3D, textures, &TexCache::flush ); + connect( NifSkope::getOptions(), &SettingsDialog::update3D, [this]() { + updateSettings(); + qglClearColor( cfg.background ); + update(); + } ); } GLView::~GLView() { - flush(); + flush(); - delete textures; - delete scene; + delete textures; + delete scene; } void GLView::updateSettings() { - QSettings settings; - settings.beginGroup( "Settings/Render" ); + QSettings settings; + settings.beginGroup( "Settings/Render" ); - cfg.background = settings.value( "Colors/Background", QColor( 46, 46, 46 ) ).value(); - cfg.fov = settings.value( "General/Camera/Field Of View" ).toFloat(); - cfg.moveSpd = settings.value( "General/Camera/Movement Speed" ).toFloat(); - cfg.rotSpd = settings.value( "General/Camera/Rotation Speed" ).toFloat(); - cfg.upAxis = UpAxis(settings.value( "General/Up Axis", ZAxis ).toInt()); + cfg.background = settings.value( "Colors/Background", QColor( 46, 46, 46 ) ).value(); + cfg.fov = settings.value( "General/Camera/Field Of View" ).toFloat(); + cfg.moveSpd = settings.value( "General/Camera/Movement Speed" ).toFloat(); + cfg.rotSpd = settings.value( "General/Camera/Rotation Speed" ).toFloat(); + cfg.upAxis = UpAxis(settings.value( "General/Up Axis", ZAxis ).toInt()); - settings.endGroup(); + settings.endGroup(); } QColor GLView::clearColor() const { - return cfg.background; + return cfg.background; } -/* +/* * Scene */ Scene * GLView::getScene() { - return scene; + return scene; } void GLView::updateScene() { - scene->update( model, QModelIndex() ); - update(); + scene->update( model, QModelIndex() ); + update(); } void GLView::updateAnimationState( bool checked ) { - QAction * action = qobject_cast(sender()); - if ( action ) { - auto opt = AnimationState( action->data().toInt() ); + QAction * action = qobject_cast(sender()); + if ( action ) { + auto opt = AnimationState( action->data().toInt() ); - if ( checked ) - animState |= opt; - else - animState &= ~opt; + if ( checked ) + animState |= opt; + else + animState &= ~opt; - scene->animate = (animState & AnimEnabled); - lastTime = QTime::currentTime(); + scene->animate = (animState & AnimEnabled); + lastTime = QTime::currentTime(); - update(); - } + update(); + } } @@ -288,817 +289,978 @@ void GLView::updateAnimationState( bool checked ) void GLView::initializeGL() { - GLenum err; - - if ( scene->options & Scene::DoMultisampling ) { - if ( !glContext->hasExtension( "GL_EXT_framebuffer_multisample" ) ) { - scene->options &= ~Scene::DoMultisampling; - //qDebug() << "System does not support multisampling"; - } /* else { - GLint maxSamples; - glGetIntegerv( GL_MAX_SAMPLES, &maxSamples ); - qDebug() << "Max samples:" << maxSamples; - }*/ - } + GLenum err; + + if ( scene->options & Scene::DoMultisampling ) { + if ( !glContext->hasExtension( "GL_EXT_framebuffer_multisample" ) ) { + scene->options &= ~Scene::DoMultisampling; + //qDebug() << "System does not support multisampling"; + } /* else { + GLint maxSamples; + glGetIntegerv( GL_MAX_SAMPLES, &maxSamples ); + qDebug() << "Max samples:" << maxSamples; + }*/ + } - initializeTextureUnits( glContext ); + initializeTextureUnits( glContext ); - if ( scene->renderer->initialize() ) - updateShaders(); + if ( scene->renderer->initialize() ) + updateShaders(); - // Initial viewport values - // Made viewport and aspect member variables. - // They were being updated every single frame instead of only when resizing. - //glGetIntegerv( GL_VIEWPORT, viewport ); - aspect = (GLdouble)width() / (GLdouble)height(); + // Initial viewport values + // Made viewport and aspect member variables. + // They were being updated every single frame instead of only when resizing. + //glGetIntegerv( GL_VIEWPORT, viewport ); + aspect = (GLdouble)width() / (GLdouble)height(); - // Check for errors - while ( ( err = glGetError() ) != GL_NO_ERROR ) - qDebug() << tr( "glview.cpp - GL ERROR (init) : " ) << (const char *)gluErrorString( err ); + // Check for errors + while ( ( err = glGetError() ) != GL_NO_ERROR ) + qDebug() << tr( "glview.cpp - GL ERROR (init) : " ) << (const char *)gluErrorString( err ); } void GLView::updateShaders() { - makeCurrent(); - scene->updateShaders(); - update(); + makeCurrent(); + scene->updateShaders(); + update(); } void GLView::glProjection( int x, int y ) { - Q_UNUSED( x ); Q_UNUSED( y ); + Q_UNUSED( x ); Q_UNUSED( y ); - glMatrixMode( GL_PROJECTION ); - glLoadIdentity(); + glMatrixMode( GL_PROJECTION ); + glLoadIdentity(); - BoundSphere bs = scene->view * scene->bounds(); + BoundSphere bs = scene->view * scene->bounds(); - if ( scene->options & Scene::ShowAxes ) { - bs |= BoundSphere( scene->view * Vector3(), axis ); - } + if ( scene->options & Scene::ShowAxes ) { + bs |= BoundSphere( scene->view * Vector3(), axis ); + } - float bounds = (bs.radius > 1024.0) ? bs.radius : 1024.0; + float bounds = (bs.radius > 1024.0) ? bs.radius : 1024.0; - GLdouble nr = fabs( bs.center[2] ) - bounds * 1.5; - GLdouble fr = fabs( bs.center[2] ) + bounds * 1.5; + GLdouble nr = fabs( bs.center[2] ) - bounds * 1.5; + GLdouble fr = fabs( bs.center[2] ) + bounds * 1.5; - if ( perspectiveMode || (view == ViewWalk) ) { - // Perspective View - if ( nr < 1.0 ) - nr = 1.0; - if ( fr < 2.0 ) - fr = 2.0; + if ( perspectiveMode || (view == ViewWalk) ) { + // Perspective View + if ( nr < 1.0 ) + nr = 1.0; + if ( fr < 2.0 ) + fr = 2.0; - if ( nr > fr ) { - // add: swap them when needed - GLfloat tmp = nr; - nr = fr; - fr = tmp; - } + if ( nr > fr ) { + // add: swap them when needed + GLfloat tmp = nr; + nr = fr; + fr = tmp; + } - if ( (fr - nr) < 0.00001f ) { - // add: ensure distance - nr = 1.0; - fr = 2.0; - } + if ( (fr - nr) < 0.00001f ) { + // add: ensure distance + nr = 1.0; + fr = 2.0; + } - GLdouble h2 = tan( ( cfg.fov / Zoom ) / 360 * M_PI ) * nr; - GLdouble w2 = h2 * aspect; - glFrustum( -w2, +w2, -h2, +h2, nr, fr ); - } else { - // Orthographic View - GLdouble h2 = Dist / Zoom; - GLdouble w2 = h2 * aspect; - glOrtho( -w2, +w2, -h2, +h2, nr, fr ); - } + GLdouble h2 = tan( ( cfg.fov / Zoom ) / 360 * M_PI ) * nr; + GLdouble w2 = h2 * aspect; + glFrustum( -w2, +w2, -h2, +h2, nr, fr ); + } else { + // Orthographic View + GLdouble h2 = Dist / Zoom; + GLdouble w2 = h2 * aspect; + glOrtho( -w2, +w2, -h2, +h2, nr, fr ); + } - glMatrixMode( GL_MODELVIEW ); - glLoadIdentity(); + glMatrixMode( GL_MODELVIEW ); + glLoadIdentity(); } #ifdef USE_GL_QPAINTER void GLView::paintEvent( QPaintEvent * event ) { - makeCurrent(); + makeCurrent(); - QPainter painter; - painter.begin( this ); - painter.setRenderHint( QPainter::TextAntialiasing ); + QPainter painter; + painter.begin( this ); + painter.setRenderHint( QPainter::TextAntialiasing ); #else void GLView::paintGL() { #endif - - - // Save GL state - glPushAttrib( GL_ALL_ATTRIB_BITS ); - glMatrixMode( GL_PROJECTION ); - glPushMatrix(); - glMatrixMode( GL_MODELVIEW ); - glPushMatrix(); - - // Clear Viewport - if ( scene->visMode & Scene::VisSilhouette ) { - qglClearColor( QColor( 255, 255, 255, 255 ) ); - } - //glViewport( 0, 0, width(), height() ); - glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT ); - - - // Compile the model - if ( doCompile ) { - textures->setNifFolder( model->getFolder() ); - scene->make( model ); - scene->transform( Transform(), scene->timeMin() ); - axis = (scene->bounds().radius <= 0) ? 1024.0 : scene->bounds().radius; - - if ( scene->timeMin() != scene->timeMax() ) { - if ( time < scene->timeMin() || time > scene->timeMax() ) - time = scene->timeMin(); - - emit sequencesUpdated(); - - } else if ( scene->timeMax() == 0 ) { - // No Animations in this NIF - emit sequencesDisabled( true ); - } - emit sceneTimeChanged( time, scene->timeMin(), scene->timeMax() ); - doCompile = false; - } - - // Center the model - if ( doCenter ) { - setCenter(); - doCenter = false; - } - - // Transform the scene - Matrix ap; - - if ( cfg.upAxis == YAxis ) { - ap( 0, 0 ) = 0; ap( 0, 1 ) = 0; ap( 0, 2 ) = 1; - ap( 1, 0 ) = 1; ap( 1, 1 ) = 0; ap( 1, 2 ) = 0; - ap( 2, 0 ) = 0; ap( 2, 1 ) = 1; ap( 2, 2 ) = 0; - } else if ( cfg.upAxis == XAxis ) { - ap( 0, 0 ) = 0; ap( 0, 1 ) = 1; ap( 0, 2 ) = 0; - ap( 1, 0 ) = 0; ap( 1, 1 ) = 0; ap( 1, 2 ) = 1; - ap( 2, 0 ) = 1; ap( 2, 1 ) = 0; ap( 2, 2 ) = 0; - } - - viewTrans.rotation.fromEuler( Rot[0] / 180.0 * PI, Rot[1] / 180.0 * PI, Rot[2] / 180.0 * PI ); - viewTrans.translation = viewTrans.rotation * Pos; - viewTrans.rotation = viewTrans.rotation * ap; - - if ( view != ViewWalk ) - viewTrans.translation[2] -= Dist * 2; - - scene->transform( viewTrans, time ); - - // Setup projection mode - glProjection(); - glLoadIdentity(); - - // Draw the grid - if ( scene->options & Scene::ShowGrid ) { - glDisable( GL_ALPHA_TEST ); - glDisable( GL_BLEND ); - glDisable( GL_LIGHTING ); - glDisable( GL_COLOR_MATERIAL ); - glEnable( GL_DEPTH_TEST ); - glDepthMask( GL_TRUE ); - glDepthFunc( GL_LESS ); - glDisable( GL_TEXTURE_2D ); - glDisable( GL_NORMALIZE ); - glLineWidth( 2.0f ); - - // Keep the grid "grounded" regardless of Up Axis - Transform gridTrans = viewTrans; - if ( cfg.upAxis != ZAxis ) - gridTrans.rotation = viewTrans.rotation * ap.inverted(); - - glPushMatrix(); - glLoadMatrix( gridTrans ); - - // TODO: Configurable grid in Settings - // 1024 game units, major lines every 128, minor lines every 64 - drawGrid( 1024, 128, 2 ); - - glPopMatrix(); - } + + + // Save GL state + glPushAttrib( GL_ALL_ATTRIB_BITS ); + glMatrixMode( GL_PROJECTION ); + glPushMatrix(); + glMatrixMode( GL_MODELVIEW ); + glPushMatrix(); + + // Clear Viewport + if ( scene->visMode & Scene::VisSilhouette ) { + qglClearColor( QColor( 255, 255, 255, 255 ) ); + } + //glViewport( 0, 0, width(), height() ); + glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT ); + + + // Compile the model + if ( doCompile ) { + textures->setNifFolder( model->getFolder() ); + scene->make( model ); + scene->transform( Transform(), scene->timeMin() ); + axis = (scene->bounds().radius <= 0) ? 1024.0 : scene->bounds().radius; + + if ( scene->timeMin() != scene->timeMax() ) { + if ( time < scene->timeMin() || time > scene->timeMax() ) + time = scene->timeMin(); + + emit sequencesUpdated(); + + } else if ( scene->timeMax() == 0 ) { + // No Animations in this NIF + emit sequencesDisabled( true ); + } + emit sceneTimeChanged( time, scene->timeMin(), scene->timeMax() ); + doCompile = false; + } + + // Center the model + if ( doCenter ) { + setCenter(); + doCenter = false; + } + + // Transform the scene + Matrix ap; + + if ( cfg.upAxis == YAxis ) { + ap( 0, 0 ) = 0; ap( 0, 1 ) = 0; ap( 0, 2 ) = 1; + ap( 1, 0 ) = 1; ap( 1, 1 ) = 0; ap( 1, 2 ) = 0; + ap( 2, 0 ) = 0; ap( 2, 1 ) = 1; ap( 2, 2 ) = 0; + } else if ( cfg.upAxis == XAxis ) { + ap( 0, 0 ) = 0; ap( 0, 1 ) = 1; ap( 0, 2 ) = 0; + ap( 1, 0 ) = 0; ap( 1, 1 ) = 0; ap( 1, 2 ) = 1; + ap( 2, 0 ) = 1; ap( 2, 1 ) = 0; ap( 2, 2 ) = 0; + } + + viewTrans.rotation.fromEuler( Rot[0] / 180.0 * PI, Rot[1] / 180.0 * PI, Rot[2] / 180.0 * PI ); + viewTrans.translation = viewTrans.rotation * Pos; + viewTrans.rotation = viewTrans.rotation * ap; + + if ( view != ViewWalk ) + viewTrans.translation[2] -= Dist * 2; + + scene->transform( viewTrans, time ); + + // Setup projection mode + glProjection(); + glLoadIdentity(); + + // Draw the grid + if ( scene->options & Scene::ShowGrid ) { + glDisable( GL_ALPHA_TEST ); + glDisable( GL_BLEND ); + glDisable( GL_LIGHTING ); + glDisable( GL_COLOR_MATERIAL ); + glEnable( GL_DEPTH_TEST ); + glDepthMask( GL_TRUE ); + glDepthFunc( GL_LESS ); + glDisable( GL_TEXTURE_2D ); + glDisable( GL_NORMALIZE ); + glLineWidth( 2.0f ); + + // Keep the grid "grounded" regardless of Up Axis + Transform gridTrans = viewTrans; + if ( cfg.upAxis != ZAxis ) + gridTrans.rotation = viewTrans.rotation * ap.inverted(); + + glPushMatrix(); + glLoadMatrix( gridTrans ); + + // TODO: Configurable grid in Settings + // 1024 game units, major lines every 128, minor lines every 64 + drawGrid( 1024, 128, 2 ); + + glPopMatrix(); + } #ifndef QT_NO_DEBUG - // Debug scene bounds - glEnable( GL_DEPTH_TEST ); - glDepthMask( GL_TRUE ); - glDepthFunc( GL_LESS ); - glPushMatrix(); - glLoadMatrix( viewTrans ); - if ( debugMode == DbgBounds ) { - BoundSphere bs = scene->bounds(); - bs |= BoundSphere( Vector3(), axis ); - drawSphere( bs.center, bs.radius ); - } - glPopMatrix(); + // Debug scene bounds + glEnable( GL_DEPTH_TEST ); + glDepthMask( GL_TRUE ); + glDepthFunc( GL_LESS ); + glPushMatrix(); + glLoadMatrix( viewTrans ); + if ( debugMode == DbgBounds ) { + BoundSphere bs = scene->bounds(); + bs |= BoundSphere( Vector3(), axis ); + drawSphere( bs.center, bs.radius ); + } + glPopMatrix(); #endif - GLfloat mat_spec[] = { 0.0f, 0.0f, 0.0f, 1.0f }; - - if ( scene->options & Scene::DoLighting ) { - // Setup light - Vector4 lightDir( 0.0, 0.0, 1.0, 0.0 ); - - if ( !frontalLight ) { - float decl = declination / 180.0 * PI; - Vector3 v( sin( decl ), 0, cos( decl ) ); - Matrix m; m.fromEuler( 0, 0, planarAngle / 180.0 * PI ); - v = m * v; - lightDir = Vector4( viewTrans.rotation * v, 0.0 ); - - if ( scene->visMode & Scene::VisLightPos ) { - glEnable( GL_BLEND ); - glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); - glEnable( GL_DEPTH_TEST ); - glDepthMask( GL_TRUE ); - glDepthFunc( GL_LESS ); - - glPushMatrix(); - glLoadMatrix( viewTrans ); - - glLineWidth( 2.0f ); - glColor4f( 1.0f, 1.0f, 1.0f, 0.5f ); - - // Scale the distance a bit - float l = axis + 64.0; - l = (l < 128) ? axis * 1.5 : l; - l = (l > 2048) ? axis * 0.66 : l; - l = (l > 1024) ? axis * 0.75 : l; - - drawDashLine( Vector3( 0, 0, 0 ), v * l, 30 ); - drawSphere( v * l, axis / 10 ); - glPopMatrix(); - glDisable( GL_BLEND ); - } - } - - float amb = ambient; - if ( (scene->visMode & Scene::VisNormalsOnly) - && (scene->options & Scene::DoTexturing) - && !(scene->options & Scene::DisableShaders) ) - { - amb = 0.1f; - } - - GLfloat mat_amb[] = { amb, amb, amb, 1.0f }; - GLfloat mat_diff[] = { brightness, brightness, brightness, 1.0f }; - - - glShadeModel( GL_SMOOTH ); - //glEnable( GL_LIGHTING ); - glEnable( GL_LIGHT0 ); - glLightfv( GL_LIGHT0, GL_AMBIENT, mat_amb ); - glLightfv( GL_LIGHT0, GL_DIFFUSE, mat_diff ); - glLightfv( GL_LIGHT0, GL_SPECULAR, mat_diff ); - glLightfv( GL_LIGHT0, GL_POSITION, lightDir.data() ); - } else { - float amb = 0.5f; - if ( scene->options & Scene::DisableShaders ) { - amb = 0.0f; - } - - GLfloat mat_amb[] = { amb, amb, amb, 1.0f }; - GLfloat mat_diff[] = { 1.0f, 1.0f, 1.0f, 1.0f }; - - - glShadeModel( GL_SMOOTH ); - //glEnable( GL_LIGHTING ); - glEnable( GL_LIGHT0 ); - glLightfv( GL_LIGHT0, GL_AMBIENT, mat_amb ); - glLightfv( GL_LIGHT0, GL_DIFFUSE, mat_diff ); - glLightfv( GL_LIGHT0, GL_SPECULAR, mat_spec ); - } - - if ( scene->visMode & Scene::VisSilhouette ) { - GLfloat mat_diff[] = { 0.0f, 0.0f, 0.0f, 1.0f }; - GLfloat mat_amb[] = { 0.0f, 0.0f, 0.0f, 1.0f }; - - glShadeModel( GL_FLAT ); - //glEnable( GL_LIGHTING ); - glEnable( GL_LIGHT0 ); - glLightModelfv( GL_LIGHT_MODEL_AMBIENT, mat_diff ); - glMaterialfv( GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, mat_diff ); - glLightfv( GL_LIGHT0, GL_AMBIENT, mat_amb ); - glLightfv( GL_LIGHT0, GL_DIFFUSE, mat_diff ); - glLightfv( GL_LIGHT0, GL_SPECULAR, mat_spec ); - } - - if ( scene->options & Scene::DoMultisampling ) - glEnable( GL_MULTISAMPLE_ARB ); + GLfloat mat_spec[] = { 0.0f, 0.0f, 0.0f, 1.0f }; + + if ( scene->options & Scene::DoLighting ) { + // Setup light + Vector4 lightDir( 0.0, 0.0, 1.0, 0.0 ); + + if ( !frontalLight ) { + float decl = declination / 180.0 * PI; + Vector3 v( sin( decl ), 0, cos( decl ) ); + Matrix m; m.fromEuler( 0, 0, planarAngle / 180.0 * PI ); + v = m * v; + lightDir = Vector4( viewTrans.rotation * v, 0.0 ); + + if ( scene->visMode & Scene::VisLightPos ) { + glEnable( GL_BLEND ); + glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); + glEnable( GL_DEPTH_TEST ); + glDepthMask( GL_TRUE ); + glDepthFunc( GL_LESS ); + + glPushMatrix(); + glLoadMatrix( viewTrans ); + + glLineWidth( 2.0f ); + glColor4f( 1.0f, 1.0f, 1.0f, 0.5f ); + + // Scale the distance a bit + float l = axis + 64.0; + l = (l < 128) ? axis * 1.5 : l; + l = (l > 2048) ? axis * 0.66 : l; + l = (l > 1024) ? axis * 0.75 : l; + + drawDashLine( Vector3( 0, 0, 0 ), v * l, 30 ); + drawSphere( v * l, axis / 10 ); + glPopMatrix(); + glDisable( GL_BLEND ); + } + } + + float amb = ambient; + if ( (scene->visMode & Scene::VisNormalsOnly) + && (scene->options & Scene::DoTexturing) + && !(scene->options & Scene::DisableShaders) ) + { + amb = 0.1f; + } + + GLfloat mat_amb[] = { amb, amb, amb, 1.0f }; + GLfloat mat_diff[] = { brightness, brightness, brightness, 1.0f }; + + + glShadeModel( GL_SMOOTH ); + //glEnable( GL_LIGHTING ); + glEnable( GL_LIGHT0 ); + glLightfv( GL_LIGHT0, GL_AMBIENT, mat_amb ); + glLightfv( GL_LIGHT0, GL_DIFFUSE, mat_diff ); + glLightfv( GL_LIGHT0, GL_SPECULAR, mat_diff ); + glLightfv( GL_LIGHT0, GL_POSITION, lightDir.data() ); + } else { + float amb = 0.5f; + if ( scene->options & Scene::DisableShaders ) { + amb = 0.0f; + } + + GLfloat mat_amb[] = { amb, amb, amb, 1.0f }; + GLfloat mat_diff[] = { 1.0f, 1.0f, 1.0f, 1.0f }; + + + glShadeModel( GL_SMOOTH ); + //glEnable( GL_LIGHTING ); + glEnable( GL_LIGHT0 ); + glLightfv( GL_LIGHT0, GL_AMBIENT, mat_amb ); + glLightfv( GL_LIGHT0, GL_DIFFUSE, mat_diff ); + glLightfv( GL_LIGHT0, GL_SPECULAR, mat_spec ); + } + + if ( scene->visMode & Scene::VisSilhouette ) { + GLfloat mat_diff[] = { 0.0f, 0.0f, 0.0f, 1.0f }; + GLfloat mat_amb[] = { 0.0f, 0.0f, 0.0f, 1.0f }; + + glShadeModel( GL_FLAT ); + //glEnable( GL_LIGHTING ); + glEnable( GL_LIGHT0 ); + glLightModelfv( GL_LIGHT_MODEL_AMBIENT, mat_diff ); + glMaterialfv( GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, mat_diff ); + glLightfv( GL_LIGHT0, GL_AMBIENT, mat_amb ); + glLightfv( GL_LIGHT0, GL_DIFFUSE, mat_diff ); + glLightfv( GL_LIGHT0, GL_SPECULAR, mat_spec ); + } + + if ( scene->options & Scene::DoMultisampling ) + glEnable( GL_MULTISAMPLE_ARB ); #ifndef QT_NO_DEBUG - // Color Key debug - if ( debugMode == DbgColorPicker ) { - glDisable( GL_MULTISAMPLE ); - glDisable( GL_LINE_SMOOTH ); - glDisable( GL_TEXTURE_2D ); - glDisable( GL_BLEND ); - glDisable( GL_DITHER ); - glDisable( GL_LIGHTING ); - glShadeModel( GL_FLAT ); - glDisable( GL_FOG ); - glDisable( GL_MULTISAMPLE_ARB ); - glEnable( GL_DEPTH_TEST ); - glDepthFunc( GL_LEQUAL ); - glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); - Node::SELECTING = 1; - } else { - Node::SELECTING = 0; - } + // Color Key debug + if ( debugMode == DbgColorPicker ) { + glDisable( GL_MULTISAMPLE ); + glDisable( GL_LINE_SMOOTH ); + glDisable( GL_TEXTURE_2D ); + glDisable( GL_BLEND ); + glDisable( GL_DITHER ); + glDisable( GL_LIGHTING ); + glShadeModel( GL_FLAT ); + glDisable( GL_FOG ); + glDisable( GL_MULTISAMPLE_ARB ); + glEnable( GL_DEPTH_TEST ); + glDepthFunc( GL_LEQUAL ); + glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); + Node::SELECTING = 1; + } else { + Node::SELECTING = 0; + } #endif - // Draw the model - scene->draw(); + // Draw the model + scene->draw(); + + if ( scene->options & Scene::ShowAxes ) { + // Resize viewport to small corner of screen + int axesSize = std::min( width() / 10, 125 ); + glViewport( 0, 0, axesSize * devicePixelRatioF(), axesSize * devicePixelRatioF() ); - if ( scene->options & Scene::ShowAxes ) { - // Resize viewport to small corner of screen - int axesSize = std::min( width() / 10, 125 ); - glViewport( 0, 0, axesSize, axesSize ); + // Reset matrices + glMatrixMode( GL_PROJECTION ); + glLoadIdentity(); - // Reset matrices - glMatrixMode( GL_PROJECTION ); - glLoadIdentity(); + // Square frustum + auto nr = 1.0; + auto fr = 250.0; + GLdouble h2 = tan( cfg.fov / 360 * M_PI ) * nr; + GLdouble w2 = h2; + glFrustum( -w2, +w2, -h2, +h2, nr, fr ); - // Square frustum - auto nr = 1.0; - auto fr = 250.0; - GLdouble h2 = tan( cfg.fov / 360 * M_PI ) * nr; - GLdouble w2 = h2; - glFrustum( -w2, +w2, -h2, +h2, nr, fr ); + // Reset matrices + glMatrixMode( GL_MODELVIEW ); + glLoadIdentity(); - // Reset matrices - glMatrixMode( GL_MODELVIEW ); - glLoadIdentity(); + glPushMatrix(); - glPushMatrix(); + // Store and reset viewTrans translation + auto viewTransOrig = viewTrans.translation; - // Store and reset viewTrans translation - auto viewTransOrig = viewTrans.translation; + // Zoom out slightly + viewTrans.translation = { 0, 0, -150.0 }; - // Zoom out slightly - viewTrans.translation = { 0, 0, -150.0 }; + // Load modified viewTrans + glLoadMatrix( viewTrans ); - // Load modified viewTrans - glLoadMatrix( viewTrans ); + // Restore original viewTrans translation + viewTrans.translation = viewTransOrig; - // Restore original viewTrans translation - viewTrans.translation = viewTransOrig; + // Find direction of axes + auto vtr = viewTrans.rotation; + QVector axesDots = { vtr( 2, 0 ), vtr( 2, 1 ), vtr( 2, 2 ) }; - // Find direction of axes - auto vtr = viewTrans.rotation; - QVector axesDots = { vtr( 2, 0 ), vtr( 2, 1 ), vtr( 2, 2 ) }; + drawAxesOverlay( { 0, 0, 0 }, 50.0, sortAxes( axesDots ) ); - drawAxesOverlay( { 0, 0, 0 }, 50.0, sortAxes( axesDots ) ); + glPopMatrix(); - glPopMatrix(); + // Restore viewport size + glViewport( 0, 0, width()*devicePixelRatioF(), height()*devicePixelRatioF()); - // Restore viewport size - glViewport( 0, 0, width(), height() ); - // Restore matrices - glProjection(); - } + // Restore matrices + glProjection(); + } - // Restore GL state - glPopAttrib(); - glMatrixMode( GL_MODELVIEW ); - glPopMatrix(); - glMatrixMode( GL_PROJECTION ); - glPopMatrix(); + // Draw mouse tool + if (scene->actionMode & Scene::Paint) + { + // Reset matrices + glViewport( 0, 0, width()*devicePixelRatioF(), height()*devicePixelRatioF()); + glMatrixMode( GL_PROJECTION ); + glLoadIdentity(); + glOrtho(0, width(), height(), 0, -1, 1); + glMatrixMode( GL_MODELVIEW ); + glLoadIdentity(); + glDisable( GL_LIGHTING ); + glDepthFunc( GL_ALWAYS ); + glLineWidth( 2.0f ); + glColor3f( 1.0, 0.0, 0.0 ); + drawCircle(Vector3(lastPos.x(), lastPos.y(), 0), Vector3(0,0,1), cfg.vertexPaintSettings.brushSize + 2.0f); - // Check for errors - GLenum err; - while ( ( err = glGetError() ) != GL_NO_ERROR ) - qDebug() << tr( "glview.cpp - GL ERROR (paint): " ) << (const char *)gluErrorString( err ); + // Restore viewport and projection + glViewport( 0, 0, width()*devicePixelRatioF(), height()*devicePixelRatioF()); + glProjection(); + } - emit paintUpdate(); + // Restore GL state + glPopAttrib(); + glMatrixMode( GL_MODELVIEW ); + glPopMatrix(); + glMatrixMode( GL_PROJECTION ); + glPopMatrix(); - // Manually handle the buffer swap - swapBuffers(); + // Check for errors + GLenum err; + while ( ( err = glGetError() ) != GL_NO_ERROR ) + qDebug() << tr( "glview.cpp - GL ERROR (paint): " ) << (const char *)gluErrorString( err ); + + emit paintUpdate(); + + // Manually handle the buffer swap + swapBuffers(); #ifdef USE_GL_QPAINTER - painter.end(); + painter.end(); #endif } void GLView::resizeGL( int width, int height ) { - resize( width, height ); + resize( width, height ); - makeCurrent(); - aspect = (GLdouble)width / (GLdouble)height; - glViewport( 0, 0, width, height ); - qglClearColor( cfg.background ); + makeCurrent(); + aspect = (GLdouble)width / (GLdouble)height; + glViewport( 0, 0, width, height ); + qglClearColor( cfg.background ); - update(); + update(); } void GLView::resizeEvent( QResizeEvent * e ) { - Q_UNUSED( e ); - // This function should never be called. - // Moved to NifSkope::eventFilter() + Q_UNUSED( e ); + // This function should never be called. + // Moved to NifSkope::eventFilter() } void GLView::setFrontalLight( bool frontal ) { - frontalLight = frontal; - update(); + frontalLight = frontal; + update(); } void GLView::setBrightness( int value ) { - if ( value > 900 ) { - value += pow(value - 900, 1.5); - } + if ( value > 900 ) { + value += pow(value - 900, 1.5); + } - brightness = float(value) / 720.0; - update(); + brightness = float(value) / 720.0; + update(); } void GLView::setAmbient( int value ) { - ambient = float( value ) / 1440.0; - update(); + ambient = float( value ) / 1440.0; + update(); } void GLView::setDeclination( int decl ) { - declination = float(decl) / 4; // Divide by 4 because sliders are -720 <-> 720 - lightVisTimer->start( lightVisTimeout ); - setVisMode( Scene::VisLightPos, true ); - update(); + declination = float(decl) / 4; // Divide by 4 because sliders are -720 <-> 720 + lightVisTimer->start( lightVisTimeout ); + setVisMode( Scene::VisLightPos, true ); + update(); } void GLView::setPlanarAngle( int angle ) { - planarAngle = float(angle) / 4; // Divide by 4 because sliders are -720 <-> 720 - lightVisTimer->start( lightVisTimeout ); - setVisMode( Scene::VisLightPos, true ); - update(); + planarAngle = float(angle) / 4; // Divide by 4 because sliders are -720 <-> 720 + lightVisTimer->start( lightVisTimeout ); + setVisMode( Scene::VisLightPos, true ); + update(); } void GLView::setDebugMode( DebugMode mode ) { - debugMode = mode; + debugMode = mode; } void GLView::setVisMode( Scene::VisMode mode, bool checked ) { - if ( checked ) - scene->visMode |= mode; - else - scene->visMode &= ~mode; + if ( checked ) + scene->visMode |= mode; + else + scene->visMode &= ~mode; - update(); + update(); } typedef void (Scene::* DrawFunc)( void ); -int indexAt( /*GLuint *buffer,*/ NifModel * model, Scene * scene, QList drawFunc, int cycle, const QPoint & pos, int & furn ) -{ - Q_UNUSED( model ); Q_UNUSED( cycle ); - // Color Key O(1) selection - // Open GL 3.0 says glRenderMode is deprecated - // ATI OpenGL API implementation of GL_SELECT corrupts NifSkope memory - // - // Create FBO for sharp edges and no shading. - // Texturing, blending, dithering, lighting and smooth shading should be disabled. - // The FBO can be used for the drawing operations to keep the drawing operations invisible to the user. - - GLint viewport[4]; - glGetIntegerv( GL_VIEWPORT, viewport ); - - // Create new FBO with multisampling disabled - QOpenGLFramebufferObjectFormat fboFmt; - fboFmt.setTextureTarget( GL_TEXTURE_2D ); - fboFmt.setInternalTextureFormat( GL_RGB32F_ARB ); - fboFmt.setAttachment( QOpenGLFramebufferObject::Attachment::CombinedDepthStencil ); - - QOpenGLFramebufferObject fbo( viewport[2], viewport[3], fboFmt ); - fbo.bind(); - - glEnable( GL_LIGHTING ); - glDisable( GL_MULTISAMPLE ); - glDisable( GL_MULTISAMPLE_ARB ); - glDisable( GL_LINE_SMOOTH ); - glDisable( GL_POINT_SMOOTH ); - glDisable( GL_POLYGON_SMOOTH ); - glDisable( GL_TEXTURE_1D ); - glDisable( GL_TEXTURE_2D ); - glDisable( GL_TEXTURE_3D ); - glDisable( GL_BLEND ); - glDisable( GL_DITHER ); - glDisable( GL_FOG ); - glDisable( GL_LIGHTING ); - glShadeModel( GL_FLAT ); - glEnable( GL_DEPTH_TEST ); - glDepthFunc( GL_LEQUAL ); - glClearColor( 0, 0, 0, 1 ); - glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); - - // Rasterize the scene - Node::SELECTING = 1; - for ( DrawFunc df : drawFunc ) { - (scene->*df)(); - } - Node::SELECTING = 0; - - fbo.release(); - - QImage img( fbo.toImage() ); - QColor pixel = img.pixel( pos ); +QImage GLView::renderIndexImage() +{ + makeCurrent(); + + glPushAttrib( GL_ALL_ATTRIB_BITS ); + glMatrixMode( GL_PROJECTION ); + glPushMatrix(); + glMatrixMode( GL_MODELVIEW ); + glPushMatrix(); + + glViewport( 0, 0, width(), height() ); + glProjection(); + + QList drawFuncs; + if ( scene->options & Scene::ShowCollision ) + drawFuncs << &Scene::drawHavok; + + if ( scene->options & Scene::ShowNodes ) + drawFuncs << &Scene::drawNodes; + + if ( scene->options & Scene::ShowMarkers ) + drawFuncs << &Scene::drawFurn; + + drawFuncs << &Scene::drawShapes; + // Color Key O(1) selection + // Open GL 3.0 says glRenderMode is deprecated + // ATI OpenGL API implementation of GL_SELECT corrupts NifSkope memory + // + // Create FBO for sharp edges and no shading. + // Texturing, blending, dithering, lighting and smooth shading should be disabled. + // The FBO can be used for the drawing operations to keep the drawing operations invisible to the user. + + GLint viewport[4]; + glGetIntegerv( GL_VIEWPORT, viewport ); + + // Create new FBO with multisampling disabled + QOpenGLFramebufferObjectFormat fboFmt; + fboFmt.setTextureTarget( GL_TEXTURE_2D ); + fboFmt.setInternalTextureFormat( GL_RGB32F_ARB ); + fboFmt.setAttachment( QOpenGLFramebufferObject::Attachment::CombinedDepthStencil ); + + QOpenGLFramebufferObject fbo( viewport[2], viewport[3], fboFmt ); + fbo.bind(); + + glEnable( GL_LIGHTING ); + glDisable( GL_MULTISAMPLE ); + glDisable( GL_MULTISAMPLE_ARB ); + glDisable( GL_LINE_SMOOTH ); + glDisable( GL_POINT_SMOOTH ); + glDisable( GL_POLYGON_SMOOTH ); + glDisable( GL_TEXTURE_1D ); + glDisable( GL_TEXTURE_2D ); + glDisable( GL_TEXTURE_3D ); + glDisable( GL_BLEND ); + glDisable( GL_DITHER ); + glDisable( GL_FOG ); + glDisable( GL_LIGHTING ); + glShadeModel( GL_FLAT ); + glEnable( GL_DEPTH_TEST ); + glDepthFunc( GL_LEQUAL ); + glClearColor( 0, 0, 0, 1 ); + glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); + + // Rasterize the scene + Node::SELECTING = 1; + for ( DrawFunc df : drawFuncs ) { + (scene->*df)(); + } + Node::SELECTING = 0; + + fbo.release(); + + glPopAttrib(); + glMatrixMode( GL_MODELVIEW ); + glPopMatrix(); + glMatrixMode( GL_PROJECTION ); + glPopMatrix(); + + return fbo.toImage(); +} + +QModelIndex GLView::sampleIndexImagePoint(const QImage& img, const QPoint& pos, NifModel* model) +{ + if (img.rect().contains(pos)) + { + QColor pixel = img.pixel( pos ); + return colorIndexToModelIndex(pixel, model); + } + return QModelIndex(); +} + +QVector GLView::sampleIndexImageCircle(const QImage& img, const QPoint& pos, float radius, NifModel* model) +{ + QVector indices; + if (img.rect().contains(pos)) + { + QPointF startf = (QPointF)pos - QPointF(radius, radius); + QPointF endf = (QPointF)pos + QPointF(radius, radius); + + QPoint start(std::round(startf.x()), std::round(startf.y())); + QPoint end(std::round(endf.x()), std::round(endf.y())); + + QVector2D origin(pos); + QVector colors; + for (int y=start.y(); y <= end.y(); y++) + { + for (int x=start.x(); x <= end.x(); x++) + { + if (img.rect().contains(x,y)) + { + float dist = (QVector2D(x,y) - origin).length(); + QColor pixel = img.pixel(x,y); + if (dist <= radius && !colors.contains(pixel)) + { + colors.push_back(pixel); + } + } + } + } + + for (const QColor& indColor : colors) + { + QModelIndex modelInd = colorIndexToModelIndex(indColor, model); + if (modelInd.isValid()) + indices.push_back(modelInd); + } + } + return indices; +} + +QModelIndex GLView::colorIndexToModelIndex(const QColor& color, NifModel* model) +{ + // Encode RGB to Int + int a = 0; + a |= color.red() << 0; + a |= color.green() << 8; + a |= color.blue() << 16; + + // Decode: + // R = (id & 0x000000FF) >> 0 + // G = (id & 0x0000FF00) >> 8 + // B = (id & 0x00FF0000) >> 16 + + int choose = COLORKEY2ID( a ); + int furn = -1; + + // Pick BSFurnitureMarker + if ( choose > 0 ) { + auto furnBlock = model->getBlock( model->index( 3, 0, model->getBlock( choose & 0x0ffff ) ), "BSFurnitureMarker" ); + + if ( furnBlock.isValid() ) { + furn = choose >> 16; + choose &= 0x0ffff; + } + } + + QModelIndex chooseIndex; + + if (scene->actionMode & Scene::Vertex ) { + // Vertex + int block = choose >> 16; + int vert = choose - (block << 16); + + auto shape = scene->shapes.value( block ); + if ( shape ) + chooseIndex = shape->vertexAt( vert ); + } else if ( choose != -1 ) { + // Block Index + chooseIndex = model->getBlock( choose ); + if ( furn != -1 ) { + // Furniture Row @ Block Index + chooseIndex = model->index( furn, 0, model->index( 3, 0, chooseIndex ) ); + } + } + + return chooseIndex; +} + +QModelIndex GLView::indexAt(const QPoint & pos, int cycle) +{ + Q_UNUSED(cycle); + + if ( !(model && isVisible() && height()) ) + return QModelIndex(); + + QImage img = renderIndexImage(); #ifndef QT_NO_DEBUG - img.save( "fbo.png" ); + img.save( "fbo.png" ); #endif - // Encode RGB to Int - int a = 0; - a |= pixel.red() << 0; - a |= pixel.green() << 8; - a |= pixel.blue() << 16; - - // Decode: - // R = (id & 0x000000FF) >> 0 - // G = (id & 0x0000FF00) >> 8 - // B = (id & 0x00FF0000) >> 16 - - int choose = COLORKEY2ID( a ); - - // Pick BSFurnitureMarker - if ( choose > 0 ) { - auto furnBlock = model->getBlock( model->index( 3, 0, model->getBlock( choose & 0x0ffff ) ), "BSFurnitureMarker" ); - - if ( furnBlock.isValid() ) { - furn = choose >> 16; - choose &= 0x0ffff; - } - } - - //qDebug() << "Key:" << a << " R" << pixel.red() << " G" << pixel.green() << " B" << pixel.blue(); - return choose; + return sampleIndexImagePoint(img, pos, model); } -QModelIndex GLView::indexAt( const QPoint & pos, int cycle ) +void GLView::center() { - if ( !(model && isVisible() && height()) ) - return QModelIndex(); - - makeCurrent(); + doCenter = true; + update(); +} - glPushAttrib( GL_ALL_ATTRIB_BITS ); - glMatrixMode( GL_PROJECTION ); - glPushMatrix(); - glMatrixMode( GL_MODELVIEW ); - glPushMatrix(); +void GLView::move( float x, float y, float z ) +{ + Pos += Matrix::euler( Rot[0] / 180 * PI, Rot[1] / 180 * PI, Rot[2] / 180 * PI ).inverted() * Vector3( x, y, z ); + updateViewpoint(); + update(); +} - glViewport( 0, 0, width(), height() ); - glProjection( pos.x(), pos.y() ); +void GLView::rotate( float x, float y, float z ) +{ + Rot += Vector3( x, y, z ); + updateViewpoint(); + update(); +} - QList df; +void GLView::zoom( float z ) +{ + Zoom *= z; - if ( scene->options & Scene::ShowCollision ) - df << &Scene::drawHavok; + if ( Zoom < ZOOM_MIN ) + Zoom = ZOOM_MIN; - if ( scene->options & Scene::ShowNodes ) - df << &Scene::drawNodes; + if ( Zoom > ZOOM_MAX ) + Zoom = ZOOM_MAX; - if ( scene->options & Scene::ShowMarkers ) - df << &Scene::drawFurn; + update(); +} - df << &Scene::drawShapes; +void GLView::setVertexPaintSettings(GLView::PaintSettings settings) +{ + cfg.vertexPaintSettings = settings; + update(); +} - int choose = -1, furn = -1; - choose = ::indexAt( model, scene, df, cycle, pos, /*out*/ furn ); +void GLView::startVertexPaint(const QPoint& point) +{ + mousePaint = true; + mousePaintVerts.clear(); + mousePaintHitDetectImg = renderIndexImage(); + vertexPaint(point); + QCursor cursor(Qt::BlankCursor); + QApplication::setOverrideCursor(cursor); + QApplication::changeOverrideCursor(cursor); +} - glPopAttrib(); - glMatrixMode( GL_MODELVIEW ); - glPopMatrix(); - glMatrixMode( GL_PROJECTION ); - glPopMatrix(); +void GLView::vertexPaint(const QPoint& point) +{ + bool updated = false; + if (mousePaint) + { + // Let's do an absolutely shit job and act like it's decent + auto inds = sampleIndexImageCircle(mousePaintHitDetectImg, point, cfg.vertexPaintSettings.brushSize, model); + Color4 brushColorAndOpacity = cfg.vertexPaintSettings.brushColor * cfg.vertexPaintSettings.brushOpacity; - QModelIndex chooseIndex; + // Expand ind to include all vertices in the same mesh with the same location + auto count = inds.size(); + for (int i=0; i < count; ++i) + { + // Get the X, Y, Z location of this vertex + Vector3 indLoc = model->get(inds[i]); + QModelIndex vertexCollection = inds[i].parent().parent(); - if ( scene->selMode & Scene::SelVertex ) { - // Vertex - int block = choose >> 16; - int vert = choose - (block << 16); + // Get all the children of the vertex container + int vertexCount = model->rowCount(vertexCollection); + for (int j=0; j < vertexCount; ++j) + { + QModelIndex siblingId = vertexCollection.child(j,0); - auto shape = scene->shapes.value( block ); - if ( shape ) - chooseIndex = shape->vertexAt( vert ); - } else if ( choose != -1 ) { - // Block Index - chooseIndex = model->getBlock( choose ); + // Get the position of each child + Vector3 siblingLocation = model->get(siblingId, "Vertex"); - if ( furn != -1 ) { - // Furniture Row @ Block Index - chooseIndex = model->index( furn, 0, model->index( 3, 0, chooseIndex ) ); - } - } + // If XYZ matches, add sibling to inds list + if (siblingLocation == indLoc) + { + inds.push_back(model->getIndex(siblingId, "Vertex")); + } + } + } - return chooseIndex; -} + for (const auto& ind : inds) + { + if ( ind.isValid() && !mousePaintVerts.contains(ind)) + { + mousePaintVerts.push_back(ind); + Color4 base = model->get(ind.parent(), "Vertex Colors"); + Color4 blend; -void GLView::center() -{ - doCenter = true; - update(); -} + if (cfg.vertexPaintSettings.brushMode == PaintBlendMode::BlendNormal) + { + blend = brushColorAndOpacity + (base * (Color4() - cfg.vertexPaintSettings.brushOpacity)); + } + else if (cfg.vertexPaintSettings.brushMode == PaintBlendMode::BlendMultiply) + { + blend = (brushColorAndOpacity + (Color4() - cfg.vertexPaintSettings.brushOpacity)) * base; + } + else if (cfg.vertexPaintSettings.brushMode == PaintBlendMode::BlendAdd) + { + blend = brushColorAndOpacity + base; + } -void GLView::move( float x, float y, float z ) -{ - Pos += Matrix::euler( Rot[0] / 180 * PI, Rot[1] / 180 * PI, Rot[2] / 180 * PI ).inverted() * Vector3( x, y, z ); - updateViewpoint(); - update(); -} + model->set(ind.parent(), "Vertex Colors", ByteColor4::fromColor4(blend)); + updated = true; + } + } + } -void GLView::rotate( float x, float y, float z ) -{ - Rot += Vector3( x, y, z ); - updateViewpoint(); - update(); + if (!updated) + { + update(); + } } -void GLView::zoom( float z ) +void GLView::endVertexPaint() { - Zoom *= z; - - if ( Zoom < ZOOM_MIN ) - Zoom = ZOOM_MIN; - - if ( Zoom > ZOOM_MAX ) - Zoom = ZOOM_MAX; - - update(); + mousePaint = false; + mousePaintVerts.clear(); + QApplication::restoreOverrideCursor(); } void GLView::setCenter() { - Node * node = scene->getNode( model, scene->currentBlock ); + Node * node = scene->getNode( model, scene->currentBlock ); - if ( node ) { - // Center on selected node - BoundSphere bs = node->bounds(); + if ( node ) { + // Center on selected node + BoundSphere bs = node->bounds(); - this->setPosition( -bs.center ); + this->setPosition( -bs.center ); - if ( bs.radius > 0 ) { - setDistance( bs.radius * 1.2 ); - } - } else { - // Center on entire mesh - BoundSphere bs = scene->bounds(); + if ( bs.radius > 0 ) { + setDistance( bs.radius * 1.2 ); + } + } else { + // Center on entire mesh + BoundSphere bs = scene->bounds(); - if ( bs.radius < 1 ) - bs.radius = 1024.0; + if ( bs.radius < 1 ) + bs.radius = 1024.0; - setDistance( bs.radius * 1.2 ); - setZoom( 1.0 ); + setDistance( bs.radius * 1.2 ); + setZoom( 1.0 ); - setPosition( -bs.center ); + setPosition( -bs.center ); - setOrientation( view ); - } + setOrientation( view ); + } } void GLView::setDistance( float x ) { - Dist = x; - update(); + Dist = x; + update(); } void GLView::setPosition( float x, float y, float z ) { - Pos = { x, y, z }; - update(); + Pos = { x, y, z }; + update(); } void GLView::setPosition( const Vector3 & v ) { - Pos = v; - update(); + Pos = v; + update(); } void GLView::setProjection( bool isPersp ) { - perspectiveMode = isPersp; - update(); + perspectiveMode = isPersp; + update(); } void GLView::setRotation( float x, float y, float z ) { - Rot = { x, y, z }; - update(); + Rot = { x, y, z }; + update(); } void GLView::setZoom( float z ) { - Zoom = z; - update(); + Zoom = z; + update(); } void GLView::flipOrientation() { - ViewState tmp = ViewDefault; - - switch ( view ) { - case ViewTop: - tmp = ViewBottom; - break; - case ViewBottom: - tmp = ViewTop; - break; - case ViewLeft: - tmp = ViewRight; - break; - case ViewRight: - tmp = ViewLeft; - break; - case ViewFront: - tmp = ViewBack; - break; - case ViewBack: - tmp = ViewFront; - break; - case ViewUser: - default: - { - // TODO: Flip any other view also? - } - break; - } - - setOrientation( tmp, false ); + ViewState tmp = ViewDefault; + + switch ( view ) { + case ViewTop: + tmp = ViewBottom; + break; + case ViewBottom: + tmp = ViewTop; + break; + case ViewLeft: + tmp = ViewRight; + break; + case ViewRight: + tmp = ViewLeft; + break; + case ViewFront: + tmp = ViewBack; + break; + case ViewBack: + tmp = ViewFront; + break; + case ViewUser: + default: + { + // TODO: Flip any other view also? + } + break; + } + + setOrientation( tmp, false ); } void GLView::setOrientation( GLView::ViewState state, bool recenter ) { - if ( state == view ) - return; - - switch ( state ) { - case ViewBottom: - setRotation( 180, 0, 0 ); // Bottom - break; - case ViewTop: - setRotation( 0, 0, 0 ); // Top - break; - case ViewBack: - setRotation( -90, 0, 0 ); // Back - break; - case ViewFront: - setRotation( -90, 0, 180 ); // Front - break; - case ViewRight: - setRotation( -90, 0, 90 ); // Right - break; - case ViewLeft: - setRotation( -90, 0, -90 ); // Left - break; - default: - break; - } - - view = state; - - // Recenter - if ( recenter ) - center(); + if ( state == view ) + return; + + switch ( state ) { + case ViewBottom: + setRotation( 180, 0, 0 ); // Bottom + break; + case ViewTop: + setRotation( 0, 0, 0 ); // Top + break; + case ViewBack: + setRotation( -90, 0, 0 ); // Back + break; + case ViewFront: + setRotation( -90, 0, 180 ); // Front + break; + case ViewRight: + setRotation( -90, 0, 90 ); // Right + break; + case ViewLeft: + setRotation( -90, 0, -90 ); // Left + break; + default: + break; + } + + view = state; + + // Recenter + if ( recenter ) + center(); } void GLView::updateViewpoint() { - switch ( view ) { - case ViewTop: - case ViewBottom: - case ViewLeft: - case ViewRight: - case ViewFront: - case ViewBack: - case ViewUser: - emit viewpointChanged(); - break; - default: - break; - } + switch ( view ) { + case ViewTop: + case ViewBottom: + case ViewLeft: + case ViewRight: + case ViewFront: + case ViewBack: + case ViewUser: + emit viewpointChanged(); + break; + default: + break; + } } void GLView::flush() { - if ( textures ) - textures->flush(); + if ( textures ) + textures->flush(); } @@ -1108,101 +1270,101 @@ void GLView::flush() void GLView::setNif( NifModel * nif ) { - if ( model ) { - // disconnect( model ) may not work with new Qt5 syntax... - // it says the calls need to remain symmetric to the connect() ones. - // Otherwise, use QMetaObject::Connection - disconnect( model ); - } + if ( model ) { + // disconnect( model ) may not work with new Qt5 syntax... + // it says the calls need to remain symmetric to the connect() ones. + // Otherwise, use QMetaObject::Connection + disconnect( model ); + } - model = nif; + model = nif; - if ( model ) { - connect( model, &NifModel::dataChanged, this, &GLView::dataChanged ); - connect( model, &NifModel::linksChanged, this, &GLView::modelLinked ); - connect( model, &NifModel::modelReset, this, &GLView::modelChanged ); - connect( model, &NifModel::destroyed, this, &GLView::modelDestroyed ); - } + if ( model ) { + connect( model, &NifModel::dataChanged, this, &GLView::dataChanged ); + connect( model, &NifModel::linksChanged, this, &GLView::modelLinked ); + connect( model, &NifModel::modelReset, this, &GLView::modelChanged ); + connect( model, &NifModel::destroyed, this, &GLView::modelDestroyed ); + } - doCompile = true; + doCompile = true; } void GLView::setCurrentIndex( const QModelIndex & index ) { - if ( !( model && index.model() == model ) ) - return; + if ( !( model && index.model() == model ) ) + return; - scene->currentBlock = model->getBlock( index ); - scene->currentIndex = index.sibling( index.row(), 0 ); + scene->currentBlock = model->getBlock( index ); + scene->currentIndex = index.sibling( index.row(), 0 ); - update(); + update(); } QModelIndex parent( QModelIndex ix, QModelIndex xi ) { - ix = ix.sibling( ix.row(), 0 ); - xi = xi.sibling( xi.row(), 0 ); + ix = ix.sibling( ix.row(), 0 ); + xi = xi.sibling( xi.row(), 0 ); - while ( ix.isValid() ) { - QModelIndex x = xi; + while ( ix.isValid() ) { + QModelIndex x = xi; - while ( x.isValid() ) { - if ( ix == x ) - return ix; + while ( x.isValid() ) { + if ( ix == x ) + return ix; - x = x.parent(); - } + x = x.parent(); + } - ix = ix.parent(); - } + ix = ix.parent(); + } - return QModelIndex(); + return QModelIndex(); } void GLView::dataChanged( const QModelIndex & idx, const QModelIndex & xdi ) { - if ( doCompile ) - return; + if ( doCompile ) + return; - QModelIndex ix = idx; + QModelIndex ix = idx; - if ( idx == xdi ) { - if ( idx.column() != 0 ) - ix = idx.sibling( idx.row(), 0 ); - } else { - ix = ::parent( idx, xdi ); - } + if ( idx == xdi ) { + if ( idx.column() != 0 ) + ix = idx.sibling( idx.row(), 0 ); + } else { + ix = ::parent( idx, xdi ); + } - if ( ix.isValid() ) { - scene->update( model, idx ); - update(); - } else { - modelChanged(); - } + if ( ix.isValid() ) { + scene->update( model, idx ); + update(); + } else { + modelChanged(); + } } void GLView::modelChanged() { - if ( doCompile ) - return; + if ( doCompile ) + return; - doCompile = true; - //doCenter = true; - update(); + doCompile = true; + //doCenter = true; + update(); } void GLView::modelLinked() { - if ( doCompile ) - return; + if ( doCompile ) + return; - doCompile = true; //scene->update( model, QModelIndex() ); - update(); + doCompile = true; //scene->update( model, QModelIndex() ); + update(); } void GLView::modelDestroyed() { - setNif( nullptr ); + setNif( nullptr ); } @@ -1212,614 +1374,624 @@ void GLView::modelDestroyed() void GLView::setSceneTime( float t ) { - time = t; - update(); - emit sceneTimeChanged( time, scene->timeMin(), scene->timeMax() ); + time = t; + update(); + emit sceneTimeChanged( time, scene->timeMin(), scene->timeMax() ); } void GLView::setSceneSequence( const QString & seqname ) { - // Update UI - QAction * action = qobject_cast(sender()); - if ( !action ) { - // Called from self and not UI - emit sequenceChanged( seqname ); - } - - scene->setSequence( seqname ); - time = scene->timeMin(); - emit sceneTimeChanged( time, scene->timeMin(), scene->timeMax() ); - update(); + // Update UI + QAction * action = qobject_cast(sender()); + if ( !action ) { + // Called from self and not UI + emit sequenceChanged( seqname ); + } + + scene->setSequence( seqname ); + time = scene->timeMin(); + emit sceneTimeChanged( time, scene->timeMin(), scene->timeMax() ); + update(); } // TODO: Multiple user views, ala Recent Files void GLView::saveUserView() { - QSettings settings; - settings.beginGroup( "GLView" ); - settings.beginGroup( "User View" ); - settings.setValue( "RotX", Rot[0] ); - settings.setValue( "RotY", Rot[1] ); - settings.setValue( "RotZ", Rot[2] ); - settings.setValue( "PosX", Pos[0] ); - settings.setValue( "PosY", Pos[1] ); - settings.setValue( "PosZ", Pos[2] ); - settings.setValue( "Dist", Dist ); - settings.endGroup(); - settings.endGroup(); + QSettings settings; + settings.beginGroup( "GLView" ); + settings.beginGroup( "User View" ); + settings.setValue( "RotX", Rot[0] ); + settings.setValue( "RotY", Rot[1] ); + settings.setValue( "RotZ", Rot[2] ); + settings.setValue( "PosX", Pos[0] ); + settings.setValue( "PosY", Pos[1] ); + settings.setValue( "PosZ", Pos[2] ); + settings.setValue( "Dist", Dist ); + settings.endGroup(); + settings.endGroup(); } void GLView::loadUserView() { - QSettings settings; - settings.beginGroup( "GLView" ); - settings.beginGroup( "User View" ); - setRotation( settings.value( "RotX" ).toDouble(), settings.value( "RotY" ).toDouble(), settings.value( "RotZ" ).toDouble() ); - setPosition( settings.value( "PosX" ).toDouble(), settings.value( "PosY" ).toDouble(), settings.value( "PosZ" ).toDouble() ); - setDistance( settings.value( "Dist" ).toDouble() ); - settings.endGroup(); - settings.endGroup(); + QSettings settings; + settings.beginGroup( "GLView" ); + settings.beginGroup( "User View" ); + setRotation( settings.value( "RotX" ).toDouble(), settings.value( "RotY" ).toDouble(), settings.value( "RotZ" ).toDouble() ); + setPosition( settings.value( "PosX" ).toDouble(), settings.value( "PosY" ).toDouble(), settings.value( "PosZ" ).toDouble() ); + setDistance( settings.value( "Dist" ).toDouble() ); + settings.endGroup(); + settings.endGroup(); } void GLView::advanceGears() { - QTime t = QTime::currentTime(); - float dT = lastTime.msecsTo( t ) / 1000.0; - dT = (dT < 0) ? 0 : ((dT > 1.0) ? 1.0 : dT); - - lastTime = t; - - if ( !isVisible() ) - return; - - if ( ( animState & AnimEnabled ) && ( animState & AnimPlay ) - && scene->timeMin() != scene->timeMax() ) - { - time += dT; - - if ( time > scene->timeMax() ) { - if ( ( animState & AnimSwitch ) && !scene->animGroups.isEmpty() ) { - int ix = scene->animGroups.indexOf( scene->animGroup ); - - if ( ++ix >= scene->animGroups.count() ) - ix -= scene->animGroups.count(); - - setSceneSequence( scene->animGroups.value( ix ) ); - } else if ( animState & AnimLoop ) { - time = scene->timeMin(); - } else { - // Animation has completed and is not looping - // or cycling through animations. - // Reset time and state and then inform UI it has stopped. - time = scene->timeMin(); - animState &= ~AnimPlay; - emit sequenceStopped(); - } - } else { - // Animation is not done yet - } - - emit sceneTimeChanged( time, scene->timeMin(), scene->timeMax() ); - update(); - } - - // TODO: Some kind of input class for choosing the appropriate - // keys based on user preferences of what app they would like to - // emulate for the control scheme - // Rotation - if ( kbd[ Qt::Key_Up ] ) rotate( -cfg.rotSpd * dT, 0, 0 ); - if ( kbd[ Qt::Key_Down ] ) rotate( +cfg.rotSpd * dT, 0, 0 ); - if ( kbd[ Qt::Key_Left ] ) rotate( 0, 0, -cfg.rotSpd * dT ); - if ( kbd[ Qt::Key_Right ] ) rotate( 0, 0, +cfg.rotSpd * dT ); - - // Movement - if ( kbd[ Qt::Key_A ] ) move( +cfg.moveSpd * dT, 0, 0 ); - if ( kbd[ Qt::Key_D ] ) move( -cfg.moveSpd * dT, 0, 0 ); - if ( kbd[ Qt::Key_W ] ) move( 0, 0, +cfg.moveSpd * dT ); - if ( kbd[ Qt::Key_S ] ) move( 0, 0, -cfg.moveSpd * dT ); - //if ( kbd[ Qt::Key_F ] ) move( 0, +MOV_SPD * dT, 0 ); - //if ( kbd[ Qt::Key_R ] ) move( 0, -MOV_SPD * dT, 0 ); - - // Zoom - if ( kbd[ Qt::Key_Q ] ) setDistance( Dist * 1.0 / 1.1 ); - if ( kbd[ Qt::Key_E ] ) setDistance( Dist * 1.1 ); - - // Focal Length - if ( kbd[ Qt::Key_PageUp ] ) zoom( 1.1f ); - if ( kbd[ Qt::Key_PageDown ] ) zoom( 1 / 1.1f ); - - if ( mouseMov[0] != 0 || mouseMov[1] != 0 || mouseMov[2] != 0 ) { - move( mouseMov[0], mouseMov[1], mouseMov[2] ); - mouseMov = Vector3(); - } - - if ( mouseRot[0] != 0 || mouseRot[1] != 0 || mouseRot[2] != 0 ) { - rotate( mouseRot[0], mouseRot[1], mouseRot[2] ); - mouseRot = Vector3(); - } + QTime t = QTime::currentTime(); + float dT = lastTime.msecsTo( t ) / 1000.0; + dT = (dT < 0) ? 0 : ((dT > 1.0) ? 1.0 : dT); + + lastTime = t; + + if ( !isVisible() ) + return; + + if ( ( animState & AnimEnabled ) && ( animState & AnimPlay ) + && scene->timeMin() != scene->timeMax() ) + { + time += dT; + + if ( time > scene->timeMax() ) { + if ( ( animState & AnimSwitch ) && !scene->animGroups.isEmpty() ) { + int ix = scene->animGroups.indexOf( scene->animGroup ); + + if ( ++ix >= scene->animGroups.count() ) + ix -= scene->animGroups.count(); + + setSceneSequence( scene->animGroups.value( ix ) ); + } else if ( animState & AnimLoop ) { + time = scene->timeMin(); + } else { + // Animation has completed and is not looping + // or cycling through animations. + // Reset time and state and then inform UI it has stopped. + time = scene->timeMin(); + animState &= ~AnimPlay; + emit sequenceStopped(); + } + } else { + // Animation is not done yet + } + + emit sceneTimeChanged( time, scene->timeMin(), scene->timeMax() ); + update(); + } + + // TODO: Some kind of input class for choosing the appropriate + // keys based on user preferences of what app they would like to + // emulate for the control scheme + // Rotation + if ( kbd[ Qt::Key_Up ] ) rotate( -cfg.rotSpd * dT, 0, 0 ); + if ( kbd[ Qt::Key_Down ] ) rotate( +cfg.rotSpd * dT, 0, 0 ); + if ( kbd[ Qt::Key_Left ] ) rotate( 0, 0, -cfg.rotSpd * dT ); + if ( kbd[ Qt::Key_Right ] ) rotate( 0, 0, +cfg.rotSpd * dT ); + + // Movement + if ( kbd[ Qt::Key_A ] ) move( +cfg.moveSpd * dT, 0, 0 ); + if ( kbd[ Qt::Key_D ] ) move( -cfg.moveSpd * dT, 0, 0 ); + if ( kbd[ Qt::Key_W ] ) move( 0, 0, +cfg.moveSpd * dT ); + if ( kbd[ Qt::Key_S ] ) move( 0, 0, -cfg.moveSpd * dT ); + //if ( kbd[ Qt::Key_F ] ) move( 0, +MOV_SPD * dT, 0 ); + //if ( kbd[ Qt::Key_R ] ) move( 0, -MOV_SPD * dT, 0 ); + + // Zoom + if ( kbd[ Qt::Key_Q ] ) setDistance( Dist * 1.0 / 1.1 ); + if ( kbd[ Qt::Key_E ] ) setDistance( Dist * 1.1 ); + + // Focal Length + if ( kbd[ Qt::Key_PageUp ] ) zoom( 1.1f ); + if ( kbd[ Qt::Key_PageDown ] ) zoom( 1 / 1.1f ); + + if ( mouseMov[0] != 0 || mouseMov[1] != 0 || mouseMov[2] != 0 ) { + move( mouseMov[0], mouseMov[1], mouseMov[2] ); + mouseMov = Vector3(); + } + + if ( mouseRot[0] != 0 || mouseRot[1] != 0 || mouseRot[2] != 0 ) { + rotate( mouseRot[0], mouseRot[1], mouseRot[2] ); + mouseRot = Vector3(); + } } // TODO: Separate widget void GLView::saveImage() { - auto dlg = new QDialog( qApp->activeWindow() ); - QGridLayout * lay = new QGridLayout( dlg ); - dlg->setWindowTitle( tr( "Save View" ) ); - dlg->setLayout( lay ); - dlg->setMinimumWidth( 400 ); - - QString date = QDateTime::currentDateTime().toString( "yyyyMMdd_HH-mm-ss" ); - QString name = model->getFilename(); - - QString nifFolder = model->getFolder(); - // TODO: Default extension in Settings - QString filename = name + (!name.isEmpty() ? "_" : "") + date + ".jpg"; - - // Default: NifSkope directory - // TODO: User-configurable default screenshot path in Options - QString nifskopePath = "screenshots/" + filename; - // Absolute: NIF directory - QString nifPath = nifFolder + (!nifFolder.isEmpty() ? "/" : "") + filename; - - FileSelector * file = new FileSelector( FileSelector::SaveFile, tr( "File" ), QBoxLayout::LeftToRight ); - file->setParent( dlg ); - // TODO: Default extension in Settings - file->setFilter( { "Images (*.jpg *.png *.webp *.bmp)", "JPEG (*.jpg)", "PNG (*.png)", "WebP (*.webp)", "BMP (*.bmp)" } ); - file->setFile( nifskopePath ); - lay->addWidget( file, 0, 0, 1, -1 ); - - auto grpDir = new QButtonGroup( dlg ); - - QRadioButton * nifskopeDir = new QRadioButton( tr( "NifSkope Directory" ), dlg ); - nifskopeDir->setChecked( true ); - nifskopeDir->setToolTip( tr( "Save to NifSkope screenshots directory" ) ); - - QRadioButton * niffileDir = new QRadioButton( tr( "NIF Directory" ), dlg ); - niffileDir->setChecked( false ); - niffileDir->setDisabled( nifFolder.isEmpty() ); - niffileDir->setToolTip( tr( "Save to NIF file directory" ) ); - - grpDir->addButton( nifskopeDir ); - grpDir->addButton( niffileDir ); - grpDir->setExclusive( true ); - - lay->addWidget( nifskopeDir, 1, 0, 1, 1 ); - lay->addWidget( niffileDir, 1, 1, 1, 1 ); - - // Save JPEG Quality - QSettings settings; - int jpegQuality = settings.value( "JPEG/Quality", 90 ).toInt(); - settings.setValue( "JPEG/Quality", jpegQuality ); - - QHBoxLayout * pixBox = new QHBoxLayout; - pixBox->setAlignment( Qt::AlignRight ); - QSpinBox * pixQuality = new QSpinBox( dlg ); - pixQuality->setRange( -1, 100 ); - pixQuality->setSingleStep( 10 ); - pixQuality->setValue( jpegQuality ); - pixQuality->setSpecialValueText( tr( "Auto" ) ); - pixQuality->setMaximumWidth( pixQuality->minimumSizeHint().width() ); - pixBox->addWidget( new QLabel( tr( "JPEG Quality" ), dlg ) ); - pixBox->addWidget( pixQuality ); - lay->addLayout( pixBox, 1, 2, Qt::AlignRight ); - - - // Image Size radio button lambda - auto btnSize = [dlg]( const QString & name ) { - auto btn = new QRadioButton( name, dlg ); - btn->setCheckable( true ); - - return btn; - }; - - // Get max viewport size for platform - GLint dims; - glGetIntegerv( GL_MAX_VIEWPORT_DIMS, &dims ); - int maxSize = dims; - - // Default size - auto btnOneX = btnSize( "1x" ); - btnOneX->setChecked( true ); - // Disable any of these that would exceed the max viewport size of the platform - auto btnTwoX = btnSize( "2x" ); - btnTwoX->setDisabled( (width() * 2) > maxSize || (height() * 2) > maxSize ); - auto btnFourX = btnSize( "4x" ); - btnFourX->setDisabled( (width() * 4) > maxSize || (height() * 4) > maxSize ); - auto btnEightX = btnSize( "8x" ); - btnEightX->setDisabled( (width() * 8) > maxSize || (height() * 8) > maxSize ); - - - auto grpBox = new QGroupBox( tr( "Image Size" ), dlg ); - auto grpBoxLayout = new QHBoxLayout; - grpBoxLayout->addWidget( btnOneX ); - grpBoxLayout->addWidget( btnTwoX ); - grpBoxLayout->addWidget( btnFourX ); - grpBoxLayout->addWidget( btnEightX ); - grpBoxLayout->addWidget( new QLabel( "Caution:
4x and 8x may be memory intensive.", dlg ) ); - grpBoxLayout->addStretch( 1 ); - grpBox->setLayout( grpBoxLayout ); - - auto grpSize = new QButtonGroup( dlg ); - grpSize->addButton( btnOneX, 1 ); - grpSize->addButton( btnTwoX, 2 ); - grpSize->addButton( btnFourX, 4 ); - grpSize->addButton( btnEightX, 8 ); - - grpSize->setExclusive( true ); - - lay->addWidget( grpBox, 2, 0, 1, -1 ); - - - QHBoxLayout * hBox = new QHBoxLayout; - QPushButton * btnOk = new QPushButton( tr( "Save" ), dlg ); - QPushButton * btnCancel = new QPushButton( tr( "Cancel" ), dlg ); - hBox->addWidget( btnOk ); - hBox->addWidget( btnCancel ); - lay->addLayout( hBox, 3, 0, 1, -1 ); - - // Set FileSelector to NifSkope dir (relative) - connect( nifskopeDir, &QRadioButton::clicked, [=]() - { - file->setText( nifskopePath ); - file->setFile( nifskopePath ); - } - ); - // Set FileSelector to NIF File dir (absolute) - connect( niffileDir, &QRadioButton::clicked, [=]() - { - file->setText( nifPath ); - file->setFile( nifPath ); - } - ); - - // Validate on OK - connect( btnOk, &QPushButton::clicked, [&]() - { - // Save JPEG Quality - QSettings settings; - settings.setValue( "JPEG/Quality", pixQuality->value() ); - - // TODO: Set up creation of screenshots directory in Options - if ( nifskopeDir->isChecked() ) { - QDir workingDir; - workingDir.mkpath( "screenshots" ); - } - - // Supersampling - int ss = grpSize->checkedId(); - - int w, h; - - w = width(); - h = height(); - - // Resize viewport for supersampling - if ( ss > 1 ) { - w *= ss; - h *= ss; - - resizeGL( w, h ); - } - - QOpenGLFramebufferObjectFormat fboFmt; - fboFmt.setTextureTarget( GL_TEXTURE_2D ); - fboFmt.setInternalTextureFormat( GL_RGB ); - fboFmt.setMipmap( false ); - fboFmt.setAttachment( QOpenGLFramebufferObject::Attachment::Depth ); - fboFmt.setSamples( 16 / ss ); - - QOpenGLFramebufferObject fbo( w, h, fboFmt ); - fbo.bind(); - - update(); - updateGL(); - - fbo.release(); - - QImage * img = new QImage(fbo.toImage()); - - // Return viewport to original size - if ( ss > 1 ) - resizeGL( width(), height() ); - - - QImageWriter writer( file->file() ); - - // Set Compression for formats that can use it - writer.setCompression( 1 ); - - // Handle JPEG/WebP Quality exclusively - // PNG will not use compression if Quality is set - if ( file->file().endsWith( ".jpg", Qt::CaseInsensitive ) ) { - writer.setFormat( "jpg" ); - writer.setQuality( 50 + pixQuality->value() / 2 ); - } else if ( file->file().endsWith( ".webp", Qt::CaseInsensitive ) ) { - writer.setFormat( "webp" ); - writer.setQuality( 75 + pixQuality->value() / 4 ); - } - - if ( writer.write( *img ) ) { - dlg->accept(); - } else { - Message::critical( this, tr( "Could not save %1" ).arg( file->file() ) ); - } - - delete img; - img = nullptr; - } - ); - connect( btnCancel, &QPushButton::clicked, dlg, &QDialog::reject ); - - if ( dlg->exec() != QDialog::Accepted ) { - return; - } -} - - -/* - * QWidget Event Handlers + auto dlg = new QDialog( qApp->activeWindow() ); + QGridLayout * lay = new QGridLayout( dlg ); + dlg->setWindowTitle( tr( "Save View" ) ); + dlg->setLayout( lay ); + dlg->setMinimumWidth( 400 ); + + QString date = QDateTime::currentDateTime().toString( "yyyyMMdd_HH-mm-ss" ); + QString name = model->getFilename(); + + QString nifFolder = model->getFolder(); + // TODO: Default extension in Settings + QString filename = name + (!name.isEmpty() ? "_" : "") + date + ".jpg"; + + // Default: NifSkope directory + // TODO: User-configurable default screenshot path in Options + QString nifskopePath = "screenshots/" + filename; + // Absolute: NIF directory + QString nifPath = nifFolder + (!nifFolder.isEmpty() ? "/" : "") + filename; + + FileSelector * file = new FileSelector( FileSelector::SaveFile, tr( "File" ), QBoxLayout::LeftToRight ); + file->setParent( dlg ); + // TODO: Default extension in Settings + file->setFilter( { "Images (*.jpg *.png *.webp *.bmp)", "JPEG (*.jpg)", "PNG (*.png)", "WebP (*.webp)", "BMP (*.bmp)" } ); + file->setFile( nifskopePath ); + lay->addWidget( file, 0, 0, 1, -1 ); + + auto grpDir = new QButtonGroup( dlg ); + + QRadioButton * nifskopeDir = new QRadioButton( tr( "NifSkope Directory" ), dlg ); + nifskopeDir->setChecked( true ); + nifskopeDir->setToolTip( tr( "Save to NifSkope screenshots directory" ) ); + + QRadioButton * niffileDir = new QRadioButton( tr( "NIF Directory" ), dlg ); + niffileDir->setChecked( false ); + niffileDir->setDisabled( nifFolder.isEmpty() ); + niffileDir->setToolTip( tr( "Save to NIF file directory" ) ); + + grpDir->addButton( nifskopeDir ); + grpDir->addButton( niffileDir ); + grpDir->setExclusive( true ); + + lay->addWidget( nifskopeDir, 1, 0, 1, 1 ); + lay->addWidget( niffileDir, 1, 1, 1, 1 ); + + // Save JPEG Quality + QSettings settings; + int jpegQuality = settings.value( "JPEG/Quality", 90 ).toInt(); + settings.setValue( "JPEG/Quality", jpegQuality ); + + QHBoxLayout * pixBox = new QHBoxLayout; + pixBox->setAlignment( Qt::AlignRight ); + QSpinBox * pixQuality = new QSpinBox( dlg ); + pixQuality->setRange( -1, 100 ); + pixQuality->setSingleStep( 10 ); + pixQuality->setValue( jpegQuality ); + pixQuality->setSpecialValueText( tr( "Auto" ) ); + pixQuality->setMaximumWidth( pixQuality->minimumSizeHint().width() ); + pixBox->addWidget( new QLabel( tr( "JPEG Quality" ), dlg ) ); + pixBox->addWidget( pixQuality ); + lay->addLayout( pixBox, 1, 2, Qt::AlignRight ); + + + // Image Size radio button lambda + auto btnSize = [dlg]( const QString & name ) { + auto btn = new QRadioButton( name, dlg ); + btn->setCheckable( true ); + + return btn; + }; + + // Get max viewport size for platform + GLint dims; + glGetIntegerv( GL_MAX_VIEWPORT_DIMS, &dims ); + int maxSize = dims; + + // Default size + auto btnOneX = btnSize( "1x" ); + btnOneX->setChecked( true ); + // Disable any of these that would exceed the max viewport size of the platform + auto btnTwoX = btnSize( "2x" ); + btnTwoX->setDisabled( (width() * 2) > maxSize || (height() * 2) > maxSize ); + auto btnFourX = btnSize( "4x" ); + btnFourX->setDisabled( (width() * 4) > maxSize || (height() * 4) > maxSize ); + auto btnEightX = btnSize( "8x" ); + btnEightX->setDisabled( (width() * 8) > maxSize || (height() * 8) > maxSize ); + + + auto grpBox = new QGroupBox( tr( "Image Size" ), dlg ); + auto grpBoxLayout = new QHBoxLayout; + grpBoxLayout->addWidget( btnOneX ); + grpBoxLayout->addWidget( btnTwoX ); + grpBoxLayout->addWidget( btnFourX ); + grpBoxLayout->addWidget( btnEightX ); + grpBoxLayout->addWidget( new QLabel( "Caution:
4x and 8x may be memory intensive.", dlg ) ); + grpBoxLayout->addStretch( 1 ); + grpBox->setLayout( grpBoxLayout ); + + auto grpSize = new QButtonGroup( dlg ); + grpSize->addButton( btnOneX, 1 ); + grpSize->addButton( btnTwoX, 2 ); + grpSize->addButton( btnFourX, 4 ); + grpSize->addButton( btnEightX, 8 ); + + grpSize->setExclusive( true ); + + lay->addWidget( grpBox, 2, 0, 1, -1 ); + + + QHBoxLayout * hBox = new QHBoxLayout; + QPushButton * btnOk = new QPushButton( tr( "Save" ), dlg ); + QPushButton * btnCancel = new QPushButton( tr( "Cancel" ), dlg ); + hBox->addWidget( btnOk ); + hBox->addWidget( btnCancel ); + lay->addLayout( hBox, 3, 0, 1, -1 ); + + // Set FileSelector to NifSkope dir (relative) + connect( nifskopeDir, &QRadioButton::clicked, [=]() + { + file->setText( nifskopePath ); + file->setFile( nifskopePath ); + } + ); + // Set FileSelector to NIF File dir (absolute) + connect( niffileDir, &QRadioButton::clicked, [=]() + { + file->setText( nifPath ); + file->setFile( nifPath ); + } + ); + + // Validate on OK + connect( btnOk, &QPushButton::clicked, [&]() + { + // Save JPEG Quality + QSettings settings; + settings.setValue( "JPEG/Quality", pixQuality->value() ); + + // TODO: Set up creation of screenshots directory in Options + if ( nifskopeDir->isChecked() ) { + QDir workingDir; + workingDir.mkpath( "screenshots" ); + } + + // Supersampling + int ss = grpSize->checkedId(); + + int w, h; + + w = width(); + h = height(); + + // Resize viewport for supersampling + if ( ss > 1 ) { + w *= ss; + h *= ss; + + resizeGL( w, h ); + } + + QOpenGLFramebufferObjectFormat fboFmt; + fboFmt.setTextureTarget( GL_TEXTURE_2D ); + fboFmt.setInternalTextureFormat( GL_RGB ); + fboFmt.setMipmap( false ); + fboFmt.setAttachment( QOpenGLFramebufferObject::Attachment::Depth ); + fboFmt.setSamples( 16 / ss ); + + QOpenGLFramebufferObject fbo( w, h, fboFmt ); + fbo.bind(); + + update(); + updateGL(); + + fbo.release(); + + QImage * img = new QImage(fbo.toImage()); + + // Return viewport to original size + if ( ss > 1 ) + resizeGL( width(), height() ); + + + QImageWriter writer( file->file() ); + + // Set Compression for formats that can use it + writer.setCompression( 1 ); + + // Handle JPEG/WebP Quality exclusively + // PNG will not use compression if Quality is set + if ( file->file().endsWith( ".jpg", Qt::CaseInsensitive ) ) { + writer.setFormat( "jpg" ); + writer.setQuality( 50 + pixQuality->value() / 2 ); + } else if ( file->file().endsWith( ".webp", Qt::CaseInsensitive ) ) { + writer.setFormat( "webp" ); + writer.setQuality( 75 + pixQuality->value() / 4 ); + } + + if ( writer.write( *img ) ) { + dlg->accept(); + } else { + Message::critical( this, tr( "Could not save %1" ).arg( file->file() ) ); + } + + delete img; + img = nullptr; + } + ); + connect( btnCancel, &QPushButton::clicked, dlg, &QDialog::reject ); + + if ( dlg->exec() != QDialog::Accepted ) { + return; + } +} + + +/* + * QWidget Event Handlers */ void GLView::dragEnterEvent( QDragEnterEvent * e ) { - auto md = e->mimeData(); - if ( md && md->hasUrls() && md->urls().count() == 1 ) { - QUrl url = md->urls().first(); + auto md = e->mimeData(); + if ( md && md->hasUrls() && md->urls().count() == 1 ) { + QUrl url = md->urls().first(); - if ( url.scheme() == "file" ) { - QString fn = url.toLocalFile(); + if ( url.scheme() == "file" ) { + QString fn = url.toLocalFile(); - if ( textures->canLoad( fn ) ) { - fnDragTex = textures->stripPath( fn, model->getFolder() ); - e->accept(); - return; - } - } - } + if ( textures->canLoad( fn ) ) { + fnDragTex = textures->stripPath( fn, model->getFolder() ); + e->accept(); + return; + } + } + } - e->ignore(); + e->ignore(); } void GLView::dragLeaveEvent( QDragLeaveEvent * e ) { - Q_UNUSED( e ); + Q_UNUSED( e ); - if ( iDragTarget.isValid() ) { - model->set( iDragTarget, fnDragTexOrg ); - iDragTarget = QModelIndex(); - fnDragTex = fnDragTexOrg = QString(); - } + if ( iDragTarget.isValid() ) { + model->set( iDragTarget, fnDragTexOrg ); + iDragTarget = QModelIndex(); + fnDragTex = fnDragTexOrg = QString(); + } } void GLView::dragMoveEvent( QDragMoveEvent * e ) { - if ( iDragTarget.isValid() ) { - model->set( iDragTarget, fnDragTexOrg ); - iDragTarget = QModelIndex(); - fnDragTexOrg = QString(); - } + if ( iDragTarget.isValid() ) { + model->set( iDragTarget, fnDragTexOrg ); + iDragTarget = QModelIndex(); + fnDragTexOrg = QString(); + } - QModelIndex iObj = model->getBlock( indexAt( e->pos() ), "NiAVObject" ); + QModelIndex iObj = model->getBlock( indexAt( e->pos() ), "NiAVObject" ); - if ( iObj.isValid() ) { - for ( const auto l : model->getChildLinks( model->getBlockNumber( iObj ) ) ) { - QModelIndex iTxt = model->getBlock( l, "NiTexturingProperty" ); + if ( iObj.isValid() ) { + for ( const auto l : model->getChildLinks( model->getBlockNumber( iObj ) ) ) { + QModelIndex iTxt = model->getBlock( l, "NiTexturingProperty" ); - if ( iTxt.isValid() ) { - QModelIndex iSrc = model->getBlock( model->getLink( iTxt, "Base Texture/Source" ), "NiSourceTexture" ); + if ( iTxt.isValid() ) { + QModelIndex iSrc = model->getBlock( model->getLink( iTxt, "Base Texture/Source" ), "NiSourceTexture" ); - if ( iSrc.isValid() ) { - iDragTarget = model->getIndex( iSrc, "File Name" ); + if ( iSrc.isValid() ) { + iDragTarget = model->getIndex( iSrc, "File Name" ); - if ( iDragTarget.isValid() ) { - fnDragTexOrg = model->get( iDragTarget ); - model->set( iDragTarget, fnDragTex ); - e->accept(); - return; - } - } - } - } - } + if ( iDragTarget.isValid() ) { + fnDragTexOrg = model->get( iDragTarget ); + model->set( iDragTarget, fnDragTex ); + e->accept(); + return; + } + } + } + } + } - e->ignore(); + e->ignore(); } void GLView::dropEvent( QDropEvent * e ) { - iDragTarget = QModelIndex(); - fnDragTex = fnDragTexOrg = QString(); - e->accept(); + iDragTarget = QModelIndex(); + fnDragTex = fnDragTexOrg = QString(); + e->accept(); } void GLView::focusOutEvent( QFocusEvent * ) { - kbd.clear(); + kbd.clear(); } void GLView::keyPressEvent( QKeyEvent * event ) { - switch ( event->key() ) { - case Qt::Key_Up: - case Qt::Key_Down: - case Qt::Key_Left: - case Qt::Key_Right: - case Qt::Key_PageUp: - case Qt::Key_PageDown: - case Qt::Key_A: - case Qt::Key_D: - case Qt::Key_W: - case Qt::Key_S: - //case Qt::Key_R: - //case Qt::Key_F: - case Qt::Key_Q: - case Qt::Key_E: - case Qt::Key_Space: - kbd[event->key()] = true; - break; - case Qt::Key_Escape: - doCompile = true; - - if ( view == ViewWalk ) - doCenter = true; - - update(); - break; - default: - event->ignore(); - break; - } + switch ( event->key() ) { + case Qt::Key_Up: + case Qt::Key_Down: + case Qt::Key_Left: + case Qt::Key_Right: + case Qt::Key_PageUp: + case Qt::Key_PageDown: + case Qt::Key_A: + case Qt::Key_D: + case Qt::Key_W: + case Qt::Key_S: + //case Qt::Key_R: + //case Qt::Key_F: + case Qt::Key_Q: + case Qt::Key_E: + case Qt::Key_Space: + kbd[event->key()] = true; + break; + case Qt::Key_Escape: + doCompile = true; + + if ( view == ViewWalk ) + doCenter = true; + + update(); + break; + default: + event->ignore(); + break; + } } void GLView::keyReleaseEvent( QKeyEvent * event ) { - switch ( event->key() ) { - case Qt::Key_Up: - case Qt::Key_Down: - case Qt::Key_Left: - case Qt::Key_Right: - case Qt::Key_PageUp: - case Qt::Key_PageDown: - case Qt::Key_A: - case Qt::Key_D: - case Qt::Key_W: - case Qt::Key_S: - //case Qt::Key_R: - //case Qt::Key_F: - case Qt::Key_Q: - case Qt::Key_E: - case Qt::Key_Space: - kbd[event->key()] = false; - break; - default: - event->ignore(); - break; - } + switch ( event->key() ) { + case Qt::Key_Up: + case Qt::Key_Down: + case Qt::Key_Left: + case Qt::Key_Right: + case Qt::Key_PageUp: + case Qt::Key_PageDown: + case Qt::Key_A: + case Qt::Key_D: + case Qt::Key_W: + case Qt::Key_S: + //case Qt::Key_R: + //case Qt::Key_F: + case Qt::Key_Q: + case Qt::Key_E: + case Qt::Key_Space: + kbd[event->key()] = false; + break; + default: + event->ignore(); + break; + } } void GLView::mouseDoubleClickEvent( QMouseEvent * ) { - /* - doCompile = true; - if ( ! aViewWalk->isChecked() ) - doCenter = true; - update(); - */ + /* + doCompile = true; + if ( ! aViewWalk->isChecked() ) + doCenter = true; + update(); + */ } void GLView::mouseMoveEvent( QMouseEvent * event ) { - int dx = event->x() - lastPos.x(); - int dy = event->y() - lastPos.y(); + int dx = event->x() - lastPos.x(); + int dy = event->y() - lastPos.y(); - if ( event->buttons() & Qt::LeftButton && !kbd[Qt::Key_Space] ) { - mouseRot += Vector3( dy * .5, 0, dx * .5 ); - } else if ( (event->buttons() & Qt::MidButton) || (event->buttons() & Qt::LeftButton && kbd[Qt::Key_Space]) ) { - float d = axis / (qMax( width(), height() ) + 1); - mouseMov += Vector3( dx * d, -dy * d, 0 ); - } else if ( event->buttons() & Qt::RightButton ) { - setDistance( Dist - (dx + dy) * (axis / (qMax( width(), height() ) + 1)) ); - } + if ( (scene->actionMode & (Scene::Paint | Scene::Vertex)) == (Scene::Paint | Scene::Vertex)) { + vertexPaint(event->pos()); + } else if ( event->buttons() & Qt::LeftButton && !kbd[Qt::Key_Space] ) { + mouseRot += Vector3( dy * .5, 0, dx * .5 ); + } else if ( (event->buttons() & Qt::MidButton) || (event->buttons() & Qt::LeftButton && kbd[Qt::Key_Space]) ) { + float d = axis / (qMax( width(), height() ) + 1); + mouseMov += Vector3( dx * d, -dy * d, 0 ); + } else if ( event->buttons() & Qt::RightButton ) { + setDistance( Dist - (dx + dy) * (axis / (qMax( width(), height() ) + 1)) ); + } - lastPos = event->pos(); + lastPos = event->pos(); } void GLView::mousePressEvent( QMouseEvent * event ) { - if ( event->button() == Qt::ForwardButton || event->button() == Qt::BackButton ) { - event->ignore(); - return; - } - - lastPos = event->pos(); + if ( event->button() == Qt::ForwardButton || event->button() == Qt::BackButton ) { + event->ignore(); + return; + } - if ( (pressPos - event->pos()).manhattanLength() <= 3 ) - cycleSelect++; - else - cycleSelect = 0; + if ( (scene->actionMode & (Scene::Paint | Scene::Vertex)) == (Scene::Paint | Scene::Vertex)) { + startVertexPaint(event->pos()); + } else { + if ( (pressPos - event->pos()).manhattanLength() <= 3 ) + cycleSelect++; + else + cycleSelect = 0; + } - pressPos = event->pos(); + lastPos = event->pos(); + pressPos = event->pos(); } void GLView::mouseReleaseEvent( QMouseEvent * event ) { - if ( !(model && (pressPos - event->pos()).manhattanLength() <= 3) ) - return; + if ( (scene->actionMode & (Scene::Paint | Scene::Vertex)) == (Scene::Paint | Scene::Vertex)) { + endVertexPaint(); + return; + } + + if ( !(model && (pressPos - event->pos()).manhattanLength() <= 3) ) + return; - if ( event->button() == Qt::ForwardButton || event->button() == Qt::BackButton || event->button() == Qt::MiddleButton ) { - event->ignore(); - return; - } + if ( event->button() == Qt::ForwardButton || event->button() == Qt::BackButton || event->button() == Qt::MiddleButton ) { + event->ignore(); + return; + } - auto mods = event->modifiers(); + auto mods = event->modifiers(); - if ( !(mods & Qt::AltModifier) ) { - QModelIndex idx = indexAt( event->pos(), cycleSelect ); - scene->currentBlock = model->getBlock( idx ); - scene->currentIndex = idx.sibling( idx.row(), 0 ); + if ( !(mods & Qt::AltModifier) ) { + QModelIndex idx = indexAt( event->pos(), cycleSelect ); + scene->currentBlock = model->getBlock( idx ); + scene->currentIndex = idx.sibling( idx.row(), 0 ); - if ( idx.isValid() ) { - emit clicked( QModelIndex() ); // HACK: To get Block Details to update - emit clicked( idx ); - } + if ( idx.isValid() ) { + emit clicked( QModelIndex() ); // HACK: To get Block Details to update + emit clicked( idx ); + } - } else { - // Color Picker / Eyedrop tool - QOpenGLFramebufferObjectFormat fboFmt; - fboFmt.setTextureTarget( GL_TEXTURE_2D ); - fboFmt.setInternalTextureFormat( GL_RGB ); - fboFmt.setMipmap( false ); - fboFmt.setAttachment( QOpenGLFramebufferObject::Attachment::Depth ); + } else { + // Color Picker / Eyedrop tool + QOpenGLFramebufferObjectFormat fboFmt; + fboFmt.setTextureTarget( GL_TEXTURE_2D ); + fboFmt.setInternalTextureFormat( GL_RGB ); + fboFmt.setMipmap( false ); + fboFmt.setAttachment( QOpenGLFramebufferObject::Attachment::Depth ); - QOpenGLFramebufferObject fbo( width(), height(), fboFmt ); - fbo.bind(); + QOpenGLFramebufferObject fbo( width(), height(), fboFmt ); + fbo.bind(); - update(); - updateGL(); + update(); + updateGL(); - fbo.release(); + fbo.release(); - QImage * img = new QImage( fbo.toImage() ); + QImage * img = new QImage( fbo.toImage() ); - auto what = img->pixel( event->pos() ); + auto what = img->pixel( event->pos() ); - qglClearColor( QColor( what ) ); - // qDebug() << QColor( what ); + qglClearColor( QColor( what ) ); + // qDebug() << QColor( what ); - delete img; - } + delete img; + } - update(); + update(); } void GLView::wheelEvent( QWheelEvent * event ) { - if ( view == ViewWalk ) - mouseMov += Vector3( 0, 0, event->delta() ); - else - setDistance( Dist * (event->delta() < 0 ? 1.0 / 0.8 : 0.8) ); + if ( view == ViewWalk ) + mouseMov += Vector3( 0, 0, event->delta() ); + else + setDistance( Dist * (event->delta() < 0 ? 1.0 / 0.8 : 0.8) ); } void GLGraphicsView::setupViewport( QWidget * viewport ) { - GLView * glWidget = qobject_cast(viewport); - if ( glWidget ) { - //glWidget->installEventFilter( this ); - } + GLView * glWidget = qobject_cast(viewport); + if ( glWidget ) { + //glWidget->installEventFilter( this ); + } - QGraphicsView::setupViewport( viewport ); + QGraphicsView::setupViewport( viewport ); } bool GLGraphicsView::eventFilter( QObject * o, QEvent * e ) { - //GLView * glWidget = qobject_cast(o); - //if ( glWidget ) { - // - //} + //GLView * glWidget = qobject_cast(o); + //if ( glWidget ) { + // + //} - return QGraphicsView::eventFilter( o, e ); + return QGraphicsView::eventFilter( o, e ); } //void GLGraphicsView::paintEvent( QPaintEvent * e ) @@ -1834,147 +2006,147 @@ bool GLGraphicsView::eventFilter( QObject * o, QEvent * e ) void GLGraphicsView::drawForeground( QPainter * painter, const QRectF & rect ) { - QGraphicsView::drawForeground( painter, rect ); + QGraphicsView::drawForeground( painter, rect ); } void GLGraphicsView::drawBackground( QPainter * painter, const QRectF & rect ) { - Q_UNUSED( painter ); Q_UNUSED( rect ); + Q_UNUSED( painter ); Q_UNUSED( rect ); - GLView * glWidget = qobject_cast(viewport()); - if ( glWidget ) { - glWidget->updateGL(); - } + GLView * glWidget = qobject_cast(viewport()); + if ( glWidget ) { + glWidget->updateGL(); + } - //QGraphicsView::drawBackground( painter, rect ); + //QGraphicsView::drawBackground( painter, rect ); } void GLGraphicsView::dragEnterEvent( QDragEnterEvent * e ) { - // Intercept NIF files - if ( e->mimeData()->hasUrls() ) { - QList urls = e->mimeData()->urls(); - for ( auto url : urls ) { - if ( url.scheme() == "file" ) { - QString fn = url.toLocalFile(); - QFileInfo finfo( fn ); - if ( finfo.exists() && NifSkope::fileExtensions().contains( finfo.suffix(), Qt::CaseInsensitive ) ) { - draggedNifs << finfo.absoluteFilePath(); - } - } - } - - if ( !draggedNifs.isEmpty() ) { - e->accept(); - return; - } - } - - // Pass event on to viewport for any texture drag/drops - GLView * glWidget = qobject_cast(viewport()); - if ( glWidget ) { - glWidget->dragEnterEvent( e ); - } + // Intercept NIF files + if ( e->mimeData()->hasUrls() ) { + QList urls = e->mimeData()->urls(); + for ( auto url : urls ) { + if ( url.scheme() == "file" ) { + QString fn = url.toLocalFile(); + QFileInfo finfo( fn ); + if ( finfo.exists() && NifSkope::fileExtensions().contains( finfo.suffix(), Qt::CaseInsensitive ) ) { + draggedNifs << finfo.absoluteFilePath(); + } + } + } + + if ( !draggedNifs.isEmpty() ) { + e->accept(); + return; + } + } + + // Pass event on to viewport for any texture drag/drops + GLView * glWidget = qobject_cast(viewport()); + if ( glWidget ) { + glWidget->dragEnterEvent( e ); + } } void GLGraphicsView::dragLeaveEvent( QDragLeaveEvent * e ) { - if ( !draggedNifs.isEmpty() ) { - draggedNifs.clear(); - e->ignore(); - return; - } + if ( !draggedNifs.isEmpty() ) { + draggedNifs.clear(); + e->ignore(); + return; + } - // Pass event on to viewport for any texture drag/drops - GLView * glWidget = qobject_cast(viewport()); - if ( glWidget ) { - glWidget->dragLeaveEvent( e ); - } + // Pass event on to viewport for any texture drag/drops + GLView * glWidget = qobject_cast(viewport()); + if ( glWidget ) { + glWidget->dragLeaveEvent( e ); + } } void GLGraphicsView::dragMoveEvent( QDragMoveEvent * e ) { - if ( !draggedNifs.isEmpty() ) { - e->accept(); - return; - } + if ( !draggedNifs.isEmpty() ) { + e->accept(); + return; + } - // Pass event on to viewport for any texture drag/drops - GLView * glWidget = qobject_cast(viewport()); - if ( glWidget ) { - glWidget->dragMoveEvent( e ); - } + // Pass event on to viewport for any texture drag/drops + GLView * glWidget = qobject_cast(viewport()); + if ( glWidget ) { + glWidget->dragMoveEvent( e ); + } } void GLGraphicsView::dropEvent( QDropEvent * e ) { - if ( !draggedNifs.isEmpty() ) { - auto ns = qobject_cast(parentWidget()); - if ( ns ) { - ns->openFiles( draggedNifs ); - } + if ( !draggedNifs.isEmpty() ) { + auto ns = qobject_cast(parentWidget()); + if ( ns ) { + ns->openFiles( draggedNifs ); + } - draggedNifs.clear(); - e->accept(); - return; - } + draggedNifs.clear(); + e->accept(); + return; + } - // Pass event on to viewport for any texture drag/drops - GLView * glWidget = qobject_cast(viewport()); - if ( glWidget ) { - glWidget->dropEvent( e ); - } + // Pass event on to viewport for any texture drag/drops + GLView * glWidget = qobject_cast(viewport()); + if ( glWidget ) { + glWidget->dropEvent( e ); + } } void GLGraphicsView::focusOutEvent( QFocusEvent * e ) { - GLView * glWidget = qobject_cast(viewport()); - if ( glWidget ) { - glWidget->focusOutEvent( e ); - } + GLView * glWidget = qobject_cast(viewport()); + if ( glWidget ) { + glWidget->focusOutEvent( e ); + } } void GLGraphicsView::keyPressEvent( QKeyEvent * e ) { - GLView * glWidget = qobject_cast(viewport()); - if ( glWidget ) { - glWidget->keyPressEvent( e ); - } + GLView * glWidget = qobject_cast(viewport()); + if ( glWidget ) { + glWidget->keyPressEvent( e ); + } } void GLGraphicsView::keyReleaseEvent( QKeyEvent * e ) { - GLView * glWidget = qobject_cast(viewport()); - if ( glWidget ) { - glWidget->keyReleaseEvent( e ); - } + GLView * glWidget = qobject_cast(viewport()); + if ( glWidget ) { + glWidget->keyReleaseEvent( e ); + } } void GLGraphicsView::mouseDoubleClickEvent( QMouseEvent * e ) { - GLView * glWidget = qobject_cast(viewport()); - if ( glWidget ) { - glWidget->mouseDoubleClickEvent( e ); - } + GLView * glWidget = qobject_cast(viewport()); + if ( glWidget ) { + glWidget->mouseDoubleClickEvent( e ); + } } void GLGraphicsView::mouseMoveEvent( QMouseEvent * e ) { - GLView * glWidget = qobject_cast(viewport()); - if ( glWidget ) { - glWidget->mouseMoveEvent( e ); - } + GLView * glWidget = qobject_cast(viewport()); + if ( glWidget ) { + glWidget->mouseMoveEvent( e ); + } } void GLGraphicsView::mousePressEvent( QMouseEvent * e ) { - GLView * glWidget = qobject_cast(viewport()); - if ( glWidget ) { - glWidget->mousePressEvent( e ); - } + GLView * glWidget = qobject_cast(viewport()); + if ( glWidget ) { + glWidget->mousePressEvent( e ); + } } void GLGraphicsView::mouseReleaseEvent( QMouseEvent * e ) { - GLView * glWidget = qobject_cast(viewport()); - if ( glWidget ) { - glWidget->mouseReleaseEvent( e ); - } + GLView * glWidget = qobject_cast(viewport()); + if ( glWidget ) { + glWidget->mouseReleaseEvent( e ); + } } void GLGraphicsView::wheelEvent( QWheelEvent * e ) { - GLView * glWidget = qobject_cast(viewport()); - if ( glWidget ) { - glWidget->wheelEvent( e ); - } + GLView * glWidget = qobject_cast(viewport()); + if ( glWidget ) { + glWidget->wheelEvent( e ); + } } diff --git a/src/glview.h b/src/glview.h index acfea7d74..c4eb5da5f 100644 --- a/src/glview.h +++ b/src/glview.h @@ -34,6 +34,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #define GLVIEW #include "gl/glscene.h" +#include "data/nifitem.h" #include // Inherited #include @@ -119,6 +120,21 @@ class GLView final : public QGLWidget ZAxis = 2 }; + enum PaintBlendMode + { + BlendNormal = 0, + BlendAdd = 1, + BlendMultiply = 2 + }; + + struct PaintSettings + { + float brushSize = 24.0f; + Color4 brushColor = {1.0f, 0.0f, 1.0f, 1.0f}; + Color4 brushOpacity = {1.0f, 1.0f, 1.0f, 0.0f}; + PaintBlendMode brushMode = PaintBlendMode::BlendNormal; + }; + void setNif( NifModel * ); Scene * getScene(); @@ -132,6 +148,10 @@ class GLView final : public QGLWidget void rotate( float, float, float ); void zoom( float ); + void startVertexPaint(const QPoint&); + void vertexPaint(const QPoint&); + void endVertexPaint(); + void setCenter(); void setDistance( float ); void setPosition( float, float, float ); @@ -147,6 +167,11 @@ class GLView final : public QGLWidget QColor clearColor() const; + QImage renderIndexImage(); + QModelIndex sampleIndexImagePoint(const QImage& img, const QPoint & p, NifModel* model); + QVector sampleIndexImageCircle(const QImage& img, const QPoint & p, float radius, NifModel* model); + + QModelIndex colorIndexToModelIndex(const QColor& color, NifModel* model); QModelIndex indexAt( const QPoint & p, int cycle = 0 ); @@ -170,6 +195,7 @@ public slots: void updateAnimationState( bool checked ); void setVisMode( Scene::VisMode, bool checked = true ); void updateSettings(); + void setVertexPaintSettings(GLView::PaintSettings settings); signals: void clicked( const QModelIndex & ); @@ -237,12 +263,16 @@ protected slots: Transform viewTrans; GLdouble aspect; - + QHash kbd; QPoint lastPos; QPoint pressPos; Vector3 mouseMov; Vector3 mouseRot; + bool mousePaint; + QVector mousePaintVerts; + QImage mousePaintHitDetectImg; + int cycleSelect; QPersistentModelIndex iDragTarget; @@ -262,6 +292,8 @@ protected slots: float rotSpd = 45; UpAxis upAxis = ZAxis; + + PaintSettings vertexPaintSettings; } cfg; private slots: diff --git a/src/nifskope.h b/src/nifskope.h index e783f8831..f6b1f13a9 100644 --- a/src/nifskope.h +++ b/src/nifskope.h @@ -203,12 +203,12 @@ public slots: void on_aViewTop_triggered( bool ); void on_aViewFront_triggered( bool ); void on_aViewLeft_triggered( bool ); - + void on_aViewCenter_triggered(); void on_aViewFlip_triggered( bool ); void on_aViewPerspective_toggled( bool ); void on_aViewWalk_triggered( bool ); - + void on_aViewUser_toggled( bool ); void on_aViewUserSave_triggered( bool ); @@ -370,7 +370,7 @@ protected slots: bool selecting = false; bool initialShowEvent = true; - + QProgressBar * progress = nullptr; QDockWidget * dList; @@ -379,6 +379,7 @@ protected slots: QDockWidget * dKfm; QDockWidget * dRefr; QDockWidget * dInsp; + QDockWidget * dPaint; QDockWidget * dBrowser; QToolBar * tool; diff --git a/src/nifskope_ui.cpp b/src/nifskope_ui.cpp index 91b655ccb..c4e785add 100644 --- a/src/nifskope_ui.cpp +++ b/src/nifskope_ui.cpp @@ -48,6 +48,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "ui/widgets/nifview.h" #include "ui/widgets/refrbrowser.h" #include "ui/widgets/inspect.h" +#include "ui/widgets/vertexpaintwidget.h" #include "ui/widgets/xmlcheck.h" #include "ui/about_dialog.h" #include "ui/settingsdialog.h" @@ -143,8 +144,8 @@ void NifSkope::initActions() aSelectFont = ui->aSelectFont; // Build all actions list - allActions = QSet::fromList( - ui->tFile->actions() + allActions = QSet::fromList( + ui->tFile->actions() << ui->mRender->actions() << ui->tRender->actions() << ui->tAnim->actions() @@ -183,12 +184,12 @@ void NifSkope::initActions() connect( ui->aBrowseArchive, &QAction::triggered, this, &NifSkope::archiveDlg ); connect( ui->aOpen, &QAction::triggered, this, &NifSkope::openDlg ); - connect( ui->aSave, &QAction::triggered, this, &NifSkope::save ); + connect( ui->aSave, &QAction::triggered, this, &NifSkope::save ); connect( ui->aSaveAs, &QAction::triggered, this, &NifSkope::saveAsDlg ); // TODO: Assure Actions and Scene state are synced // Set Data for Actions to pass onto Scene when clicking - /* + /* ShowAxes = 0x1, ShowGrid = 0x2, ShowNodes = 0x4, @@ -225,8 +226,9 @@ void NifSkope::initActions() ui->aLighting->setData( Scene::DoLighting ); ui->aDisableShading->setData( Scene::DisableShaders ); - ui->aSelectObject->setData( Scene::SelObject ); - ui->aSelectVertex->setData( Scene::SelVertex ); + ui->aSelectObject->setData( (int)( Scene::Select | Scene::Object ) ); + ui->aSelectVertex->setData( (int)( Scene::Select | Scene::Vertex ) ); + ui->aPaintVertex->setData( (int)( Scene::Paint | Scene::Vertex ) ); auto agroup = [this]( QVector actions, bool exclusive ) { QActionGroup * ag = new QActionGroup( this ); @@ -239,7 +241,7 @@ void NifSkope::initActions() return ag; }; - selectActions = agroup( { ui->aSelectObject, ui->aSelectVertex }, true ); + selectActions = agroup( { ui->aSelectObject, ui->aSelectVertex, ui->aPaintVertex }, true ); connect( selectActions, &QActionGroup::triggered, ogl->getScene(), &Scene::updateSelectMode ); showActions = agroup( { ui->aShowAxes, ui->aShowGrid, ui->aShowNodes, ui->aShowCollision, @@ -315,7 +317,7 @@ void NifSkope::initActions() ogl->setDebugMode( GLView::DbgColorPicker ); else ogl->setDebugMode( GLView::DbgNone ); - + ogl->update(); } ); @@ -372,6 +374,7 @@ void NifSkope::initDockWidgets() dTree = ui->TreeDock; dHeader = ui->HeaderDock; dInsp = ui->InspectDock; + dPaint = ui->PaintDock; dKfm = ui->KfmDock; dBrowser = ui->BrowserDock; @@ -385,17 +388,24 @@ void NifSkope::initDockWidgets() // Hide certain docks by default dRefr->toggleViewAction()->setChecked( false ); dInsp->toggleViewAction()->setChecked( false ); + dPaint->toggleViewAction()->setChecked( false ); dKfm->toggleViewAction()->setChecked( false ); dRefr->setVisible( false ); dInsp->setVisible( false ); + dPaint->setVisible( false ); dKfm->setVisible( false ); // Set Inspect widget dInsp->setWidget( inspect ); - connect( dList->toggleViewAction(), &QAction::triggered, tree, &NifTreeView::clearRootIndex ); + // Push the initial brush settings to the vertex paint widget + ui->vertexPaintSettings->setValue(ogl->cfg.vertexPaintSettings); + + // Connect vertex paint widget to glview + connect(ui->vertexPaintSettings, &PaintSettingsWidget::valueChanged, ogl, &GLView::setVertexPaintSettings); + connect( dList->toggleViewAction(), &QAction::triggered, tree, &NifTreeView::clearRootIndex ); } void NifSkope::initMenu() @@ -501,7 +511,7 @@ void NifSkope::initMenu() QActionGroup * grpTheme = new QActionGroup( this ); - // Fill the action data with the integer correlating to + // Fill the action data with the integer correlating to // their position in WindowTheme and add to the action group. int i = 0; auto themes = ui->mTheme->actions(); @@ -511,7 +521,6 @@ void NifSkope::initMenu() } } - void NifSkope::initToolBars() { // Disable without NIF loaded @@ -521,7 +530,7 @@ void NifSkope::initToolBars() // Status Bar ui->statusbar->setContentsMargins( 0, 0, 0, 0 ); ui->statusbar->addPermanentWidget( progress ); - + // TODO: Split off into own widget ui->statusbar->addPermanentWidget( filePathWidget( this ) ); @@ -575,7 +584,7 @@ void NifSkope::initToolBars() connect( animSlider, &FloatSlider::valueChanged, animSliderEdit, &FloatEdit::setValue ); connect( animSliderEdit, static_cast(&FloatEdit::sigEdited), ogl, &GLView::setSceneTime ); connect( animSliderEdit, static_cast(&FloatEdit::sigEdited), animSlider, &FloatSlider::setValue ); - + // Animations animGroups = new QComboBox( ui->tAnim ); animGroups->setMinimumWidth( 60 ); @@ -645,12 +654,11 @@ void NifSkope::initConnections() connect( this, &NifSkope::completeSave, this, &NifSkope::onSaveComplete ); } - QMenu * NifSkope::lightingWidget() { QMenu * mLight = new QMenu( this ); mLight->setObjectName( "mLight" ); - + auto lightingWidget = new LightingWidget( ogl, mLight ); lightingWidget->setActions( {ui->aLighting, ui->aTextures, ui->aVertexColors, @@ -665,7 +673,6 @@ QMenu * NifSkope::lightingWidget() return mLight; } - QWidget * NifSkope::filePathWidget( QWidget * parent ) { // Show Filepath of loaded NIF @@ -727,7 +734,6 @@ QWidget * NifSkope::filePathWidget( QWidget * parent ) return filepathWidget; } - void NifSkope::archiveDlg() { QString file = QFileDialog::getOpenFileName( this, tr( "Open Archive" ), "", "Archives (*.bsa *.ba2)" ); @@ -808,7 +814,7 @@ void NifSkope::onLoadComplete( bool success, QString & fname ) } else { // File failed to load - Message::append( this, NifModel::tr( readFail ), + Message::append( this, NifModel::tr( readFail ), NifModel::tr( readFailFinal ).arg( fname ), QMessageBox::Critical ); nif->clear(); @@ -829,6 +835,9 @@ void NifSkope::onLoadComplete( bool success, QString & fname ) nif->undoStack->clear(); indexStack->clear(); + // Update widget sizes to fixup ogl view + resizeDone(); + // Center the model on load ogl->center(); @@ -836,7 +845,6 @@ void NifSkope::onLoadComplete( bool success, QString & fname ) QTimer::singleShot( timeout, progress, SLOT( hide() ) ); } - void NifSkope::saveAsDlg() { QString filename = QFileDialog::getSaveFileName( this, tr( "Save File" ), nif->getFileInfo().absoluteFilePath(), @@ -935,7 +943,6 @@ void NifSkope::saveUi() const settings.setValue( "GLView/Perspective", ui->aViewPerspective->isChecked() ); } - void NifSkope::restoreUi() { QSettings settings; @@ -1096,7 +1103,7 @@ void NifSkope::loadTheme() pal.setColor( QPalette::ColorGroup::Disabled, QPalette::HighlightedText, baseCTxtHighlightDark ); // Set Palette and Stylesheet - + QDir qssDir( QApplication::applicationDirPath() ); QStringList qssList( QStringList() << "style.qss" @@ -1219,7 +1226,6 @@ void NifSkope::resizeDone() ogl->resizeGL( centralWidget()->width(), centralWidget()->height() ); } - bool NifSkope::eventFilter( QObject * o, QEvent * e ) { // TODO: This doesn't seem to be doing anything extra @@ -1306,7 +1312,6 @@ bool NifSkope::eventFilter( QObject * o, QEvent * e ) * ********************** */ - void NifSkope::contextMenu( const QPoint & pos ) { QModelIndex idx; @@ -1451,7 +1456,7 @@ void NifSkope::on_aViewWalk_triggered( bool checked ) void NifSkope::on_aViewUserSave_triggered( bool checked ) -{ +{ Q_UNUSED( checked ); ogl->saveUserView(); ui->aViewUser->setChecked( true ); diff --git a/src/ui/nifskope.ui b/src/ui/nifskope.ui index 2ecad1d36..6001bf1ed 100644 --- a/src/ui/nifskope.ui +++ b/src/ui/nifskope.ui @@ -10,7 +10,7 @@ 800 - + @@ -69,6 +69,7 @@ + @@ -278,6 +279,7 @@ + @@ -1021,6 +1023,33 @@ background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0 + + + Paint Vertex + + + 2 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + true @@ -2093,6 +2122,32 @@ background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0 Error Color + + + true + + + + :/btn/paintVerts:/btn/paintVerts + + + Paint Vertex + + + Paint Vertex + + + + + true + + + Paint + + + Paint + + @@ -2105,6 +2160,12 @@ background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0 QTextBrowser
ui/widgets/refrbrowser.h
+ + PaintSettingsWidget + QWidget +
ui/widgets/vertexpaintwidget.h
+ 1 +
@@ -2654,6 +2715,70 @@ background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0 + + aTogglePaintSettings + toggled(bool) + PaintDock + setVisible(bool) + + + -1 + -1 + + + 1112 + 466 + + + + + PaintDock + visibilityChanged(bool) + aTogglePaintSettings + setChecked(bool) + + + 1112 + 466 + + + -1 + -1 + + + + + aTogglePaintSettings + triggered() + PaintDock + raise() + + + -1 + -1 + + + 1112 + 466 + + + + + aPaintVertex + toggled(bool) + aTogglePaintSettings + setChecked(bool) + + + -1 + -1 + + + -1 + -1 + + + openURL() diff --git a/src/ui/widgets/uvedit.cpp b/src/ui/widgets/uvedit.cpp index 1360e7a53..f7f8331a1 100644 --- a/src/ui/widgets/uvedit.cpp +++ b/src/ui/widgets/uvedit.cpp @@ -478,7 +478,7 @@ void UVWidget::setupViewport( int width, int height ) glMatrixMode( GL_PROJECTION ); glLoadIdentity(); - glViewport( 0, 0, width, height ); + glViewport( 0, 0, width * devicePixelRatioF(), height * devicePixelRatioF() ); glOrtho( glViewRect[0], glViewRect[1], glViewRect[2], glViewRect[3], -10.0, +10.0 ); } @@ -979,7 +979,7 @@ bool UVWidget::setTexCoords() tris << nif->getArray( nif->index( i, 0, partIdx ), "Triangles" ); } } - + } if ( tris.isEmpty() ) @@ -1022,7 +1022,7 @@ void UVWidget::updateNif() nif->dataChanged( iShape, iShape ); } - + nif->restoreState(); connect( nif, &NifModel::dataChanged, this, &UVWidget::nifDataChanged ); } diff --git a/src/ui/widgets/vertexpaintwidget.cpp b/src/ui/widgets/vertexpaintwidget.cpp new file mode 100644 index 000000000..cdc653a99 --- /dev/null +++ b/src/ui/widgets/vertexpaintwidget.cpp @@ -0,0 +1,267 @@ +#include "vertexpaintwidget.h" +#include "ui_vertexpaintwidget.h" +#include + +enum OpacityMode : int +{ + Color = 0, + Alpha = 1, + ColorAndAlpha = 2 +}; + +PaintSettingsWidget::PaintSettingsWidget(QWidget *parent) : + QWidget(parent), + ui(new Ui::PaintSettingsWidget) +{ + ui->setupUi(this); + supressUpdate_ = false; + supressHexUpdate_ = false; + supressWheelUpdate_ = false; + + ui->opacityMode->addItems({"Color", "Alpha", "Color + Alpha"}); + ui->blendMode->setCurrentIndex(0); + ui->blendMode->addItems({"Normal","Add","Multiply"}); + ui->blendMode->setCurrentIndex(OpacityMode::Color); + setHexFromColor(); + setPreviewFromValue(); + setWheelFromColor(); + + // Hookup change events to handle sync of the hex and double color reps + connect(ui->colorR, SIGNAL(valueChanged(double)), this, SLOT(setHexFromColor())); + connect(ui->colorG, SIGNAL(valueChanged(double)), this, SLOT(setHexFromColor())); + connect(ui->colorB, SIGNAL(valueChanged(double)), this, SLOT(setHexFromColor())); + connect(ui->colorA, SIGNAL(valueChanged(double)), this, SLOT(setHexFromColor())); + connect(ui->colorR, SIGNAL(valueChanged(double)), this, SLOT(setWheelFromColor())); + connect(ui->colorG, SIGNAL(valueChanged(double)), this, SLOT(setWheelFromColor())); + connect(ui->colorB, SIGNAL(valueChanged(double)), this, SLOT(setWheelFromColor())); + connect(ui->colorA, SIGNAL(valueChanged(double)), this, SLOT(setWheelFromColor())); + connect(ui->colorHex, SIGNAL(textChanged(QString)), this, SLOT(setColorFromHex())); + connect(ui->colorWheel, SIGNAL(sigColor(QColor)), this, SLOT(setColorFromWheel())); + + // Hookup change events to update the paint config + connect(ui->brushSize, SIGNAL(valueChanged(int)), this, SLOT(updateValue())); + connect(ui->colorR, SIGNAL(valueChanged(double)), this, SLOT(updateValue())); + connect(ui->colorG, SIGNAL(valueChanged(double)), this, SLOT(updateValue())); + connect(ui->colorB, SIGNAL(valueChanged(double)), this, SLOT(updateValue())); + connect(ui->colorA, SIGNAL(valueChanged(double)), this, SLOT(updateValue())); + connect(ui->opacity, SIGNAL(valueChanged(int)), this, SLOT(updateValue())); + connect(ui->opacityMode, SIGNAL(currentIndexChanged(int)), this, SLOT(updateValue())); + connect(ui->blendMode, SIGNAL(currentIndexChanged(int)), this, SLOT(updateValue())); +} + +static int colorDoubleToInt(double value) +{ + return qBound((int)std::round(value*255.0), 0, 255); +} + +static double colorIntToDouble(int value) +{ + return qBound((double)value/255.0, 0.0, 1.0); +} + +void PaintSettingsWidget::setValue(const GLView::PaintSettings& value) +{ + supressUpdate_ = true; + + // Read all the settings that we can directly into controls + ui->brushSize->setValue(value.brushSize); + ui->colorR->setValue(value.brushColor.red()); + ui->colorG->setValue(value.brushColor.green()); + ui->colorB->setValue(value.brushColor.blue()); + ui->colorA->setValue(value.brushColor.alpha()); + setHexFromColor(); + setWheelFromColor(); + ui->blendMode->setCurrentIndex((int)value.brushMode); + + // Infer the opacity from the provided brushOpacity + if (value.brushOpacity.red() != 0 && + value.brushOpacity.green() != 0 && + value.brushOpacity.blue() != 0 && + value.brushOpacity.alpha() == 0) + { + ui->opacityMode->setCurrentIndex(OpacityMode::Color); + ui->opacity->setValue(colorDoubleToInt(value.brushOpacity.red())); + } + else if (value.brushOpacity.red() == 0 && + value.brushOpacity.green() == 0 && + value.brushOpacity.blue() == 0 && + value.brushOpacity.alpha() != 0) + { + ui->opacityMode->setCurrentIndex(OpacityMode::Alpha); + ui->opacity->setValue(colorDoubleToInt(value.brushOpacity.alpha())); + } + else + { + ui->opacityMode->setCurrentIndex(OpacityMode::ColorAndAlpha); + ui->opacity->setValue(colorDoubleToInt(value.brushOpacity.red())); + } + + supressUpdate_ = false; + + // With all controls populated, issue a manual updateValue + updateValue(); +} + +void PaintSettingsWidget::updateValue() +{ + if (supressUpdate_) + return; + + value_.brushColor = Color4(ui->colorR->value(), + ui->colorG->value(), + ui->colorB->value(), + ui->colorA->value()); + + value_.brushSize = (float)ui->brushSize->value(); + + float opacity = (float)ui->opacity->value() / 255.0f; + if (ui->opacityMode->currentIndex() == OpacityMode::Color) // Color only + { + value_.brushOpacity = Color4(opacity,opacity,opacity,0.0f); + } + else if (ui->opacityMode->currentIndex() == OpacityMode::Alpha) // Alpha only + { + value_.brushOpacity = Color4(0,0,0,opacity); + } + else if (ui->opacityMode->currentIndex() == OpacityMode::ColorAndAlpha) // Color+Alpha + { + value_.brushOpacity = Color4(opacity,opacity,opacity,opacity); + } + + value_.brushMode = (GLView::PaintBlendMode)ui->blendMode->currentIndex(); + + // Update some internal stuff too... + setPreviewFromValue(); + + emit valueChanged(value_); +} + +void PaintSettingsWidget::setColorFromHex() +{ + QString hex = ui->colorHex->text(); + bool ok; + + supressHexUpdate_ = true; + + // Red + if (hex.length() >= 2) + { + int i = hex.midRef(0,2).toInt(&ok, 16); + if (ok) + ui->colorR->setValue(colorIntToDouble(i)); + } + else + { + ui->colorR->setValue(0.0); + ui->colorG->setValue(0.0); + ui->colorB->setValue(0.0); + ui->colorA->setValue(1.0); + } + + // Green + if (hex.length() >= 4) + { + int i = hex.midRef(2,2).toInt(&ok, 16); + if (ok) + ui->colorG->setValue(colorIntToDouble(i)); + } + else + { + ui->colorG->setValue(0.0); + ui->colorB->setValue(0.0); + ui->colorA->setValue(1.0); + } + + // Blue + if (hex.length() >= 6) + { + int i = hex.midRef(4,2).toInt(&ok, 16); + if (ok) + ui->colorB->setValue(colorIntToDouble(i)); + } + else + { + ui->colorB->setValue(0.0); + ui->colorA->setValue(1.0); + } + + // Alpha + if (hex.length() >= 8) + { + int i = hex.midRef(6,2).toInt(&ok, 16); + if (ok) + ui->colorA->setValue(colorIntToDouble(i)); + } + else + { + ui->colorA->setValue(1.0); + } + + supressHexUpdate_ = false; +} + +void PaintSettingsWidget::setHexFromColor() +{ + if (supressHexUpdate_) + return; + + // Convert the color components from doubles [0.0 , 1.0] to ints [0 , 255] + int r = colorDoubleToInt(ui->colorR->value()); + int g = colorDoubleToInt(ui->colorG->value()); + int b = colorDoubleToInt(ui->colorB->value()); + int a = colorDoubleToInt(ui->colorA->value()); + + // Format the rgba int components into an 8 character hex string like FFAA00FF + QString result = QString("%1").arg(r, 2, 16, QLatin1Char('0')).toUpper() + + QString("%1").arg(g, 2, 16, QLatin1Char('0')).toUpper() + + QString("%1").arg(b, 2, 16, QLatin1Char('0')).toUpper() + + QString("%1").arg(a, 2, 16, QLatin1Char('0')).toUpper(); + ui->colorHex->setText(result); +} + +void PaintSettingsWidget::setColorFromWheel() +{ + supressWheelUpdate_ = true; + + QColor color = ui->colorWheel->getColor(); + ui->colorR->setValue(color.redF()); + ui->colorG->setValue(color.greenF()); + ui->colorB->setValue(color.blueF()); + + supressWheelUpdate_ = false; +} + +void PaintSettingsWidget::setWheelFromColor() +{ + if (supressWheelUpdate_) + return; + + int r = colorDoubleToInt(ui->colorR->value()); + int g = colorDoubleToInt(ui->colorG->value()); + int b = colorDoubleToInt(ui->colorB->value()); + int a = colorDoubleToInt(ui->colorA->value()); + + ui->colorWheel->setColor(QColor::fromRgb(r,g,b,a)); +} + +void PaintSettingsWidget::setPreviewFromValue() +{ + QPalette pal = QPalette(); + pal.setColor(QPalette::Button, QColor::fromRgbF(value_.brushColor.red(), + value_.brushColor.green(), + value_.brushColor.blue(), + 1.0f)); + ui->colorPreview->setPalette(pal); + + QPalette palA = QPalette(); + palA.setColor(QPalette::Button, QColor::fromRgbF(value_.brushColor.red(), + value_.brushColor.green(), + value_.brushColor.blue(), + value_.brushColor.alpha())); + ui->colorPreviewAlpha->setPalette(palA); +} + +PaintSettingsWidget::~PaintSettingsWidget() +{ + delete ui; +} diff --git a/src/ui/widgets/vertexpaintwidget.h b/src/ui/widgets/vertexpaintwidget.h new file mode 100644 index 000000000..7017d62c1 --- /dev/null +++ b/src/ui/widgets/vertexpaintwidget.h @@ -0,0 +1,41 @@ +#ifndef VERTEXPAINTWIDGET_H +#define VERTEXPAINTWIDGET_H + +#include +#include + +namespace Ui { +class PaintSettingsWidget; +} + +class PaintSettingsWidget final : public QWidget +{ + Q_OBJECT + +public: + explicit PaintSettingsWidget(QWidget *parent = nullptr); + ~PaintSettingsWidget(); + +public slots: + void setValue( const GLView::PaintSettings& value ); + +signals: + void valueChanged( const GLView::PaintSettings& value ); + +protected slots: + void setColorFromHex(); + void setColorFromWheel(); + void setPreviewFromValue(); + void setHexFromColor(); + void setWheelFromColor(); + void updateValue(); + +private: + Ui::PaintSettingsWidget *ui; + GLView::PaintSettings value_; + bool supressUpdate_; + bool supressHexUpdate_; + bool supressWheelUpdate_; +}; + +#endif // VERTEXPAINTWIDGET_H diff --git a/src/ui/widgets/vertexpaintwidget.ui b/src/ui/widgets/vertexpaintwidget.ui new file mode 100644 index 000000000..8a0f4bf42 --- /dev/null +++ b/src/ui/widgets/vertexpaintwidget.ui @@ -0,0 +1,468 @@ + + + PaintSettingsWidget + + + + 0 + 0 + 366 + 183 + + + + Form + + + + + + + 1 + 0 + + + + Brush Size + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 128 + + + 1 + + + 10 + + + 24 + + + Qt::Horizontal + + + + + + + 1 + + + 128 + + + 24 + + + + + + + + + + + 0 + 0 + + + + Color + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 3 + + + 1.000000000000000 + + + 0.003906000000000 + + + 1.000000000000000 + + + + + + + 3 + + + 1.000000000000000 + + + 0.003906000000000 + + + + + + + 3 + + + 1.000000000000000 + + + 0.003906000000000 + + + 1.000000000000000 + + + + + + + 3 + + + 1.000000000000000 + + + 0.003906000000000 + + + 1.000000000000000 + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 1 + 0 + + + + + + + + + 2 + 0 + + + + + 16777215 + 16777215 + + + + HHHHHHHH + + + FF00FFFF + + + 8 + + + + + + + + 1 + 0 + + + + true + + + QFrame::StyledPanel + + + 2 + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + true + + + + + + true + + + + + + + + 0 + 0 + + + + true + + + + + + true + + + + + + + + + + + + + Opacity + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 255 + + + 255 + + + Qt::Horizontal + + + + + + + 255 + + + 255 + + + + + + + + + + Blend Mode + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + -1 + + + + + + + + + + + + + + + + + ColorWheel + QWidget +
ui/widgets/colorwheel.h
+ 1 +
+
+ + + + brushSizeSlider + valueChanged(int) + brushSize + setValue(int) + + + 194 + 21 + + + 332 + 21 + + + + + brushSize + valueChanged(int) + brushSizeSlider + setValue(int) + + + 332 + 21 + + + 194 + 21 + + + + + opacity + valueChanged(int) + opacitySlider + setValue(int) + + + 331 + 113 + + + 193 + 113 + + + + + opacitySlider + valueChanged(int) + opacity + setValue(int) + + + 193 + 113 + + + 331 + 113 + + + + +
diff --git a/src/xml/kfmxml.cpp b/src/xml/kfmxml.cpp index 878fd0e0e..89a18de10 100644 --- a/src/xml/kfmxml.cpp +++ b/src/xml/kfmxml.cpp @@ -140,16 +140,16 @@ class KfmXmlHandler final : public QXmlDefaultHandler QString abs = list.value( "abstract" ); NifData data( - list.value( "name" ), + list.value( "name" ), type, tmpl, - NifValue( NifValue::type( type ) ), - list.value( "arg" ), + NifValue( NifValue::type( type ) ), + list.value( "arg" ), arr1, arr2, cond, - KfmModel::version2number( ver1 ), - KfmModel::version2number( ver2 ) + KfmModel::version2number( ver1 ), + KfmModel::version2number( ver2 ) ); bool isTemplated = (type == "TEMPLATE" || tmpl == "TEMPLATE"); @@ -263,9 +263,12 @@ bool KfmModel::loadXML() QDir dir( QCoreApplication::applicationDirPath() ); QString fname; QStringList xmlList( QStringList() - << "kfm.xml" + << "kfm.xml" #ifdef Q_OS_LINUX - << "/usr/share/nifskope/kfm.xml" + << "/usr/share/nifskope/kfm.xml" +#endif +#ifdef Q_OS_MACX + << "../../../kfm.xml" #endif ); for ( const QString& str : xmlList ) { diff --git a/src/xml/nifxml.cpp b/src/xml/nifxml.cpp index 182e0629c..ded08178c 100644 --- a/src/xml/nifxml.cpp +++ b/src/xml/nifxml.cpp @@ -526,8 +526,8 @@ class NifXmlHandler final : public QXmlDefaultHandler bool checkType( const NifData & d ) { return ( NifModel::compounds.contains( d.type() ) - || NifValue::type( d.type() ) != NifValue::tNone - || d.type() == "TEMPLATE" + || NifValue::type( d.type() ) != NifValue::tNone + || d.type() == "TEMPLATE" ); } @@ -535,10 +535,10 @@ class NifXmlHandler final : public QXmlDefaultHandler bool checkTemp( const NifData & d ) { return ( d.temp().isEmpty() - || NifValue::type( d.temp() ) != NifValue::tNone - || d.temp() == "TEMPLATE" - || NifModel::blocks.contains( d.temp() ) - || NifModel::compounds.contains( d.temp() ) + || NifValue::type( d.temp() ) != NifValue::tNone + || d.temp() == "TEMPLATE" + || NifModel::blocks.contains( d.temp() ) + || NifModel::compounds.contains( d.temp() ) ); } @@ -603,9 +603,12 @@ bool NifModel::loadXML() QDir dir( QCoreApplication::applicationDirPath() ); QString fname; QStringList xmlList( QStringList() - << "nif.xml" + << "nif.xml" #ifdef Q_OS_LINUX - << "/usr/share/nifskope/nif.xml" + << "/usr/share/nifskope/nif.xml" +#endif +#ifdef Q_OS_MACX + << "../../../nif.xml" #endif ); for ( const QString& str : xmlList ) {